Compare commits
	
		
			2 Commits
		
	
	
		
			d244cbf589
			...
			ff2a9e21b5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ff2a9e21b5 | |||
| 24c7720831 | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,5 +1,12 @@ | |||||||
| /goserv | /goserv | ||||||
|  | *.claims.json | ||||||
|  | *.jwk.json | ||||||
|  | *.jws.json | ||||||
|  | *.jwt.txt | ||||||
| 
 | 
 | ||||||
|  | xversion.go | ||||||
|  | 
 | ||||||
|  | *_string.go | ||||||
| *_vfsdata.go | *_vfsdata.go | ||||||
| *.env | *.env | ||||||
| .env | .env | ||||||
|  | |||||||
							
								
								
									
										37
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | # This is an example goreleaser.yaml file with some sane defaults. | ||||||
|  | # Make sure to check the documentation at http://goreleaser.com | ||||||
|  | before: | ||||||
|  |   hooks: | ||||||
|  |     # You may remove this if you don't use go modules. | ||||||
|  |     - go mod download | ||||||
|  |     # you may remove this if you don't need go generate | ||||||
|  |     - go generate ./... | ||||||
|  | builds: | ||||||
|  |   - env: | ||||||
|  |       - CGO_ENABLED=0 | ||||||
|  |     goos: | ||||||
|  |       - linux | ||||||
|  |       - windows | ||||||
|  |       - darwin | ||||||
|  |     goarch: | ||||||
|  |       - amd64 | ||||||
|  |       - arm64 | ||||||
|  |       - arm | ||||||
|  | archives: | ||||||
|  |   - replacements: | ||||||
|  |       darwin: Darwin | ||||||
|  |       linux: Linux | ||||||
|  |       windows: Windows | ||||||
|  |       386: i386 | ||||||
|  |       amd64: x86_64 | ||||||
|  |       arm64: aarch64 | ||||||
|  | checksum: | ||||||
|  |   name_template: 'checksums.txt' | ||||||
|  | snapshot: | ||||||
|  |   name_template: "{{ .Tag }}-next" | ||||||
|  | changelog: | ||||||
|  |   sort: asc | ||||||
|  |   filters: | ||||||
|  |     exclude: | ||||||
|  |       - '^docs:' | ||||||
|  |       - '^test:' | ||||||
							
								
								
									
										191
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										191
									
								
								README.md
									
									
									
									
									
								
							| @ -1,22 +1,69 @@ | |||||||
| # goserv | # [GoServ](https://git.coolaj86.com/coolaj86/goserv) | ||||||
|  | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| > Boilerplate for how I like to write a backend web service | > Boilerplate for how I like to write a backend web service | ||||||
| 
 | 
 | ||||||
| ## Build | ## Build | ||||||
| 
 | 
 | ||||||
|  | #### Goreleaser | ||||||
|  | 
 | ||||||
|  | See <https://webinstall.dev/goreleaser> | ||||||
|  | 
 | ||||||
|  | Local-only | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | goreleaser --snapshot --skip-publish --rm-dist | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Publish | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Get a token at https://github.com/settings/tokens | ||||||
|  | export GITHUB_TOKEN=xxxxxxx | ||||||
|  | 
 | ||||||
|  | # Remove --snapshot to error on non-clean releases | ||||||
|  | goreleaser --snapshot --rm-dist | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The platform, publish URL, and token file can be changed in `.goreleaser.yml`: | ||||||
|  | 
 | ||||||
|  | ```yml | ||||||
|  | env_files: | ||||||
|  |   gitea_token: ~/.config/goreleaser/gitea_token | ||||||
|  | gitea_urls: | ||||||
|  |   api: https://try.gitea.io/api/v1/ | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Manually | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | git clone ssh://gitea@git.coolaj86.com:22042/coolaj86/goserv.git ./goserv | ||||||
|  | pushd ./goserv | ||||||
|  | bash ./examples/build.sh | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ```bash | ```bash | ||||||
| export GOFLAGS="-mod=vendor" | export GOFLAGS="-mod=vendor" | ||||||
| go mod tidy | go mod tidy | ||||||
| go mod vendor | go mod vendor | ||||||
| go generate -mod=vendor ./... | go generate -mod=vendor ./... | ||||||
| go build -mod=vendor . | go build -mod=vendor -o dist/goserv . | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | To build for another platform (such as Raspberry Pi) set `GOOS` and GOARCH`: | ||||||
|  | 
 | ||||||
| ```bash | ```bash | ||||||
| ./goserv run --listen :3000 --serve-path ./overrides | GOOS=linux GOARCH=arm64 go build -mod=vendor -o dist/goserv-linux-arm64 . | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Eamples and Config Templates | #### Run | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | ./dist/goserv run --listen :3000 --trust-proxy --serve-path ./overrides | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Examples and Config Templates | ||||||
| 
 | 
 | ||||||
| The example files are located in `./examples` | The example files are located in `./examples` | ||||||
| 
 | 
 | ||||||
| @ -24,6 +71,128 @@ The example files are located in `./examples` | |||||||
| - .env (environment variables) | - .env (environment variables) | ||||||
| - build.sh | - build.sh | ||||||
| 
 | 
 | ||||||
|  | ```bash | ||||||
|  | export BASE_URL=https://example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Authentication | ||||||
|  | 
 | ||||||
|  | You can use an OIDC provider or sign your own tokens. | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | go install -mod=vendor git.rootprojects.org/root/keypairs/cmd/keypairs | ||||||
|  | 
 | ||||||
|  | # Generate a keypair | ||||||
|  | keypairs gen -o key.jwk.json --pub pub.jwk.json | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Create an Admin token: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Sign an Admin token | ||||||
|  | echo '{ "sub": "random_ppid", "email": "me@example.com", "iss": "'"${BASE_URL}"'" }' \ | ||||||
|  |     > admin.claims.json | ||||||
|  | keypairs sign --exp 1h ./key.jwk.json ./admin.claims.json > admin.jwt.txt 2> admin.jws.json | ||||||
|  | 
 | ||||||
|  | # verify the Admin token | ||||||
|  | keypairs verify ./pub.jwk.json ./admin.jwt.txt | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Create a User token: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Sign a User token | ||||||
|  | echo '{ "sub": "random_ppid", "email": "me@example.com", "iss": "'"${BASE_URL}"'" }' \ | ||||||
|  |     > user.claims.json | ||||||
|  | keypairs sign --exp 1h ./key.jwk.json ./user.claims.json > user.jwt.txt 2> user.jws.json | ||||||
|  | 
 | ||||||
|  | # verify the User token | ||||||
|  | keypairs verify ./pub.jwk.json ./user.jwt.txt | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | **Impersonation** can be accomplished by appending the token `sub` (aka PPID) to the url as | ||||||
|  | `?user_id=`. | ||||||
|  | 
 | ||||||
|  | ## REST API | ||||||
|  | 
 | ||||||
|  | All routes require authentication, except for those at `/api/public`. | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | Authentication: Bearer <token> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Here's the API, in brief: | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | # Demo Mode Only | ||||||
|  | DELETE /public/reset                                    Drop database and re-initialize | ||||||
|  | 
 | ||||||
|  | # Public | ||||||
|  | GET  /public/ping                                       Health Check | ||||||
|  | POST /public/setup                  <= (none)           Bootstrap | ||||||
|  | 
 | ||||||
|  | # Admin-only | ||||||
|  | GET  /admin/ping                                        (authenticated) Health Check | ||||||
|  | 
 | ||||||
|  | # User | ||||||
|  | GET  /user/ping                                         (authenticated) Health Check | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | When you `GET` anything, it will be wrapped in the `result`. | ||||||
|  | 
 | ||||||
|  | #### Bootstrapping | ||||||
|  | 
 | ||||||
|  | The first user to hit the `/api/setup` endpoint will be the **admin**: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | export TOKEN=$(cat admin.jwt.txt) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | curl -X POST "${BASE_URL}/api/public/setup" -H "Authorization: Bearer ${TOKEN}" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Then the setup endpoint will be disabled: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | curl -X POST "${BASE_URL}/api/public/setup" -H "Authorization: Bearer ${TOKEN}" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## GoDoc | ||||||
|  | 
 | ||||||
|  | If the documentation is not public hosted you can view it with GoDoc: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | godoc --http :6060 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - <http://localhost:6060/pkg/git.example.com/example/goserv/> | ||||||
|  | - <http://localhost:6060/pkg/git.example.com/example/goserv/internal/api> | ||||||
|  | - <http://localhost:6060/pkg/git.example.com/example/goserv/internal/db> | ||||||
|  | 
 | ||||||
|  | You can see abbreviated documentation with `go`'s built-in `doc`, for example: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | go doc git.example.com/example/goserv/internal/api | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | <http://localhost:6060> | ||||||
|  | 
 | ||||||
|  | ## Test | ||||||
|  | 
 | ||||||
|  | Create a test database. It must have `test` in the name. | ||||||
|  | 
 | ||||||
|  | ```sql | ||||||
|  | DROP DATABASE "goserv_test"; CREATE DATABASE "goserv_test"; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Run the tests: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | export TEST_DATABASE_URL='postgres://postgres:postgres@localhost:5432/goserv_test' | ||||||
|  | go test -mod=vendor ./... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Dependencies | ## Dependencies | ||||||
| 
 | 
 | ||||||
| This setup can be run on a VPS, such as Digital Ocean, OVH, or Scaleway | This setup can be run on a VPS, such as Digital Ocean, OVH, or Scaleway | ||||||
| @ -80,7 +249,7 @@ sudo env PATH="$PATH" \ | |||||||
|     caddy run --config ./Caddyfile |     caddy run --config ./Caddyfile | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| See the Cheat Sheet at https://webinstall.dev/caddy | See the Cheat Sheets at https://webinstall.dev/caddy | ||||||
| and https://webinstall.dev/serviceman | and https://webinstall.dev/serviceman | ||||||
| 
 | 
 | ||||||
| ### PostgreSQL (Database) | ### PostgreSQL (Database) | ||||||
| @ -106,6 +275,14 @@ psql 'postgres://postgres:postgres@localhost:5432/postgres' | |||||||
| See the Cheat Sheets at https://webinstall.dev/postgres | See the Cheat Sheets at https://webinstall.dev/postgres | ||||||
| and https://webinstall.dev/serviceman | and https://webinstall.dev/serviceman | ||||||
| 
 | 
 | ||||||
| ## License | ## Licenses | ||||||
| 
 | 
 | ||||||
| Copyright 2020. All rights reserved. | Copyright 2020 The GoServ Authors. All rights reserved. | ||||||
|  | 
 | ||||||
|  | ### Exceptions | ||||||
|  | 
 | ||||||
|  | - `countries.json` LGPL, taken from <https://github.com/stefangabos/world_countries> | ||||||
|  | - `flags.json` MIT, taken from <https://github.com/matiassingers/emoji-flags> | ||||||
|  | 
 | ||||||
|  | These are probably also in the Public Domain. \ | ||||||
|  | (gathering the official data from any source would yield the same dataset) | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| // +build !dev | // +build !dev | ||||||
| 
 | 
 | ||||||
| //go:generate go run -mod vendor github.com/shurcooL/vfsgen/cmd/vfsgendev -source="git.coolaj86.com/coolaj86/goserv/assets".Assets | //go:generate go run -mod vendor github.com/shurcooL/vfsgen/cmd/vfsgendev -source="git.example.com/example/goserv/assets".Assets | ||||||
| 
 | 
 | ||||||
| package assets | package assets | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| // +build !dev | // +build !dev | ||||||
| 
 | 
 | ||||||
| //go:generate go run -mod vendor github.com/shurcooL/vfsgen/cmd/vfsgendev -source="git.coolaj86.com/coolaj86/goserv/assets/configfs".Assets | //go:generate go run -mod vendor github.com/shurcooL/vfsgen/cmd/vfsgendev -source="git.example.com/example/goserv/assets/configfs".Assets | ||||||
| 
 | 
 | ||||||
| package configfs | package configfs | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								assets/configfs/files/postgres/drop.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								assets/configfs/files/postgres/drop.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | -- this is only used for the tests | ||||||
|  | DROP TABLE IF EXISTS "authn"; | ||||||
|  | DROP TABLE IF EXISTS "events"; | ||||||
| @ -8,12 +8,12 @@ CREATE TABLE IF NOT EXISTS "authn" ( | |||||||
|     "id" TEXT PRIMARY KEY DEFAULT gen_random_uuid(), |     "id" TEXT PRIMARY KEY DEFAULT gen_random_uuid(), | ||||||
|     "ppid" TEXT NOT NULL, |     "ppid" TEXT NOT NULL, | ||||||
|     "email" TEXT NOT NULL, |     "email" TEXT NOT NULL, | ||||||
|     "verified" BOOL NOT NULL DEFAULT FALSE, |     "verified_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00' AT TIME ZONE 'UTC'), | ||||||
|     "created_at" TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), |     "created_at" TIMESTAMPTZ NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), | ||||||
|     "updated_at" TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), |     "updated_at" TIMESTAMPTZ NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), | ||||||
|     "deleted_at" TIMESTAMP NOT NULL DEFAULT ('epoch' AT TIME ZONE 'UTC') |     "deleted_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00' AT TIME ZONE 'UTC') | ||||||
| ); | ); | ||||||
| --CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_slug" ON "authn" ("ppid"); | --CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_ppid" ON "authn" ("ppid"); | ||||||
| CREATE INDEX IF NOT EXISTS "idx_ppid" ON "authn" ("ppid"); | CREATE INDEX IF NOT EXISTS "idx_ppid" ON "authn" ("ppid"); | ||||||
| CREATE INDEX IF NOT EXISTS "idx_email" ON "authn" ("email"); | CREATE INDEX IF NOT EXISTS "idx_email" ON "authn" ("email"); | ||||||
| 
 | 
 | ||||||
| @ -26,8 +26,8 @@ CREATE TABLE IF NOT EXISTS "events" ( | |||||||
|     "table" TEXT NOT NULL, |     "table" TEXT NOT NULL, | ||||||
|     "record" TEXT NOT NULL, |     "record" TEXT NOT NULL, | ||||||
|     "by" TEXT NOT NULL, |     "by" TEXT NOT NULL, | ||||||
|     "at" TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), |     "at" TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC') | ||||||
| ); | ); | ||||||
| --CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_record" ON "events" ("record"); | --CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_record" ON "events" ("record"); | ||||||
| CREATE INDEX IF NOT EXISTS "idx_record" ON authn ("record"); | CREATE INDEX IF NOT EXISTS "idx_record" ON "events" ("record"); | ||||||
| CREATE INDEX IF NOT EXISTS "idx_by" ON authn ("by"); | CREATE INDEX IF NOT EXISTS "idx_by" ON "events" ("by"); | ||||||
|  | |||||||
							
								
								
									
										0
									
								
								assets/configfs/files/postgres/tables.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								assets/configfs/files/postgres/tables.sql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										6
									
								
								doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								doc.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | // See also | ||||||
|  | // | ||||||
|  | // internal/api: http://localhost:6060/pkg/git.example.com/example/project/internal/api | ||||||
|  | // | ||||||
|  | // internal/db: http://localhost:6060/pkg/git.example.com/example/project/internal/db | ||||||
|  | package main | ||||||
| @ -15,6 +15,8 @@ example.com { | |||||||
| 
 | 
 | ||||||
|     # reverse proxy /api to :3000 |     # reverse proxy /api to :3000 | ||||||
|     reverse_proxy /api/* localhost:3000 |     reverse_proxy /api/* localhost:3000 | ||||||
|  |     reverse_proxy /.well-known/openid-configuration localhost:3000 | ||||||
|  |     reverse_proxy /.well-known/jwks.json localhost:3000 | ||||||
| 
 | 
 | ||||||
|     # serve static files from public folder, but not /api |     # serve static files from public folder, but not /api | ||||||
|     @notApi { |     @notApi { | ||||||
| @ -22,6 +24,8 @@ example.com { | |||||||
|             try_files {path} {path}/ {path}/index.html |             try_files {path} {path}/ {path}/index.html | ||||||
|         } |         } | ||||||
|         not path /api/* |         not path /api/* | ||||||
|  |         not path /.well-known/openid-configuration | ||||||
|  |         not path /.well-known/jwks.json | ||||||
|     } |     } | ||||||
|     route { |     route { | ||||||
|       rewrite @notApi {http.matchers.file.relative} |       rewrite @notApi {http.matchers.file.relative} | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								examples/build.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										1
									
								
								examples/build.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -8,4 +8,3 @@ go mod tidy | |||||||
| go mod vendor | go mod vendor | ||||||
| go generate -mod=vendor ./... | go generate -mod=vendor ./... | ||||||
| go build -mod=vendor . | go build -mod=vendor . | ||||||
| 
 |  | ||||||
|  | |||||||
| @ -1,2 +1,22 @@ | |||||||
| PORT="3000" | PORT="3000" | ||||||
| #LISTEN=":3000" | #LISTEN=":3000" | ||||||
|  | DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres | ||||||
|  | TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres_test | ||||||
|  | 
 | ||||||
|  | TRUST_PROXY=false | ||||||
|  | 
 | ||||||
|  | # Supports OIDC-compliant SSO issuers / providers | ||||||
|  | # (Auth0, Google, etc) | ||||||
|  | # (should provide .well-known/openid-configuration and .well-known/jwks.json) | ||||||
|  | OIDC_WHITELIST=https://mock.pocketid.app | ||||||
|  | 
 | ||||||
|  | # Public Key may be provided in addition to or in lieu of OIDC_WHITELIST | ||||||
|  | # can be RSA or ECDSA, either a filename or JWK/JSON (or PEM, but good luck escaping the newlines) | ||||||
|  | # | ||||||
|  | #   go install -mod=vendor git.rootprojects.org/root/keypairs/cmd/keypairs | ||||||
|  | #   keypairs gen -o priv.jwk.json --pub pub.jwk.json | ||||||
|  | # | ||||||
|  | #PUBLIC_KEY='{"crv":"P-256","kid":"kN4qj1w01Ry6ElG9I3qAVJOZFYLDklPFUdHaKozWtmc","kty":"EC","use":"sig","x":"SzzNgrOM_N0GwQWZPGFcdIKmfoQD6aXIzYm4gzGyPgQ","y":"erYeb884pk0BGMewDzEh_qYDB0aOFIxFjrXdqIzkmbw"}' | ||||||
|  | PUBLIC_KEY=./pub.jwk.json | ||||||
|  | 
 | ||||||
|  | #--demo is explicit | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								examples/genkeys.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5
									
								
								examples/genkeys.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | # Install `keypairs` | ||||||
|  | go install -mod=vendor git.rootprojects.org/root/keypairs/cmd/keypairs | ||||||
|  | 
 | ||||||
|  | # Generate a keypair | ||||||
|  | keypairs gen -o key.jwk.json --pub pub.jwk.json | ||||||
							
								
								
									
										4
									
								
								examples/setup.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								examples/setup.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | bash ./examples/build.sh | ||||||
|  | bash ./examples/genkeys.sh | ||||||
|  | bash ./examples/test.sh | ||||||
							
								
								
									
										45
									
								
								examples/test.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										45
									
								
								examples/test.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | export BASE_URL=http://localhost:7070 | ||||||
|  | #export BASE_URL=https://example.com | ||||||
|  | #CURL_OPTS="-sS" | ||||||
|  | CURL_OPTS="" | ||||||
|  | 
 | ||||||
|  | mkdir -p ./tmp/ | ||||||
|  | 
 | ||||||
|  | # Sign an Admin token | ||||||
|  | echo '{ "sub": "admin_ppid", "email": "me@example.com", "iss": "'"${BASE_URL}"'" }' > ./tmp/admin.claims.json | ||||||
|  | keypairs sign --exp 1h ./key.jwk.json ./tmp/admin.claims.json > ./tmp/admin.jwt.txt 2> ./tmp/admin.jws.json | ||||||
|  | export ADMIN_TOKEN=$(cat ./tmp/admin.jwt.txt) | ||||||
|  | 
 | ||||||
|  | # verify the Admin token | ||||||
|  | #keypairs verify ./pub.jwk.json ./admin.jwt.txt | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Sign a User token | ||||||
|  | echo '{ "sub": "random_ppid", "email": "me@example.com", "iss": "'"${BASE_URL}"'" }' > ./tmp/user.claims.json | ||||||
|  | keypairs sign --exp 1h ./key.jwk.json ./tmp/user.claims.json > ./tmp/user.jwt.txt 2> ./tmp/user.jws.json | ||||||
|  | export USER_TOKEN=$(cat ./tmp/user.jwt.txt) | ||||||
|  | 
 | ||||||
|  | # verify the User token | ||||||
|  | #keypairs verify ./pub.jwk.json ./user.jwt.txt | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | EID=$(cat ./user.jws.json | grep sub | cut -d'"' -f 4) | ||||||
|  | 
 | ||||||
|  | echo "" | ||||||
|  | echo 'DELETE /api/public/reset (only works in --demo mode, deletes all data)' | ||||||
|  | curl $CURL_OPTS -X DELETE "${BASE_URL}/api/public/reset" | ||||||
|  | echo "" | ||||||
|  | 
 | ||||||
|  | echo "" | ||||||
|  | echo "Bootstrap with a new admin (only works once)" | ||||||
|  | curl -f $CURL_OPTS -X POST "${BASE_URL}/api/public/setup" \ | ||||||
|  |     -H "Authorization: Bearer ${ADMIN_TOKEN}" | ||||||
|  | echo "" | ||||||
|  | 
 | ||||||
|  | echo "Create a new user" | ||||||
|  | curl $CURL_OPTS -X POST "${BASE_URL}/api/users" \ | ||||||
|  |     -H "Authorization: Bearer ${USER_TOKEN}" \ | ||||||
|  |     -d '{ "display_name": "Jo Doe" }' | ||||||
|  | echo "" | ||||||
							
								
								
									
										7
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								go.mod
									
									
									
									
									
								
							| @ -1,14 +1,17 @@ | |||||||
| module git.coolaj86.com/coolaj86/goserv | module git.example.com/example/goserv | ||||||
| 
 | 
 | ||||||
| go 1.15 | go 1.15 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  | 	git.rootprojects.org/root/go-gitver v1.1.3 // indirect | ||||||
|  | 	git.rootprojects.org/root/go-gitver/v2 v2.0.1 | ||||||
|  | 	git.rootprojects.org/root/keypairs v0.6.3 | ||||||
| 	github.com/go-chi/chi v4.1.2+incompatible | 	github.com/go-chi/chi v4.1.2+incompatible | ||||||
| 	github.com/jmoiron/sqlx v1.2.0 | 	github.com/jmoiron/sqlx v1.2.0 | ||||||
| 	github.com/joho/godotenv v1.3.0 | 	github.com/joho/godotenv v1.3.0 | ||||||
| 	github.com/lib/pq v1.8.0 | 	github.com/lib/pq v1.8.0 | ||||||
| 	github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect | 	github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect | ||||||
| 	github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 | 	github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 | ||||||
| 	golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 // indirect | 	golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b | ||||||
| 	google.golang.org/appengine v1.6.6 // indirect | 	google.golang.org/appengine v1.6.6 // indirect | ||||||
| ) | ) | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								go.sum
									
									
									
									
									
								
							| @ -1,3 +1,9 @@ | |||||||
|  | git.rootprojects.org/root/go-gitver v1.1.3 h1:/qR9z53vY+IFhWRxLkF9cjaiWh8xRJIm6gyuW+MG81A= | ||||||
|  | git.rootprojects.org/root/go-gitver v1.1.3/go.mod h1:Rj1v3TBhvdaSphFEqMynUYwAz/4f+wY/+syBTvRrmlI= | ||||||
|  | git.rootprojects.org/root/go-gitver/v2 v2.0.1 h1:CdNfvlGDggFbyImxlqA2eFUVRKQKn1EJNk7w/3TQAfk= | ||||||
|  | git.rootprojects.org/root/go-gitver/v2 v2.0.1/go.mod h1:ur82M/jZcvr1WWihyVtNEgDBqIjo22o56wcVHeVJFh8= | ||||||
|  | git.rootprojects.org/root/keypairs v0.6.3 h1:dFuDjlg9KFXhRTIyVy3VfXzlX7hyyeC0J8PTUUZBzpo= | ||||||
|  | git.rootprojects.org/root/keypairs v0.6.3/go.mod h1:WGI8PadOp+4LjUuI+wNlSwcJwFtY8L9XuNjuO3213HA= | ||||||
| github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= | github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= | ||||||
| github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= | github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= | ||||||
| github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= | github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= | ||||||
| @ -20,6 +26,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec | |||||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
|  | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= | ||||||
| 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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
| golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | ||||||
| @ -37,10 +44,11 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= | |||||||
| golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-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-20200925191224-5d1fdd8fa346 h1:hzJjkvxUIF3bSt+v8N5tBQNx/605vszZJ+3XsIamzZo= | golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b h1:07IVqnnzaip3TGyl/cy32V5YP3FguWG4BybYDTBNpm0= | ||||||
| golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= | golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= | ||||||
| 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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= | ||||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= | ||||||
| google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||||||
|  | |||||||
							
								
								
									
										414
									
								
								internal/api/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										414
									
								
								internal/api/api.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,414 @@ | |||||||
|  | package api | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"database/sql" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.rootprojects.org/root/keypairs" | ||||||
|  | 	"git.rootprojects.org/root/keypairs/keyfetch" | ||||||
|  | 
 | ||||||
|  | 	"github.com/go-chi/chi" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // TrustProxy will respect X-Forwarded-* headers | ||||||
|  | var TrustProxy bool | ||||||
|  | 
 | ||||||
|  | // OIDCWhitelist is a list of allowed issuers | ||||||
|  | var OIDCWhitelist string | ||||||
|  | var issuers keyfetch.Whitelist | ||||||
|  | 
 | ||||||
|  | // RandReader is a crypto/rand.Reader by default | ||||||
|  | var RandReader io.Reader = rand.Reader | ||||||
|  | 
 | ||||||
|  | var startedAt = time.Now() | ||||||
|  | var defaultMaxBytes int64 = 1 << 20 | ||||||
|  | var apiIsReady bool | ||||||
|  | 
 | ||||||
|  | // Init will add the API routes to the given router | ||||||
|  | func Init(pub keypairs.PublicKey, r chi.Router) http.Handler { | ||||||
|  | 
 | ||||||
|  | 	// TODO more of this stuff should be options for the API | ||||||
|  | 	{ | ||||||
|  | 		// block-scoped so we don't keep temp vars around | ||||||
|  | 		var err error | ||||||
|  | 		list := strings.Fields(strings.ReplaceAll(strings.TrimSpace(OIDCWhitelist), ",", " ")) | ||||||
|  | 		issuers, err = keyfetch.NewWhitelist(list) | ||||||
|  | 		if nil != err { | ||||||
|  | 			log.Fatal("error parsing oidc whitelist:", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// OIDC Routes | ||||||
|  | 	if nil != pub { | ||||||
|  | 		fmt.Println("Public Key Thumbprint:", pub.Thumbprint()) | ||||||
|  | 		fmt.Println("OIDC enabled at /.well-known/openid-configuration") | ||||||
|  | 		r.Get("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			baseURL := getBaseURL(r) | ||||||
|  | 			w.Header().Set("Content-Type", "application/json") | ||||||
|  | 			w.Write([]byte(fmt.Sprintf( | ||||||
|  | 				`{ "issuer": "%s", "jwks_uri": "%s/.well-known/jwks.json" }`+"\n", | ||||||
|  | 				baseURL, baseURL, | ||||||
|  | 			))) | ||||||
|  | 		}) | ||||||
|  | 		fmt.Println("JWKs enabled at /.well-known/jwks.json") | ||||||
|  | 		r.Get("/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			w.Header().Set("Content-Type", "application/json") | ||||||
|  | 
 | ||||||
|  | 			// non-standard: add expiry for when key should be fetched again | ||||||
|  | 			// TODO expiry should also go in the HTTP caching headers | ||||||
|  | 			exp := time.Now().Add(2 * time.Hour) | ||||||
|  | 			b := pubToOIDC(pub, exp) | ||||||
|  | 
 | ||||||
|  | 			// it's the little things | ||||||
|  | 			w.Write(append(b, '\n')) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.Route("/api", func(r chi.Router) { | ||||||
|  | 		r.Use(limitResponseSize) | ||||||
|  | 		r.Use(jsonAllTheThings) | ||||||
|  | 
 | ||||||
|  | 		/* | ||||||
|  | 			n, err := countAdmins() | ||||||
|  | 			if nil != err { | ||||||
|  | 				log.Fatal("could not connect to database on boot:", err) | ||||||
|  | 			} | ||||||
|  | 			apiIsReady = n > 0 | ||||||
|  | 		*/ | ||||||
|  | 
 | ||||||
|  | 		// Unauthenticated routes | ||||||
|  | 		r.Route("/public", func(r chi.Router) { | ||||||
|  | 			r.Post("/setup", publicSetup) | ||||||
|  | 
 | ||||||
|  | 			r.Get("/ping", ping) | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		// Require admin-level permission | ||||||
|  | 		r.Route("/admin", func(r chi.Router) { | ||||||
|  | 			r.Use(errorUnlessReady()) | ||||||
|  | 			r.Use(mustAdmin()) | ||||||
|  | 
 | ||||||
|  | 			r.Get("/ping", ping) | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		// Any authenticated user | ||||||
|  | 		r.Route("/user", func(r chi.Router) { | ||||||
|  | 			r.Use(errorUnlessReady()) | ||||||
|  | 			r.Use(canImpersonate()) | ||||||
|  | 			r.Get("/ping", ping) | ||||||
|  | 
 | ||||||
|  | 			// TODO get ALL of the user's data | ||||||
|  | 			//r.Get("/", userComplete) | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reset sets the API back to its initial state | ||||||
|  | func Reset() { | ||||||
|  | 	apiIsReady = false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // utils | ||||||
|  | 
 | ||||||
|  | func getBaseURL(r *http.Request) string { | ||||||
|  | 	var scheme string | ||||||
|  | 	if nil != r.TLS || | ||||||
|  | 		(TrustProxy && "https" == r.Header.Get("X-Forwarded-Proto")) { | ||||||
|  | 		scheme = "https:" | ||||||
|  | 	} else { | ||||||
|  | 		scheme = "http:" | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf( | ||||||
|  | 		"%s//%s", | ||||||
|  | 		scheme, | ||||||
|  | 		r.Host, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func mustAuthn(r *http.Request) (*http.Request, error) { | ||||||
|  | 	authzParts := strings.Split(r.Header.Get("Authorization"), " ") | ||||||
|  | 	if 2 != len(authzParts) { | ||||||
|  | 		return nil, fmt.Errorf("Bad Request: missing Auhorization header") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	jwt := authzParts[1] | ||||||
|  | 	// TODO should probably add an error to keypairs | ||||||
|  | 	jws := keypairs.JWTToJWS(jwt) | ||||||
|  | 	if nil == jws { | ||||||
|  | 		return nil, fmt.Errorf("Bad Request: malformed Authorization header") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := jws.DecodeComponents(); nil != err { | ||||||
|  | 		return nil, fmt.Errorf("Bad Request: malformed JWT") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	kid, _ := jws.Header["kid"].(string) | ||||||
|  | 	if "" == kid { | ||||||
|  | 		return nil, fmt.Errorf("Bad Request: missing 'kid' identifier") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	iss, _ := jws.Claims["iss"].(string) | ||||||
|  | 	// TODO beware domain fronting, we should set domain statically | ||||||
|  | 	// See https://pkg.go.dev/git.rootprojects.org/root/keypairs@v0.6.2/keyfetch | ||||||
|  | 	// (Caddy does protect against Domain-Fronting by default: | ||||||
|  | 	//     https://github.com/caddyserver/caddy/issues/2500) | ||||||
|  | 	if "" == iss || !issuers.IsTrustedIssuer(iss, r) { | ||||||
|  | 		return nil, fmt.Errorf("Bad Request: 'iss' is not a trusted issuer") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub, err := keyfetch.OIDCJWK(kid, iss) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, fmt.Errorf("Bad Request: 'kid' could not be matched to a known public key") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	errs := keypairs.VerifyClaims(pub, jws) | ||||||
|  | 	if nil != errs { | ||||||
|  | 		strs := []string{} | ||||||
|  | 		for _, err := range errs { | ||||||
|  | 			strs = append(strs, err.Error()) | ||||||
|  | 		} | ||||||
|  | 		return nil, fmt.Errorf("invalid jwt:\n%s", strings.Join(strs, "\n\t")) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	email, _ := jws.Claims["email"].(string) | ||||||
|  | 	ppid, _ := jws.Claims["sub"].(string) | ||||||
|  | 	if "" == email || "" == ppid { | ||||||
|  | 		return nil, fmt.Errorf("valid signed token, but missing claim for either 'email' or 'sub'") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx := context.WithValue(r.Context(), userPPID, ppid) | ||||||
|  | 	return r.WithContext(ctx), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func pubToOIDC(pub keypairs.PublicKey, exp time.Time) []byte { | ||||||
|  | 	exps := strconv.FormatInt(exp.Unix(), 10) | ||||||
|  | 	jsons := string(keypairs.MarshalJWKPublicKey(pub)) | ||||||
|  | 
 | ||||||
|  | 	// this isn't as fragile as it looks, just adding some OIDC keys and such | ||||||
|  | 
 | ||||||
|  | 	// make prettier | ||||||
|  | 	jsons = strings.Replace(jsons, `{"`, `{ "`, 1) | ||||||
|  | 	jsons = strings.Replace(jsons, `",`, `" ,`, -1) | ||||||
|  | 
 | ||||||
|  | 	// nix trailing } | ||||||
|  | 	jsons = jsons[0 : len(jsons)-1] | ||||||
|  | 	// add on the OIDC stuff (exp is non-standard, but used by pocketid) | ||||||
|  | 	jsons = `{ "keys": [ ` + | ||||||
|  | 		jsons + fmt.Sprintf(`, "ext": true , "key_ops": ["verify"], "exp": %s }`, exps) + | ||||||
|  | 		" ] }" | ||||||
|  | 
 | ||||||
|  | 	return []byte(jsons) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // HTTPResponse gives a basic status message | ||||||
|  | // TODO sanitize all error messages and define error codes | ||||||
|  | type HTTPResponse struct { | ||||||
|  | 	Error   string      `json:"error,omitempty"` | ||||||
|  | 	Code    string      `json:"code,omitempty"` | ||||||
|  | 	Success bool        `json:"success"` | ||||||
|  | 	Data    interface{} `json:"result,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ping(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	ctx := r.Context() | ||||||
|  | 	// as of yet, only known to be a user | ||||||
|  | 	ppid, _ := ctx.Value(userPPID).(string) | ||||||
|  | 
 | ||||||
|  | 	w.Write([]byte(fmt.Sprintf( | ||||||
|  | 		`{ "success": true, "uptime": %.0f, "ppid": %q }`+"\n", | ||||||
|  | 		time.Since(startedAt).Seconds(), ppid, | ||||||
|  | 	))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func noImpl(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	w.Write([]byte( | ||||||
|  | 		`{ "success": false, "error": "not implemented" }` + "\n", | ||||||
|  | 	)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func publicSetup(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	if apiIsReady { | ||||||
|  | 		// default is already 404, methinks | ||||||
|  | 		http.Error(w, "Not Found", http.StatusNotFound) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r, err := mustAuthn(r) | ||||||
|  | 	if nil != err { | ||||||
|  | 		userError(w, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx := r.Context() | ||||||
|  | 	ppid := ctx.Value(userPPID).(string) | ||||||
|  | 	if "" == ppid { | ||||||
|  | 		if nil != err { | ||||||
|  | 			userError(w, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := adminBootstrap(ppid); nil != err { | ||||||
|  | 		serverError("publicSetup.bootstrap", w, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	apiIsReady = true | ||||||
|  | 
 | ||||||
|  | 	w.Write([]byte(fmt.Sprintf( | ||||||
|  | 		`{ "success": true, "sub": %q }`+"\n", | ||||||
|  | 		ppid, | ||||||
|  | 	))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func reply(w http.ResponseWriter, msg interface{}) { | ||||||
|  | 	w.WriteHeader(http.StatusOK) | ||||||
|  | 	b, _ := json.MarshalIndent(&HTTPResponse{ | ||||||
|  | 		Success: true, | ||||||
|  | 		Data:    msg, | ||||||
|  | 	}, "", "  ") | ||||||
|  | 	w.Write(append(b, '\n')) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func userError(w http.ResponseWriter, err error) { | ||||||
|  | 	w.WriteHeader(http.StatusBadRequest) | ||||||
|  | 	b, _ := json.Marshal(&HTTPResponse{ | ||||||
|  | 		Error: err.Error(), | ||||||
|  | 	}) | ||||||
|  | 	w.Write(append(b, '\n')) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func dbError(hint string, w http.ResponseWriter, err error) { | ||||||
|  | 	serverError(hint, w, err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func serverError(hint string, w http.ResponseWriter, err error) { | ||||||
|  | 	// TODO check constraint errors and such, as those are likely user errors | ||||||
|  | 	if sql.ErrNoRows == err { | ||||||
|  | 		userError(w, fmt.Errorf("E_NOT_FOUND: %s", err)) | ||||||
|  | 		return | ||||||
|  | 	} else if strings.Contains(err.Error(), "constraint") { | ||||||
|  | 		userError(w, fmt.Errorf("E_DUPLICATE_NAME: %s", err)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	log.Printf("[%s] error: %v", hint, err) | ||||||
|  | 	w.WriteHeader(http.StatusInternalServerError) | ||||||
|  | 	b, _ := json.Marshal(&HTTPResponse{ | ||||||
|  | 		Error: err.Error(), | ||||||
|  | 	}) | ||||||
|  | 	w.Write(append(b, '\n')) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var errSetID = errors.New("you may not set the player's ID") | ||||||
|  | 
 | ||||||
|  | // Middleware | ||||||
|  | 
 | ||||||
|  | func errorUnlessReady() func(next http.Handler) http.Handler { | ||||||
|  | 	return func(next http.Handler) http.Handler { | ||||||
|  | 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			if !apiIsReady { | ||||||
|  | 				http.Error(w, "Not Found", http.StatusNotFound) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			next.ServeHTTP(w, r) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type adminCtx string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	adminPPID adminCtx = "ppid" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type userCtx string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	userPPID userCtx = "ppid" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func canImpersonate() func(next http.Handler) http.Handler { | ||||||
|  | 	return actAsAdmin(false) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func mustAdmin() func(next http.Handler) http.Handler { | ||||||
|  | 	return actAsAdmin(true) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func countAdmins() (int, error) { | ||||||
|  | 	return 0, errors.New("not implemented") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func adminBootstrap(ppid string) error { | ||||||
|  | 	return errors.New("not implemented") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func actAsAdmin(must bool) func(next http.Handler) http.Handler { | ||||||
|  | 	return func(next http.Handler) http.Handler { | ||||||
|  | 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			var err error | ||||||
|  | 			r, err = mustAuthn(r) | ||||||
|  | 			if nil != err { | ||||||
|  | 				userError(w, err) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ctx := r.Context() | ||||||
|  | 			// as of yet, only known to be a user | ||||||
|  | 			ppid, _ := ctx.Value(userPPID).(string) | ||||||
|  | 
 | ||||||
|  | 			//ok, err := isAdmin(ppid) | ||||||
|  | 			ok, err := false, errors.New("not implemented") | ||||||
|  | 			if nil != err { | ||||||
|  | 				serverError("actAsAdmin", w, err) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			if !ok { | ||||||
|  | 				if must { | ||||||
|  | 					userError(w, errors.New("you're not an admin")) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				next.ServeHTTP(w, r) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// we now know this is an admin, adjust accordingly | ||||||
|  | 			ctx = context.WithValue(r.Context(), adminPPID, ppid) | ||||||
|  | 
 | ||||||
|  | 			// also, an admin can impersonate | ||||||
|  | 			//uemail := r.URL.Query().Get("user_email") | ||||||
|  | 			uppid := r.URL.Query().Get("manager_id") | ||||||
|  | 			if "" == uppid { | ||||||
|  | 				uppid = ppid | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ctx = context.WithValue(ctx, userPPID, uppid) | ||||||
|  | 			next.ServeHTTP(w, r.WithContext(ctx)) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func jsonAllTheThings(next http.Handler) http.Handler { | ||||||
|  | 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		// just setting a default, other handlers can change this | ||||||
|  | 		w.Header().Set("Content-Type", "application/json") | ||||||
|  | 		next.ServeHTTP(w, r) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func limitResponseSize(next http.Handler) http.Handler { | ||||||
|  | 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBytes) | ||||||
|  | 		next.ServeHTTP(w, r) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										164
									
								
								internal/api/api_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								internal/api/api_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,164 @@ | |||||||
|  | package api | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	mathrand "math/rand" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/httptest" | ||||||
|  | 	"net/url" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"git.example.com/example/goserv/internal/db" | ||||||
|  | 	"git.rootprojects.org/root/keypairs" | ||||||
|  | 	"git.rootprojects.org/root/keypairs/keyfetch" | ||||||
|  | 
 | ||||||
|  | 	"github.com/go-chi/chi" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var srv *httptest.Server | ||||||
|  | 
 | ||||||
|  | var testKey keypairs.PrivateKey | ||||||
|  | var testPub keypairs.PublicKey | ||||||
|  | var testWhitelist keyfetch.Whitelist | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	// In tests it's nice to get the same "random" values, every time | ||||||
|  | 	RandReader = testReader{} | ||||||
|  | 	mathrand.Seed(0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestMain(m *testing.M) { | ||||||
|  | 	connStr := needsTestDB(m) | ||||||
|  | 	if strings.Contains(connStr, "@localhost/") || strings.Contains(connStr, "@localhost:") { | ||||||
|  | 		connStr += "?sslmode=disable" | ||||||
|  | 	} else { | ||||||
|  | 		connStr += "?sslmode=required" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := db.Init(connStr); nil != err { | ||||||
|  | 		log.Fatal("db connection error", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := db.DropAllTables(db.PleaseDoubleCheckTheDatabaseURLDontDropProd(connStr)); nil != err { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if err := db.Init(connStr); nil != err { | ||||||
|  | 		log.Fatal("db connection error", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	testKey = keypairs.NewDefaultPrivateKey() | ||||||
|  | 	testPub = keypairs.NewPublicKey(testKey.Public()) | ||||||
|  | 	r := chi.NewRouter() | ||||||
|  | 	srv = httptest.NewServer(Init(testPub, r)) | ||||||
|  | 	testWhitelist, err = keyfetch.NewWhitelist(nil, []string{srv.URL}) | ||||||
|  | 	if nil != err { | ||||||
|  | 		log.Fatal("bad whitelist", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	os.Exit(m.Run()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // public APIs | ||||||
|  | 
 | ||||||
|  | func Test_Public_Ping(t *testing.T) { | ||||||
|  | 	if err := testPing("public"); nil != err { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // test types | ||||||
|  | 
 | ||||||
|  | type testReader struct{} | ||||||
|  | 
 | ||||||
|  | func (testReader) Read(p []byte) (n int, err error) { | ||||||
|  | 	return mathrand.Read(p) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testPing(which string) error { | ||||||
|  | 	urlstr := fmt.Sprintf("/api/%s/ping", which) | ||||||
|  | 	res, err := testReq("GET", urlstr, "", nil, 200) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	data := map[string]interface{}{} | ||||||
|  | 	if err := json.NewDecoder(res.Body).Decode(&data); nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if success, ok := data["success"].(bool); !ok || !success { | ||||||
|  | 		log.Printf("Bad Response\n\tURL:%s\n\tBody:\n%#v", urlstr, data) | ||||||
|  | 		return errors.New("bad response: missing success") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ppid, _ := data["ppid"].(string); "" != ppid { | ||||||
|  | 		return fmt.Errorf("the effective user ID isn't what it should be: %q != %q", ppid, "") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testReq(method, pathname string, jwt string, payload []byte, expectedStatus int) (*http.Response, error) { | ||||||
|  | 	client := srv.Client() | ||||||
|  | 	urlstr, _ := url.Parse(srv.URL + pathname) | ||||||
|  | 
 | ||||||
|  | 	if "" == method { | ||||||
|  | 		method = "GET" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req := &http.Request{ | ||||||
|  | 		Method: method, | ||||||
|  | 		URL:    urlstr, | ||||||
|  | 		Body:   ioutil.NopCloser(bytes.NewReader(payload)), | ||||||
|  | 		Header: http.Header{}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(jwt) > 0 { | ||||||
|  | 		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt)) | ||||||
|  | 	} | ||||||
|  | 	res, err := client.Do(req) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if expectedStatus > 0 { | ||||||
|  | 		if expectedStatus != res.StatusCode { | ||||||
|  | 			data, _ := ioutil.ReadAll(res.Body) | ||||||
|  | 			log.Printf("Bad Response: %d\n\tURL:%s\n\tBody:\n%s", res.StatusCode, urlstr, string(data)) | ||||||
|  | 			return nil, fmt.Errorf("bad status code: %d", res.StatusCode) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return res, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func needsTestDB(m *testing.M) string { | ||||||
|  | 	connStr := os.Getenv("TEST_DATABASE_URL") | ||||||
|  | 	if "" == connStr { | ||||||
|  | 		log.Fatal(`no connection string defined | ||||||
|  | 
 | ||||||
|  | You must set TEST_DATABASE_URL to run db tests. | ||||||
|  | 
 | ||||||
|  | You may find this helpful: | ||||||
|  | 
 | ||||||
|  |     psql 'postgres://postgres:postgres@localhost:5432/postgres' | ||||||
|  | 
 | ||||||
|  | 	DROP DATABASE IF EXISTS postgres_test; | ||||||
|  | 	CREATE DATABASE postgres_test; | ||||||
|  | 	\q | ||||||
|  | 
 | ||||||
|  | Then your test database URL will be | ||||||
|  | 
 | ||||||
|  |     export TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres_test | ||||||
|  | `) | ||||||
|  | 	} | ||||||
|  | 	return connStr | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								internal/api/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								internal/api/db.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | package api | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.example.com/example/goserv/internal/db" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func newID() string { | ||||||
|  | 	// Postgres returns IDs on inserts but, | ||||||
|  | 	// for portability and ease of association, | ||||||
|  | 	// we'll create our own. | ||||||
|  | 	b := make([]byte, 16) | ||||||
|  | 	_, _ = RandReader.Read(b) | ||||||
|  | 	id := base64.RawURLEncoding.EncodeToString(b) | ||||||
|  | 	return id | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NotDeleted supplements a WHERE clause | ||||||
|  | const NotDeleted = ` | ||||||
|  |  ( "deleted_at" IS NULL OR "deleted_at" = '0001-01-01 00:00:00+00' OR "deleted_at" = '1970-01-01 00:00:00+00' )  | ||||||
|  | ` | ||||||
|  | 
 | ||||||
|  | func logEvent(action, table, recordID, by string, at time.Time) (string, error) { | ||||||
|  | 	id := newID() | ||||||
|  | 
 | ||||||
|  | 	if _, err := db.DB.Exec(` | ||||||
|  | 		INSERT INTO "events" ("id", "action", "table", "record", "by", "at") | ||||||
|  | 		VALUES ($1, $2, $3, $4, $5, $6)`, | ||||||
|  | 		id, action, table, recordID, by, at, | ||||||
|  | 	); nil != err { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return id, nil | ||||||
|  | } | ||||||
| @ -3,10 +3,13 @@ package db | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"database/sql" | 	"database/sql" | ||||||
|  | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | 	"regexp" | ||||||
|  | 	"sort" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"git.coolaj86.com/coolaj86/goserv/assets/configfs" | 	"git.example.com/example/goserv/assets/configfs" | ||||||
| 	"github.com/jmoiron/sqlx" | 	"github.com/jmoiron/sqlx" | ||||||
| 
 | 
 | ||||||
| 	// pq injects itself into sql as 'postgres' | 	// pq injects itself into sql as 'postgres' | ||||||
| @ -15,21 +18,14 @@ import ( | |||||||
| 
 | 
 | ||||||
| // DB is a concurrency-safe db connection instance | // DB is a concurrency-safe db connection instance | ||||||
| var DB *sqlx.DB | var DB *sqlx.DB | ||||||
|  | var firstDBURL PleaseDoubleCheckTheDatabaseURLDontDropProd | ||||||
| 
 | 
 | ||||||
| // Init returns a, you guessed it, New Store | // Init initializes the database | ||||||
| func Init(pgURL string) error { | func Init(pgURL string) error { | ||||||
| 	// https://godoc.org/github.com/lib/pq | 	// https://godoc.org/github.com/lib/pq | ||||||
| 
 | 
 | ||||||
| 	f, err := configfs.Assets.Open("./postgres/init.sql") | 	firstDBURL = PleaseDoubleCheckTheDatabaseURLDontDropProd(pgURL) | ||||||
| 	if nil != err { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	dbtype := "postgres" | 	dbtype := "postgres" | ||||||
| 	sqlBytes, err := ioutil.ReadAll(f) |  | ||||||
| 	if nil != err { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	ctx, done := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) | 	ctx, done := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) | ||||||
| 	defer done() | 	defer done() | ||||||
| @ -37,6 +33,29 @@ func Init(pgURL string) error { | |||||||
| 	if err := db.PingContext(ctx); nil != err { | 	if err := db.PingContext(ctx); nil != err { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	// basic stuff | ||||||
|  | 	f, err := configfs.Assets.Open("./postgres/init.sql") | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	sqlBytes, err := ioutil.ReadAll(f) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if _, err := db.ExecContext(ctx, string(sqlBytes)); nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// project-specific stuff | ||||||
|  | 	f, err = configfs.Assets.Open("./postgres/tables.sql") | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	sqlBytes, err = ioutil.ReadAll(f) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	if _, err := db.ExecContext(ctx, string(sqlBytes)); nil != err { | 	if _, err := db.ExecContext(ctx, string(sqlBytes)); nil != err { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @ -45,3 +64,58 @@ func Init(pgURL string) error { | |||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // PleaseDoubleCheckTheDatabaseURLDontDropProd is just a friendly, | ||||||
|  | // hopefully helpful reminder, not to only use this in test files, | ||||||
|  | // and to not drop the production database | ||||||
|  | type PleaseDoubleCheckTheDatabaseURLDontDropProd string | ||||||
|  | 
 | ||||||
|  | // DropAllTables runs drop.sql, which is intended only for tests | ||||||
|  | func DropAllTables(dbURL PleaseDoubleCheckTheDatabaseURLDontDropProd) error { | ||||||
|  | 	if err := CanDropAllTables(string(dbURL)); nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// drop stuff | ||||||
|  | 	f, err := configfs.Assets.Open("./postgres/drop.sql") | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	sqlBytes, err := ioutil.ReadAll(f) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	ctx, done := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) | ||||||
|  | 	defer done() | ||||||
|  | 	if _, err := DB.ExecContext(ctx, string(sqlBytes)); nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CanDropAllTables returns an error if the dbURL does not contain the words "test" or | ||||||
|  | // "demo" at a letter boundary | ||||||
|  | func CanDropAllTables(dbURL string) error { | ||||||
|  | 	var isDemo bool | ||||||
|  | 	nonalpha := regexp.MustCompile(`[^a-zA-Z]`) | ||||||
|  | 	haystack := nonalpha.Split(dbURL, -1) | ||||||
|  | 	sort.Strings(haystack) | ||||||
|  | 	for _, needle := range []string{"test", "demo"} { | ||||||
|  | 		// the index to insert x if x is not present (it could be len(a)) | ||||||
|  | 		// (meaning that it is the index at which it exists, if it exists) | ||||||
|  | 		i := sort.SearchStrings(haystack, needle) | ||||||
|  | 		if i < len(haystack) && haystack[i] == needle { | ||||||
|  | 			isDemo = true | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if isDemo { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return fmt.Errorf( | ||||||
|  | 		"test and demo database URLs must contain the word 'test' or 'demo' "+ | ||||||
|  | 			"separated by a non-alphabet character, such as /test2/db_demo1\n%q\n", | ||||||
|  | 		dbURL, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										66
									
								
								internal/db/db_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								internal/db/db_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | package db | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestMain(m *testing.M) { | ||||||
|  | 	if err := testConnectAndInit(); nil != err { | ||||||
|  | 		fmt.Fprintf(os.Stderr, err.Error()) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	os.Exit(m.Run()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func needsTestDB() (string, error) { | ||||||
|  | 	connStr := os.Getenv("TEST_DATABASE_URL") | ||||||
|  | 	if "" == connStr { | ||||||
|  | 		return "", errors.New(`no connection string defined | ||||||
|  | 
 | ||||||
|  | You must set TEST_DATABASE_URL to run db tests. | ||||||
|  | 
 | ||||||
|  | You may find this helpful: | ||||||
|  | 
 | ||||||
|  |     psql 'postgres://postgres:postgres@localhost:5432/postgres' | ||||||
|  | 
 | ||||||
|  | 	DROP DATABASE IF EXISTS postgres_test; | ||||||
|  | 	CREATE DATABASE postgres_test; | ||||||
|  | 	\q | ||||||
|  | 
 | ||||||
|  | Then your test database URL will be | ||||||
|  | 
 | ||||||
|  |     export TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres_test`) | ||||||
|  | 	} | ||||||
|  | 	return connStr, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testConnectAndInit() error { | ||||||
|  | 	connStr, err := needsTestDB() | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if strings.Contains(connStr, "@localhost/") || strings.Contains(connStr, "@localhost:") { | ||||||
|  | 		connStr += "?sslmode=disable" | ||||||
|  | 	} else { | ||||||
|  | 		connStr += "?sslmode=required" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := Init(connStr); nil != err { | ||||||
|  | 		return fmt.Errorf("db connection error: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestDropAll(t *testing.T) { | ||||||
|  | 	connStr := os.Getenv("TEST_DATABASE_URL") | ||||||
|  | 
 | ||||||
|  | 	if err := DropAllTables(PleaseDoubleCheckTheDatabaseURLDontDropProd(connStr)); nil != err { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										237
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										237
									
								
								main.go
									
									
									
									
									
								
							| @ -1,20 +1,27 @@ | |||||||
|  | //go:generate go run git.rootprojects.org/root/go-gitver/v2 | ||||||
|  | 
 | ||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"compress/flate" | 	"compress/flate" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"log" | 	"log" | ||||||
|  | 	"math/rand" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"git.coolaj86.com/coolaj86/goserv/assets" | 	"git.example.com/example/goserv/assets" | ||||||
| 	"git.coolaj86.com/coolaj86/goserv/internal/db" | 	"git.example.com/example/goserv/internal/api" | ||||||
|  | 	"git.example.com/example/goserv/internal/db" | ||||||
|  | 	"git.rootprojects.org/root/keypairs" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-chi/chi" | 	"github.com/go-chi/chi" | ||||||
| 	"github.com/go-chi/chi/middleware" | 	"github.com/go-chi/chi/middleware" | ||||||
|  | 
 | ||||||
| 	_ "github.com/joho/godotenv/autoload" | 	_ "github.com/joho/godotenv/autoload" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -45,6 +52,9 @@ type runOptions struct { | |||||||
| 	trustProxy bool | 	trustProxy bool | ||||||
| 	compress   bool | 	compress   bool | ||||||
| 	static     string | 	static     string | ||||||
|  | 	pub        string | ||||||
|  | 	oidcWL     string | ||||||
|  | 	demo       bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var runFlags *flag.FlagSet | var runFlags *flag.FlagSet | ||||||
| @ -53,17 +63,32 @@ var initFlags *flag.FlagSet | |||||||
| var dbURL string | var dbURL string | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
|  | 	// chosen by fair dice roll. | ||||||
|  | 	// guaranteed to be random. | ||||||
|  | 	rand.Seed(4) | ||||||
|  | 
 | ||||||
|  | 	// j/k | ||||||
|  | 	rand.Seed(time.Now().UnixNano()) | ||||||
|  | 	initFlags = flag.NewFlagSet("init", flag.ExitOnError) | ||||||
|  | 	var conftodo bool | ||||||
|  | 	var confdomain string | ||||||
|  | 	initFlags.BoolVar(&conftodo, "todo", false, "TODO init should copy out nice templated config files") | ||||||
|  | 	initFlags.StringVar(&confdomain, "base-url", "https://example.com", "TODO the domain to use for templated scripts") | ||||||
|  | 
 | ||||||
| 	runOpts = runOptions{} | 	runOpts = runOptions{} | ||||||
| 	runFlags = flag.NewFlagSet("run", flag.ExitOnError) | 	runFlags = flag.NewFlagSet("run", flag.ExitOnError) | ||||||
| 	runFlags.StringVar( | 	runFlags.StringVar( | ||||||
| 		&runOpts.listen, "listen", "", | 		&runOpts.listen, "listen", "", | ||||||
| 		"the address and port on which to listen (default \""+defaultAddr+"\")") | 		"the address and port on which to listen (default \""+defaultAddr+"\")") | ||||||
| 	runFlags.BoolVar(&runOpts.trustProxy, "trust-proxy", false, "trust X-Forwarded-For header") | 	runFlags.BoolVar(&runOpts.trustProxy, "trust-proxy", false, "trust X-Forwarded-* headers") | ||||||
| 	runFlags.BoolVar(&runOpts.compress, "compress", true, "enable compression for text,html,js,css,etc") | 	runFlags.BoolVar(&runOpts.compress, "compress", true, "enable compression for text,html,js,css,etc") | ||||||
| 	runFlags.StringVar(&runOpts.static, "serve-path", "", "path to serve, falls back to built-in web app") | 	runFlags.StringVar(&runOpts.static, "serve-path", "", "path to serve, falls back to built-in web app") | ||||||
| 	runFlags.StringVar( | 	runFlags.StringVar(&runOpts.pub, "public-key", "", "path to public key, or key string - RSA or ECDSA, JWK (JSON) or PEM") | ||||||
| 		&dbURL, "db-url", "postgres://postgres:postgres@localhost:5432/postgres", | 	runFlags.StringVar(&runOpts.oidcWL, "oidc-whitelist", "", "list of trusted OIDC issuer URLs (ex: Auth0, Google, PocketID) for SSO") | ||||||
| 		"database (postgres) connection url") | 	runFlags.BoolVar(&runOpts.demo, "demo", false, "demo mode enables unauthenticated 'DELETE /api/public/reset' to reset") | ||||||
|  | 	runFlags.StringVar(&dbURL, "database-url", "", | ||||||
|  | 		"database (postgres) connection url (default postgres://postgres:postgres@localhost:5432/postgres)", | ||||||
|  | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
| @ -106,6 +131,19 @@ func main() { | |||||||
| 				runOpts.listen = defaultAddr | 				runOpts.listen = defaultAddr | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		if "" == dbURL { | ||||||
|  | 			dbURL = os.Getenv("DATABASE_URL") | ||||||
|  | 		} | ||||||
|  | 		if "" == dbURL { | ||||||
|  | 			dbURL = "postgres://postgres:postgres@localhost:5432/postgres" | ||||||
|  | 		} | ||||||
|  | 		api.OIDCWhitelist = runOpts.oidcWL | ||||||
|  | 		if "" == api.OIDCWhitelist { | ||||||
|  | 			api.OIDCWhitelist = os.Getenv("OIDC_WHITELIST") | ||||||
|  | 		} | ||||||
|  | 		if "" == runOpts.pub { | ||||||
|  | 			runOpts.pub = os.Getenv("PUBLIC_KEY") | ||||||
|  | 		} | ||||||
| 		serve() | 		serve() | ||||||
| 	default: | 	default: | ||||||
| 		usage() | 		usage() | ||||||
| @ -114,100 +152,6 @@ func main() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var startedAt = time.Now() |  | ||||||
| var defaultMaxBytes int64 = 1 << 20 |  | ||||||
| 
 |  | ||||||
| func serve() { |  | ||||||
| 	initDB(dbURL) |  | ||||||
| 
 |  | ||||||
| 	r := chi.NewRouter() |  | ||||||
| 
 |  | ||||||
| 	// A good base middleware stack |  | ||||||
| 	if runOpts.trustProxy { |  | ||||||
| 		r.Use(middleware.RealIP) |  | ||||||
| 	} |  | ||||||
| 	if runOpts.compress { |  | ||||||
| 		r.Use(middleware.Compress(flate.DefaultCompression)) |  | ||||||
| 	} |  | ||||||
| 	r.Use(middleware.Logger) |  | ||||||
| 	r.Use(middleware.Recoverer) |  | ||||||
| 
 |  | ||||||
| 	r.Route("/api", func(r chi.Router) { |  | ||||||
| 		r.Use(limitResponseSize) |  | ||||||
| 		r.Use(jsonAllTheThings) |  | ||||||
| 
 |  | ||||||
| 		r.Route("/public", func(r chi.Router) { |  | ||||||
| 			r.Get("/status", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 				w.Write([]byte(fmt.Sprintf( |  | ||||||
| 					`{ "success": true, "uptime": %.0f }%s`, |  | ||||||
| 					time.Since(startedAt).Seconds(), |  | ||||||
| 					"\n", |  | ||||||
| 				))) |  | ||||||
| 			}) |  | ||||||
| 		}) |  | ||||||
| 
 |  | ||||||
| 		r.Route("/user", func(r chi.Router) { |  | ||||||
| 			r.Get("/inspect", func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 				w.Write([]byte(fmt.Sprintf( |  | ||||||
| 					`{ "success": false, "error": "not implemented" }%s`, "\n", |  | ||||||
| 				))) |  | ||||||
| 			}) |  | ||||||
| 		}) |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	var staticHandler http.HandlerFunc |  | ||||||
| 	pub := http.FileServer(assets.Assets) |  | ||||||
| 
 |  | ||||||
| 	if len(runOpts.static) > 0 { |  | ||||||
| 		// try the user-provided directory first, then fallback to the built-in |  | ||||||
| 		devFS := http.Dir(runOpts.static) |  | ||||||
| 		dev := http.FileServer(devFS) |  | ||||||
| 		staticHandler = func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			if _, err := devFS.Open(r.URL.Path); nil != err { |  | ||||||
| 				pub.ServeHTTP(w, r) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			dev.ServeHTTP(w, r) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		staticHandler = func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 			pub.ServeHTTP(w, r) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	r.Get("/*", staticHandler) |  | ||||||
| 
 |  | ||||||
| 	fmt.Println("Listening for http (with reasonable timeouts) on", runOpts.listen) |  | ||||||
| 	srv := &http.Server{ |  | ||||||
| 		Addr:              runOpts.listen, |  | ||||||
| 		Handler:           r, |  | ||||||
| 		ReadHeaderTimeout: 2 * time.Second, |  | ||||||
| 		ReadTimeout:       10 * time.Second, |  | ||||||
| 		WriteTimeout:      20 * time.Second, |  | ||||||
| 		MaxHeaderBytes:    1024 * 1024, // 1MiB |  | ||||||
| 	} |  | ||||||
| 	if err := srv.ListenAndServe(); nil != err { |  | ||||||
| 		fmt.Fprintf(os.Stderr, "%s", err) |  | ||||||
| 		os.Exit(1) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func jsonAllTheThings(next http.Handler) http.Handler { |  | ||||||
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		// just setting a default, other handlers can change this |  | ||||||
| 		w.Header().Set("Content-Type", "application/json") |  | ||||||
| 		next.ServeHTTP(w, r) |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func limitResponseSize(next http.Handler) http.Handler { |  | ||||||
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBytes) |  | ||||||
| 		next.ServeHTTP(w, r) |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func initDB(connStr string) { | func initDB(connStr string) { | ||||||
| 	// TODO url.Parse | 	// TODO url.Parse | ||||||
| 	if strings.Contains(connStr, "@localhost/") || strings.Contains(connStr, "@localhost:") { | 	if strings.Contains(connStr, "@localhost/") || strings.Contains(connStr, "@localhost:") { | ||||||
| @ -225,3 +169,96 @@ func initDB(connStr string) { | |||||||
| 
 | 
 | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func serve() { | ||||||
|  | 	initDB(dbURL) | ||||||
|  | 
 | ||||||
|  | 	r := chi.NewRouter() | ||||||
|  | 
 | ||||||
|  | 	// A good base middleware stack | ||||||
|  | 	if runOpts.trustProxy { | ||||||
|  | 		api.TrustProxy = true | ||||||
|  | 		r.Use(middleware.RealIP) | ||||||
|  | 	} | ||||||
|  | 	if runOpts.compress { | ||||||
|  | 		r.Use(middleware.Compress(flate.DefaultCompression)) | ||||||
|  | 	} | ||||||
|  | 	r.Use(middleware.Logger) | ||||||
|  | 	r.Use(middleware.Recoverer) | ||||||
|  | 
 | ||||||
|  | 	var pub keypairs.PublicKey = nil | ||||||
|  | 	if "" != runOpts.pub { | ||||||
|  | 		var err error | ||||||
|  | 		pub, err = keypairs.ParsePublicKey([]byte(runOpts.pub)) | ||||||
|  | 		if nil != err { | ||||||
|  | 			b, err := ioutil.ReadFile(runOpts.pub) | ||||||
|  | 			if nil != err { | ||||||
|  | 				// ignore | ||||||
|  | 			} else { | ||||||
|  | 				pub, err = keypairs.ParsePublicKey(b) | ||||||
|  | 				if nil != err { | ||||||
|  | 					log.Fatal("could not parse public key:", err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	_ = api.Init(pub, r) | ||||||
|  | 
 | ||||||
|  | 	var staticHandler http.HandlerFunc | ||||||
|  | 	pubdir := http.FileServer(assets.Assets) | ||||||
|  | 
 | ||||||
|  | 	if len(runOpts.static) > 0 { | ||||||
|  | 		// try the user-provided directory first, then fallback to the built-in | ||||||
|  | 		devFS := http.Dir(runOpts.static) | ||||||
|  | 		dev := http.FileServer(devFS) | ||||||
|  | 		staticHandler = func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			if _, err := devFS.Open(r.URL.Path); nil != err { | ||||||
|  | 				pubdir.ServeHTTP(w, r) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			dev.ServeHTTP(w, r) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		staticHandler = func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			pubdir.ServeHTTP(w, r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if runOpts.demo { | ||||||
|  | 		if err := db.CanDropAllTables(dbURL); nil != err { | ||||||
|  | 			fmt.Fprintf(os.Stderr, err.Error()) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fmt.Println("DANGER: running in demo mode with DELETE /api/public/reset enabled") | ||||||
|  | 		fmt.Fprintf(os.Stderr, "DANGER: running in demo mode with DELETE /api/public/reset enabled\n") | ||||||
|  | 		r.Delete("/api/public/reset", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			if err := db.DropAllTables(db.PleaseDoubleCheckTheDatabaseURLDontDropProd(dbURL)); nil != err { | ||||||
|  | 				w.Write([]byte("error dropping tabels: " + err.Error())) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			api.Reset() | ||||||
|  | 			initDB(dbURL) | ||||||
|  | 			w.Write([]byte("re-initialized\n")) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r.Get("/*", staticHandler) | ||||||
|  | 
 | ||||||
|  | 	fmt.Println("") | ||||||
|  | 	fmt.Println("Listening for http (with reasonable timeouts) on", runOpts.listen) | ||||||
|  | 	srv := &http.Server{ | ||||||
|  | 		Addr:              runOpts.listen, | ||||||
|  | 		Handler:           r, | ||||||
|  | 		ReadHeaderTimeout: 2 * time.Second, | ||||||
|  | 		ReadTimeout:       10 * time.Second, | ||||||
|  | 		WriteTimeout:      20 * time.Second, | ||||||
|  | 		MaxHeaderBytes:    1024 * 1024, // 1MiB | ||||||
|  | 	} | ||||||
|  | 	if err := srv.ListenAndServe(); nil != err { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "%s\n", err) | ||||||
|  | 		time.Sleep(100 * time.Millisecond) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | |||||||
| @ -36,10 +36,7 @@ | |||||||
|             name="email" |             name="email" | ||||||
|             placeholder="email" |             placeholder="email" | ||||||
|           /> |           /> | ||||||
|                     <button |           <button class="btn btn-secondary my-2 my-sm-0" type="submit"> | ||||||
|                         class="btn btn-secondary my-2 my-sm-0" |  | ||||||
|                         type="submit" |  | ||||||
|                     > |  | ||||||
|             Sign in |             Sign in | ||||||
|           </button> |           </button> | ||||||
|         </form> |         </form> | ||||||
| @ -56,19 +53,16 @@ | |||||||
|               ><h3 class="card-header"> |               ><h3 class="card-header"> | ||||||
|                 Server Health |                 Server Health | ||||||
|                 <form class="js-healthcheck"> |                 <form class="js-healthcheck"> | ||||||
|                                     <button |                   <button type="submit" class="float-right btn btn-primary"> | ||||||
|                                         type="submit" |  | ||||||
|                                         class="float-right btn btn-primary" |  | ||||||
|                                     > |  | ||||||
|                     Check |                     Check | ||||||
|                   </button> |                   </button> | ||||||
|                 </form> |                 </form> | ||||||
|               </h3></a |               </h3></a | ||||||
|             > |             > | ||||||
|                         <div class="card-body"> |             <div class="js-pre card-body"> | ||||||
|               <h5 class="card-title">Check Server Status</h5> |               <h5 class="card-title">Check Server Status</h5> | ||||||
|               <div class="card-text"> |               <div class="card-text"> | ||||||
|                                 <pre><code>curl https://example.com/api/public/status</code></pre> |                 <pre><code>curl https://example.com/api/public/ping</code></pre> | ||||||
|                 <pre><code class="js-server-health">-</code></pre> |                 <pre><code class="js-server-health">-</code></pre> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -41,9 +41,71 @@ | |||||||
|     ev.preventDefault(); |     ev.preventDefault(); | ||||||
|     ev.stopPropagation(); |     ev.stopPropagation(); | ||||||
| 
 | 
 | ||||||
|         window.fetch("/api/public/status").then(async function (resp) { |     window.fetch("/api/public/ping").then(async function (resp) { | ||||||
|       var res = await resp.json(); |       var res = await resp.json(); | ||||||
|       $(".js-server-health").innerText = JSON.stringify(res, null, 2); |       $(".js-server-health").innerText = JSON.stringify(res, null, 2); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  |   ` | ||||||
|  |     # Demo Mode Only | ||||||
|  |     DELETE /public/reset                                    Drop database and re-initialize | ||||||
|  | 
 | ||||||
|  |     # Public | ||||||
|  |     GET  /public/ping                                       Health Check | ||||||
|  |     POST /public/setup                  <= (none)           Bootstrap | ||||||
|  | 
 | ||||||
|  |     # Admin-only | ||||||
|  |     GET  /admin/ping                                        (authenticated) Health Check | ||||||
|  |     GET  /admin/users                      []Users =>       List ALL Users | ||||||
|  | 
 | ||||||
|  |     # User | ||||||
|  |     GET  /user/ping                                         (authenticated) Health Check | ||||||
|  |     GET  /user                          => User             User profile | ||||||
|  |     ` | ||||||
|  |     .trim() | ||||||
|  |     .split(/\n/) | ||||||
|  |     .forEach(function (line) { | ||||||
|  |       line = line.trim(); | ||||||
|  |       if ("#" === line[0] || !line.trim()) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       line = line.replace(/(<=)?\s*\(none\)\s*(=>)?/g, ""); | ||||||
|  |       var parts = line.split(/\s+/g); | ||||||
|  |       var method = parts[0]; | ||||||
|  |       if ("GET" != method) { | ||||||
|  |         method = "-X " + method + " "; | ||||||
|  |       } else { | ||||||
|  |         method = ""; | ||||||
|  |       } | ||||||
|  |       var pathname = parts[1]; | ||||||
|  |       var auth = pathname.match(/(public|user|admin)/)[1]; | ||||||
|  |       if ("admin" == auth) { | ||||||
|  |         auth = " \\\n    -H 'Authorization: Bearer ADMIN_TOKEN'"; | ||||||
|  |       } else if ("user" == auth) { | ||||||
|  |         auth = " \\\n    -H 'Authorization: Bearer USER_TOKEN'"; | ||||||
|  |       } else { | ||||||
|  |         auth = ""; | ||||||
|  |       } | ||||||
|  |       document.body.querySelector(".js-pre").innerHTML += ( | ||||||
|  |         ` | ||||||
|  |                 <div class="card-text"> | ||||||
|  |                     <pre><code>curl -X POST https://example.com/api</code></pre>
 | ||||||
|  |                     <pre><code class="js-` +
 | ||||||
|  |         pathname.replace(/\//g, "-") + | ||||||
|  |         `">-</code></pre>
 | ||||||
|  |                 </div> | ||||||
|  |             ` | ||||||
|  |       ) | ||||||
|  |         .replace( | ||||||
|  |           /https:\/\/example\.com/g, | ||||||
|  |           location.protocol + "//" + location.host | ||||||
|  |         ) | ||||||
|  |         .replace(/-X POST /g, method) | ||||||
|  |         .replace(/\/api/g, "/api" + pathname + auth); | ||||||
|  |     }); | ||||||
|  |   /* | ||||||
|  |     document.body.querySelector(".js-pre").innerHTML = document.body | ||||||
|  |         .querySelector(".js-pre") | ||||||
|  |         .innerHTML | ||||||
|  |         */ | ||||||
| })(); | })(); | ||||||
|  | |||||||
| @ -7,4 +7,7 @@ import ( | |||||||
| 	// these are 'go generate' tooling dependencies, not including in the binary | 	// these are 'go generate' tooling dependencies, not including in the binary | ||||||
| 	_ "github.com/shurcooL/vfsgen" | 	_ "github.com/shurcooL/vfsgen" | ||||||
| 	_ "github.com/shurcooL/vfsgen/cmd/vfsgendev" | 	_ "github.com/shurcooL/vfsgen/cmd/vfsgendev" | ||||||
|  | 	_ "golang.org/x/tools/cmd/stringer" | ||||||
|  | 	_ "git.rootprojects.org/root/keypairs/cmd/keypairs" | ||||||
|  | 	_ "git.rootprojects.org/root/go-gitver/v2" | ||||||
| ) | ) | ||||||
|  | |||||||
							
								
								
									
										312
									
								
								vendor/git.rootprojects.org/root/go-gitver/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								vendor/git.rootprojects.org/root/go-gitver/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,312 @@ | |||||||
|  | Mozilla Public License Version 2.0 | ||||||
|  | 
 | ||||||
|  |    1. Definitions | ||||||
|  | 
 | ||||||
|  | 1.1. "Contributor" means each individual or legal entity that creates, contributes | ||||||
|  | to the creation of, or owns Covered Software. | ||||||
|  | 
 | ||||||
|  | 1.2. "Contributor Version" means the combination of the Contributions of others | ||||||
|  | (if any) used by a Contributor and that particular Contributor's Contribution. | ||||||
|  | 
 | ||||||
|  |       1.3. "Contribution" means Covered Software of a particular Contributor. | ||||||
|  | 
 | ||||||
|  | 1.4. "Covered Software" means Source Code Form to which the initial Contributor | ||||||
|  | has attached the notice in Exhibit A, the Executable Form of such Source Code | ||||||
|  | Form, and Modifications of such Source Code Form, in each case including portions | ||||||
|  | thereof. | ||||||
|  | 
 | ||||||
|  |       1.5. "Incompatible With Secondary Licenses" means | ||||||
|  | 
 | ||||||
|  | (a) that the initial Contributor has attached the notice described in Exhibit | ||||||
|  | B to the Covered Software; or | ||||||
|  | 
 | ||||||
|  | (b) that the Covered Software was made available under the terms of version | ||||||
|  | 1.1 or earlier of the License, but not also under the terms of a Secondary | ||||||
|  | License. | ||||||
|  | 
 | ||||||
|  | 1.6. "Executable Form" means any form of the work other than Source Code Form. | ||||||
|  | 
 | ||||||
|  | 1.7. "Larger Work" means a work that combines Covered Software with other | ||||||
|  | material, in a separate file or files, that is not Covered Software. | ||||||
|  | 
 | ||||||
|  |       1.8. "License" means this document. | ||||||
|  | 
 | ||||||
|  | 1.9. "Licensable" means having the right to grant, to the maximum extent possible, | ||||||
|  | whether at the time of the initial grant or subsequently, any and all of the | ||||||
|  | rights conveyed by this License. | ||||||
|  | 
 | ||||||
|  |       1.10. "Modifications" means any of the following: | ||||||
|  | 
 | ||||||
|  | (a) any file in Source Code Form that results from an addition to, deletion | ||||||
|  | from, or modification of the contents of Covered Software; or | ||||||
|  | 
 | ||||||
|  | (b) any new file in Source Code Form that contains any Covered Software. | ||||||
|  | 
 | ||||||
|  | 1.11. "Patent Claims" of a Contributor means any patent claim(s), including | ||||||
|  | without limitation, method, process, and apparatus claims, in any patent Licensable | ||||||
|  | by such Contributor that would be infringed, but for the grant of the License, | ||||||
|  | by the making, using, selling, offering for sale, having made, import, or | ||||||
|  | transfer of either its Contributions or its Contributor Version. | ||||||
|  | 
 | ||||||
|  | 1.12. "Secondary License" means either the GNU General Public License, Version | ||||||
|  | 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General | ||||||
|  | Public License, Version 3.0, or any later versions of those licenses. | ||||||
|  | 
 | ||||||
|  | 1.13. "Source Code Form" means the form of the work preferred for making modifications. | ||||||
|  | 
 | ||||||
|  | 1.14. "You" (or "Your") means an individual or a legal entity exercising rights | ||||||
|  | under this License. For legal entities, "You" includes any entity that controls, | ||||||
|  | is controlled by, or is under common control with You. For purposes of this | ||||||
|  | definition, "control" means (a) the power, direct or indirect, to cause the | ||||||
|  | direction or management of such entity, whether by contract or otherwise, | ||||||
|  | or (b) ownership of more than fifty percent (50%) of the outstanding shares | ||||||
|  | or beneficial ownership of such entity. | ||||||
|  | 
 | ||||||
|  |    2. License Grants and Conditions | ||||||
|  | 
 | ||||||
|  |       2.1. Grants | ||||||
|  | 
 | ||||||
|  | Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive | ||||||
|  | license: | ||||||
|  | 
 | ||||||
|  | (a) under intellectual property rights (other than patent or trademark) Licensable | ||||||
|  | by such Contributor to use, reproduce, make available, modify, display, perform, | ||||||
|  | distribute, and otherwise exploit its Contributions, either on an unmodified | ||||||
|  | basis, with Modifications, or as part of a Larger Work; and | ||||||
|  | 
 | ||||||
|  | (b) under Patent Claims of such Contributor to make, use, sell, offer for | ||||||
|  | sale, have made, import, and otherwise transfer either its Contributions or | ||||||
|  | its Contributor Version. | ||||||
|  | 
 | ||||||
|  |       2.2. Effective Date | ||||||
|  | 
 | ||||||
|  | The licenses granted in Section 2.1 with respect to any Contribution become | ||||||
|  | effective for each Contribution on the date the Contributor first distributes | ||||||
|  | such Contribution. | ||||||
|  | 
 | ||||||
|  |       2.3. Limitations on Grant Scope | ||||||
|  | 
 | ||||||
|  | The licenses granted in this Section 2 are the only rights granted under this | ||||||
|  | License. No additional rights or licenses will be implied from the distribution | ||||||
|  | or licensing of Covered Software under this License. Notwithstanding Section | ||||||
|  | 2.1(b) above, no patent license is granted by a Contributor: | ||||||
|  | 
 | ||||||
|  | (a) for any code that a Contributor has removed from Covered Software; or | ||||||
|  | 
 | ||||||
|  | (b) for infringements caused by: (i) Your and any other third party's modifications | ||||||
|  | of Covered Software, or (ii) the combination of its Contributions with other | ||||||
|  | software (except as part of its Contributor Version); or | ||||||
|  | 
 | ||||||
|  | (c) under Patent Claims infringed by Covered Software in the absence of its | ||||||
|  | Contributions. | ||||||
|  | 
 | ||||||
|  | This License does not grant any rights in the trademarks, service marks, or | ||||||
|  | logos of any Contributor (except as may be necessary to comply with the notice | ||||||
|  | requirements in Section 3.4). | ||||||
|  | 
 | ||||||
|  |       2.4. Subsequent Licenses | ||||||
|  | 
 | ||||||
|  | No Contributor makes additional grants as a result of Your choice to distribute | ||||||
|  | the Covered Software under a subsequent version of this License (see Section | ||||||
|  | 10.2) or under the terms of a Secondary License (if permitted under the terms | ||||||
|  | of Section 3.3). | ||||||
|  | 
 | ||||||
|  |       2.5. Representation | ||||||
|  | 
 | ||||||
|  | Each Contributor represents that the Contributor believes its Contributions | ||||||
|  | are its original creation(s) or it has sufficient rights to grant the rights | ||||||
|  | to its Contributions conveyed by this License. | ||||||
|  | 
 | ||||||
|  |       2.6. Fair Use | ||||||
|  | 
 | ||||||
|  | This License is not intended to limit any rights You have under applicable | ||||||
|  | copyright doctrines of fair use, fair dealing, or other equivalents. | ||||||
|  | 
 | ||||||
|  |       2.7. Conditions | ||||||
|  | 
 | ||||||
|  | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in | ||||||
|  | Section 2.1. | ||||||
|  | 
 | ||||||
|  |    3. Responsibilities | ||||||
|  | 
 | ||||||
|  |       3.1. Distribution of Source Form | ||||||
|  | 
 | ||||||
|  | All distribution of Covered Software in Source Code Form, including any Modifications | ||||||
|  | that You create or to which You contribute, must be under the terms of this | ||||||
|  | License. You must inform recipients that the Source Code Form of the Covered | ||||||
|  | Software is governed by the terms of this License, and how they can obtain | ||||||
|  | a copy of this License. You may not attempt to alter or restrict the recipients' | ||||||
|  | rights in the Source Code Form. | ||||||
|  | 
 | ||||||
|  |       3.2. Distribution of Executable Form | ||||||
|  | 
 | ||||||
|  |       If You distribute Covered Software in Executable Form then: | ||||||
|  | 
 | ||||||
|  | (a) such Covered Software must also be made available in Source Code Form, | ||||||
|  | as described in Section 3.1, and You must inform recipients of the Executable | ||||||
|  | Form how they can obtain a copy of such Source Code Form by reasonable means | ||||||
|  | in a timely manner, at a charge no more than the cost of distribution to the | ||||||
|  | recipient; and | ||||||
|  | 
 | ||||||
|  | (b) You may distribute such Executable Form under the terms of this License, | ||||||
|  | or sublicense it under different terms, provided that the license for the | ||||||
|  | Executable Form does not attempt to limit or alter the recipients' rights | ||||||
|  | in the Source Code Form under this License. | ||||||
|  | 
 | ||||||
|  |       3.3. Distribution of a Larger Work | ||||||
|  | 
 | ||||||
|  | You may create and distribute a Larger Work under terms of Your choice, provided | ||||||
|  | that You also comply with the requirements of this License for the Covered | ||||||
|  | Software. If the Larger Work is a combination of Covered Software with a work | ||||||
|  | governed by one or more Secondary Licenses, and the Covered Software is not | ||||||
|  | Incompatible With Secondary Licenses, this License permits You to additionally | ||||||
|  | distribute such Covered Software under the terms of such Secondary License(s), | ||||||
|  | so that the recipient of the Larger Work may, at their option, further distribute | ||||||
|  | the Covered Software under the terms of either this License or such Secondary | ||||||
|  | License(s). | ||||||
|  | 
 | ||||||
|  |       3.4. Notices | ||||||
|  | 
 | ||||||
|  | You may not remove or alter the substance of any license notices (including | ||||||
|  | copyright notices, patent notices, disclaimers of warranty, or limitations | ||||||
|  | of liability) contained within the Source Code Form of the Covered Software, | ||||||
|  | except that You may alter any license notices to the extent required to remedy | ||||||
|  | known factual inaccuracies. | ||||||
|  | 
 | ||||||
|  |       3.5. Application of Additional Terms | ||||||
|  | 
 | ||||||
|  | You may choose to offer, and to charge a fee for, warranty, support, indemnity | ||||||
|  | or liability obligations to one or more recipients of Covered Software. However, | ||||||
|  | You may do so only on Your own behalf, and not on behalf of any Contributor. | ||||||
|  | You must make it absolutely clear that any such warranty, support, indemnity, | ||||||
|  | or liability obligation is offered by You alone, and You hereby agree to indemnify | ||||||
|  | every Contributor for any liability incurred by such Contributor as a result | ||||||
|  | of warranty, support, indemnity or liability terms You offer. You may include | ||||||
|  | additional disclaimers of warranty and limitations of liability specific to | ||||||
|  | any jurisdiction. | ||||||
|  | 
 | ||||||
|  |    4. Inability to Comply Due to Statute or Regulation | ||||||
|  | 
 | ||||||
|  | If it is impossible for You to comply with any of the terms of this License | ||||||
|  | with respect to some or all of the Covered Software due to statute, judicial | ||||||
|  | order, or regulation then You must: (a) comply with the terms of this License | ||||||
|  | to the maximum extent possible; and (b) describe the limitations and the code | ||||||
|  | they affect. Such description must be placed in a text file included with | ||||||
|  | all distributions of the Covered Software under this License. Except to the | ||||||
|  | extent prohibited by statute or regulation, such description must be sufficiently | ||||||
|  | detailed for a recipient of ordinary skill to be able to understand it. | ||||||
|  | 
 | ||||||
|  |    5. Termination | ||||||
|  | 
 | ||||||
|  | 5.1. The rights granted under this License will terminate automatically if | ||||||
|  | You fail to comply with any of its terms. However, if You become compliant, | ||||||
|  | then the rights granted under this License from a particular Contributor are | ||||||
|  | reinstated (a) provisionally, unless and until such Contributor explicitly | ||||||
|  | and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor | ||||||
|  | fails to notify You of the non-compliance by some reasonable means prior to | ||||||
|  | 60 days after You have come back into compliance. Moreover, Your grants from | ||||||
|  | a particular Contributor are reinstated on an ongoing basis if such Contributor | ||||||
|  | notifies You of the non-compliance by some reasonable means, this is the first | ||||||
|  | time You have received notice of non-compliance with this License from such | ||||||
|  | Contributor, and You become compliant prior to 30 days after Your receipt | ||||||
|  | of the notice. | ||||||
|  | 
 | ||||||
|  | 5.2. If You initiate litigation against any entity by asserting a patent infringement | ||||||
|  | claim (excluding declaratory judgment actions, counter-claims, and cross-claims) | ||||||
|  | alleging that a Contributor Version directly or indirectly infringes any patent, | ||||||
|  | then the rights granted to You by any and all Contributors for the Covered | ||||||
|  | Software under Section 2.1 of this License shall terminate. | ||||||
|  | 
 | ||||||
|  | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end | ||||||
|  | user license agreements (excluding distributors and resellers) which have | ||||||
|  | been validly granted by You or Your distributors under this License prior | ||||||
|  | to termination shall survive termination. | ||||||
|  | 
 | ||||||
|  |    6. Disclaimer of Warranty | ||||||
|  | 
 | ||||||
|  | Covered Software is provided under this License on an "as is" basis, without | ||||||
|  | warranty of any kind, either expressed, implied, or statutory, including, | ||||||
|  | without limitation, warranties that the Covered Software is free of defects, | ||||||
|  | merchantable, fit for a particular purpose or non-infringing. The entire risk | ||||||
|  | as to the quality and performance of the Covered Software is with You. Should | ||||||
|  | any Covered Software prove defective in any respect, You (not any Contributor) | ||||||
|  | assume the cost of any necessary servicing, repair, or correction. This disclaimer | ||||||
|  | of warranty constitutes an essential part of this License. No use of any Covered | ||||||
|  | Software is authorized under this License except under this disclaimer. | ||||||
|  | 
 | ||||||
|  |    7. Limitation of Liability | ||||||
|  | 
 | ||||||
|  | Under no circumstances and under no legal theory, whether tort (including | ||||||
|  | negligence), contract, or otherwise, shall any Contributor, or anyone who | ||||||
|  | distributes Covered Software as permitted above, be liable to You for any | ||||||
|  | direct, indirect, special, incidental, or consequential damages of any character | ||||||
|  | including, without limitation, damages for lost profits, loss of goodwill, | ||||||
|  | work stoppage, computer failure or malfunction, or any and all other commercial | ||||||
|  | damages or losses, even if such party shall have been informed of the possibility | ||||||
|  | of such damages. This limitation of liability shall not apply to liability | ||||||
|  | for death or personal injury resulting from such party's negligence to the | ||||||
|  | extent applicable law prohibits such limitation. Some jurisdictions do not | ||||||
|  | allow the exclusion or limitation of incidental or consequential damages, | ||||||
|  | so this exclusion and limitation may not apply to You. | ||||||
|  | 
 | ||||||
|  |    8. Litigation | ||||||
|  | 
 | ||||||
|  | Any litigation relating to this License may be brought only in the courts | ||||||
|  | of a jurisdiction where the defendant maintains its principal place of business | ||||||
|  | and such litigation shall be governed by laws of that jurisdiction, without | ||||||
|  | reference to its conflict-of-law provisions. Nothing in this Section shall | ||||||
|  | prevent a party's ability to bring cross-claims or counter-claims. | ||||||
|  | 
 | ||||||
|  |    9. Miscellaneous | ||||||
|  | 
 | ||||||
|  | This License represents the complete agreement concerning the subject matter | ||||||
|  | hereof. If any provision of this License is held to be unenforceable, such | ||||||
|  | provision shall be reformed only to the extent necessary to make it enforceable. | ||||||
|  | Any law or regulation which provides that the language of a contract shall | ||||||
|  | be construed against the drafter shall not be used to construe this License | ||||||
|  | against a Contributor. | ||||||
|  | 
 | ||||||
|  |    10. Versions of the License | ||||||
|  | 
 | ||||||
|  |       10.1. New Versions | ||||||
|  | 
 | ||||||
|  | Mozilla Foundation is the license steward. Except as provided in Section 10.3, | ||||||
|  | no one other than the license steward has the right to modify or publish new | ||||||
|  | versions of this License. Each version will be given a distinguishing version | ||||||
|  | number. | ||||||
|  | 
 | ||||||
|  |       10.2. Effect of New Versions | ||||||
|  | 
 | ||||||
|  | You may distribute the Covered Software under the terms of the version of | ||||||
|  | the License under which You originally received the Covered Software, or under | ||||||
|  | the terms of any subsequent version published by the license steward. | ||||||
|  | 
 | ||||||
|  |       10.3. Modified Versions | ||||||
|  | 
 | ||||||
|  | If you create software not governed by this License, and you want to create | ||||||
|  | a new license for such software, you may create and use a modified version | ||||||
|  | of this License if you rename the license and remove any references to the | ||||||
|  | name of the license steward (except to note that such modified license differs | ||||||
|  | from this License). | ||||||
|  | 
 | ||||||
|  | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses | ||||||
|  | 
 | ||||||
|  | If You choose to distribute Source Code Form that is Incompatible With Secondary | ||||||
|  | Licenses under the terms of this version of the License, the notice described | ||||||
|  | in Exhibit B of this License must be attached. Exhibit A - Source Code Form | ||||||
|  | License Notice | ||||||
|  | 
 | ||||||
|  | This Source Code Form is subject to the terms of the Mozilla Public License, | ||||||
|  | v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain | ||||||
|  | one at http://mozilla.org/MPL/2.0/. | ||||||
|  | 
 | ||||||
|  | If it is not possible or desirable to put the notice in a particular file, | ||||||
|  | then You may include the notice in a location (such as a LICENSE file in a | ||||||
|  | relevant directory) where a recipient would be likely to look for such a notice. | ||||||
|  | 
 | ||||||
|  | You may add additional accurate notices of copyright ownership. | ||||||
|  | 
 | ||||||
|  | Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||||
|  | 
 | ||||||
|  | This Source Code Form is "Incompatible With Secondary Licenses", as defined | ||||||
|  | by the Mozilla Public License, v. 2.0. | ||||||
							
								
								
									
										143
									
								
								vendor/git.rootprojects.org/root/go-gitver/gitver/gitver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								vendor/git.rootprojects.org/root/go-gitver/gitver/gitver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,143 @@ | |||||||
|  | package gitver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var exactVer *regexp.Regexp | ||||||
|  | var gitVer *regexp.Regexp | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	// exactly vX.Y.Z (go-compatible semver) | ||||||
|  | 	exactVer = regexp.MustCompile(`^v\d+\.\d+\.\d+$`) | ||||||
|  | 
 | ||||||
|  | 	// vX.Y.Z-n-g0000000 git post-release, semver prerelease | ||||||
|  | 	// vX.Y.Z-dirty git post-release, semver prerelease | ||||||
|  | 	gitVer = regexp.MustCompile(`^(v\d+\.\d+)\.(\d+)(-(\d+))?(-(g[0-9a-f]+))?(-(dirty))?`) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Versions struct { | ||||||
|  | 	Timestamp time.Time | ||||||
|  | 	Version   string | ||||||
|  | 	Rev       string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExecAndParse() (*Versions, error) { | ||||||
|  | 	desc, err := gitDesc() | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	rev, err := gitRev() | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	ver, err := semVer(desc) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	ts, err := gitTimestamp(desc) | ||||||
|  | 	if nil != err { | ||||||
|  | 		ts = time.Now() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &Versions{ | ||||||
|  | 		Timestamp: ts, | ||||||
|  | 		Version:   ver, | ||||||
|  | 		Rev:       rev, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func gitDesc() (string, error) { | ||||||
|  | 	args := strings.Split("git describe --tags --dirty --always", " ") | ||||||
|  | 	cmd := exec.Command(args[0], args[1:]...) | ||||||
|  | 	out, err := cmd.CombinedOutput() | ||||||
|  | 	if nil != err { | ||||||
|  | 		// Don't panic, just carry on | ||||||
|  | 		//out = []byte("v0.0.0-0-g0000000") | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return strings.TrimSpace(string(out)), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func gitRev() (string, error) { | ||||||
|  | 	args := strings.Split("git rev-parse HEAD", " ") | ||||||
|  | 	cmd := exec.Command(args[0], args[1:]...) | ||||||
|  | 	out, err := cmd.CombinedOutput() | ||||||
|  | 	if nil != err { | ||||||
|  | 		return "", fmt.Errorf("\nUnexpected Error\n\n"+ | ||||||
|  | 			"Please open an issue at https://git.rootprojects.org/root/go-gitver/issues/new \n"+ | ||||||
|  | 			"Please include the following:\n\n"+ | ||||||
|  | 			"Command: %s\n"+ | ||||||
|  | 			"Output: %s\n"+ | ||||||
|  | 			"Error: %s\n"+ | ||||||
|  | 			"\nPlease and Thank You.\n\n", strings.Join(args, " "), out, err) | ||||||
|  | 	} | ||||||
|  | 	return strings.TrimSpace(string(out)), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func semVer(desc string) (string, error) { | ||||||
|  | 	if exactVer.MatchString(desc) { | ||||||
|  | 		// v1.0.0 | ||||||
|  | 		return desc, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !gitVer.MatchString(desc) { | ||||||
|  | 		return "", nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// (v1.0).(0)(-(1))(-(g0000000))(-(dirty)) | ||||||
|  | 	vers := gitVer.FindStringSubmatch(desc) | ||||||
|  | 	patch, err := strconv.Atoi(vers[2]) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return "", fmt.Errorf("\nUnexpected Error\n\n"+ | ||||||
|  | 			"Please open an issue at https://git.rootprojects.org/root/go-gitver/issues/new \n"+ | ||||||
|  | 			"Please include the following:\n\n"+ | ||||||
|  | 			"git description: %s\n"+ | ||||||
|  | 			"RegExp: %#v\n"+ | ||||||
|  | 			"Error: %s\n"+ | ||||||
|  | 			"\nPlease and Thank You.\n\n", desc, gitVer, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// v1.0.1-pre1 | ||||||
|  | 	// v1.0.1-pre1+g0000000 | ||||||
|  | 	// v1.0.1-pre0+dirty | ||||||
|  | 	// v1.0.1-pre0+g0000000-dirty | ||||||
|  | 	if "" == vers[4] { | ||||||
|  | 		vers[4] = "0" | ||||||
|  | 	} | ||||||
|  | 	ver := fmt.Sprintf("%s.%d-pre%s", vers[1], patch+1, vers[4]) | ||||||
|  | 	if "" != vers[6] || "dirty" == vers[8] { | ||||||
|  | 		ver += "+" | ||||||
|  | 		if "" != vers[6] { | ||||||
|  | 			ver += vers[6] | ||||||
|  | 			if "" != vers[8] { | ||||||
|  | 				ver += "-" | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		ver += vers[8] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ver, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func gitTimestamp(desc string) (time.Time, error) { | ||||||
|  | 	args := []string{ | ||||||
|  | 		"git", | ||||||
|  | 		"show", desc, | ||||||
|  | 		"--format=%cd", | ||||||
|  | 		"--date=format:%Y-%m-%dT%H:%M:%SZ%z", | ||||||
|  | 		"--no-patch", | ||||||
|  | 	} | ||||||
|  | 	cmd := exec.Command(args[0], args[1:]...) | ||||||
|  | 	out, err := cmd.CombinedOutput() | ||||||
|  | 	if nil != err { | ||||||
|  | 		// a dirty desc was probably used | ||||||
|  | 		return time.Time{}, err | ||||||
|  | 	} | ||||||
|  | 	return time.Parse(time.RFC3339, strings.TrimSpace(string(out))) | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | xversion.go | ||||||
|  | zversion.go | ||||||
|  | 
 | ||||||
|  | /go-gitver | ||||||
|  | examples/**.sum | ||||||
|  | 
 | ||||||
|  | # ---> Go | ||||||
|  | # Binaries for programs and plugins | ||||||
|  | *.exe | ||||||
|  | *.exe~ | ||||||
|  | *.dll | ||||||
|  | *.so | ||||||
|  | *.dylib | ||||||
|  | 
 | ||||||
|  | # Test binary, build with `go test -c` | ||||||
|  | *.test | ||||||
|  | 
 | ||||||
|  | # Output of the go coverage tool, specifically when used with LiteIDE | ||||||
|  | *.out | ||||||
|  | 
 | ||||||
							
								
								
									
										1
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/.prettierrc
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/.prettierrc
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | {} | ||||||
							
								
								
									
										312
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,312 @@ | |||||||
|  | Mozilla Public License Version 2.0 | ||||||
|  | 
 | ||||||
|  |    1. Definitions | ||||||
|  | 
 | ||||||
|  | 1.1. "Contributor" means each individual or legal entity that creates, contributes | ||||||
|  | to the creation of, or owns Covered Software. | ||||||
|  | 
 | ||||||
|  | 1.2. "Contributor Version" means the combination of the Contributions of others | ||||||
|  | (if any) used by a Contributor and that particular Contributor's Contribution. | ||||||
|  | 
 | ||||||
|  |       1.3. "Contribution" means Covered Software of a particular Contributor. | ||||||
|  | 
 | ||||||
|  | 1.4. "Covered Software" means Source Code Form to which the initial Contributor | ||||||
|  | has attached the notice in Exhibit A, the Executable Form of such Source Code | ||||||
|  | Form, and Modifications of such Source Code Form, in each case including portions | ||||||
|  | thereof. | ||||||
|  | 
 | ||||||
|  |       1.5. "Incompatible With Secondary Licenses" means | ||||||
|  | 
 | ||||||
|  | (a) that the initial Contributor has attached the notice described in Exhibit | ||||||
|  | B to the Covered Software; or | ||||||
|  | 
 | ||||||
|  | (b) that the Covered Software was made available under the terms of version | ||||||
|  | 1.1 or earlier of the License, but not also under the terms of a Secondary | ||||||
|  | License. | ||||||
|  | 
 | ||||||
|  | 1.6. "Executable Form" means any form of the work other than Source Code Form. | ||||||
|  | 
 | ||||||
|  | 1.7. "Larger Work" means a work that combines Covered Software with other | ||||||
|  | material, in a separate file or files, that is not Covered Software. | ||||||
|  | 
 | ||||||
|  |       1.8. "License" means this document. | ||||||
|  | 
 | ||||||
|  | 1.9. "Licensable" means having the right to grant, to the maximum extent possible, | ||||||
|  | whether at the time of the initial grant or subsequently, any and all of the | ||||||
|  | rights conveyed by this License. | ||||||
|  | 
 | ||||||
|  |       1.10. "Modifications" means any of the following: | ||||||
|  | 
 | ||||||
|  | (a) any file in Source Code Form that results from an addition to, deletion | ||||||
|  | from, or modification of the contents of Covered Software; or | ||||||
|  | 
 | ||||||
|  | (b) any new file in Source Code Form that contains any Covered Software. | ||||||
|  | 
 | ||||||
|  | 1.11. "Patent Claims" of a Contributor means any patent claim(s), including | ||||||
|  | without limitation, method, process, and apparatus claims, in any patent Licensable | ||||||
|  | by such Contributor that would be infringed, but for the grant of the License, | ||||||
|  | by the making, using, selling, offering for sale, having made, import, or | ||||||
|  | transfer of either its Contributions or its Contributor Version. | ||||||
|  | 
 | ||||||
|  | 1.12. "Secondary License" means either the GNU General Public License, Version | ||||||
|  | 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General | ||||||
|  | Public License, Version 3.0, or any later versions of those licenses. | ||||||
|  | 
 | ||||||
|  | 1.13. "Source Code Form" means the form of the work preferred for making modifications. | ||||||
|  | 
 | ||||||
|  | 1.14. "You" (or "Your") means an individual or a legal entity exercising rights | ||||||
|  | under this License. For legal entities, "You" includes any entity that controls, | ||||||
|  | is controlled by, or is under common control with You. For purposes of this | ||||||
|  | definition, "control" means (a) the power, direct or indirect, to cause the | ||||||
|  | direction or management of such entity, whether by contract or otherwise, | ||||||
|  | or (b) ownership of more than fifty percent (50%) of the outstanding shares | ||||||
|  | or beneficial ownership of such entity. | ||||||
|  | 
 | ||||||
|  |    2. License Grants and Conditions | ||||||
|  | 
 | ||||||
|  |       2.1. Grants | ||||||
|  | 
 | ||||||
|  | Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive | ||||||
|  | license: | ||||||
|  | 
 | ||||||
|  | (a) under intellectual property rights (other than patent or trademark) Licensable | ||||||
|  | by such Contributor to use, reproduce, make available, modify, display, perform, | ||||||
|  | distribute, and otherwise exploit its Contributions, either on an unmodified | ||||||
|  | basis, with Modifications, or as part of a Larger Work; and | ||||||
|  | 
 | ||||||
|  | (b) under Patent Claims of such Contributor to make, use, sell, offer for | ||||||
|  | sale, have made, import, and otherwise transfer either its Contributions or | ||||||
|  | its Contributor Version. | ||||||
|  | 
 | ||||||
|  |       2.2. Effective Date | ||||||
|  | 
 | ||||||
|  | The licenses granted in Section 2.1 with respect to any Contribution become | ||||||
|  | effective for each Contribution on the date the Contributor first distributes | ||||||
|  | such Contribution. | ||||||
|  | 
 | ||||||
|  |       2.3. Limitations on Grant Scope | ||||||
|  | 
 | ||||||
|  | The licenses granted in this Section 2 are the only rights granted under this | ||||||
|  | License. No additional rights or licenses will be implied from the distribution | ||||||
|  | or licensing of Covered Software under this License. Notwithstanding Section | ||||||
|  | 2.1(b) above, no patent license is granted by a Contributor: | ||||||
|  | 
 | ||||||
|  | (a) for any code that a Contributor has removed from Covered Software; or | ||||||
|  | 
 | ||||||
|  | (b) for infringements caused by: (i) Your and any other third party's modifications | ||||||
|  | of Covered Software, or (ii) the combination of its Contributions with other | ||||||
|  | software (except as part of its Contributor Version); or | ||||||
|  | 
 | ||||||
|  | (c) under Patent Claims infringed by Covered Software in the absence of its | ||||||
|  | Contributions. | ||||||
|  | 
 | ||||||
|  | This License does not grant any rights in the trademarks, service marks, or | ||||||
|  | logos of any Contributor (except as may be necessary to comply with the notice | ||||||
|  | requirements in Section 3.4). | ||||||
|  | 
 | ||||||
|  |       2.4. Subsequent Licenses | ||||||
|  | 
 | ||||||
|  | No Contributor makes additional grants as a result of Your choice to distribute | ||||||
|  | the Covered Software under a subsequent version of this License (see Section | ||||||
|  | 10.2) or under the terms of a Secondary License (if permitted under the terms | ||||||
|  | of Section 3.3). | ||||||
|  | 
 | ||||||
|  |       2.5. Representation | ||||||
|  | 
 | ||||||
|  | Each Contributor represents that the Contributor believes its Contributions | ||||||
|  | are its original creation(s) or it has sufficient rights to grant the rights | ||||||
|  | to its Contributions conveyed by this License. | ||||||
|  | 
 | ||||||
|  |       2.6. Fair Use | ||||||
|  | 
 | ||||||
|  | This License is not intended to limit any rights You have under applicable | ||||||
|  | copyright doctrines of fair use, fair dealing, or other equivalents. | ||||||
|  | 
 | ||||||
|  |       2.7. Conditions | ||||||
|  | 
 | ||||||
|  | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in | ||||||
|  | Section 2.1. | ||||||
|  | 
 | ||||||
|  |    3. Responsibilities | ||||||
|  | 
 | ||||||
|  |       3.1. Distribution of Source Form | ||||||
|  | 
 | ||||||
|  | All distribution of Covered Software in Source Code Form, including any Modifications | ||||||
|  | that You create or to which You contribute, must be under the terms of this | ||||||
|  | License. You must inform recipients that the Source Code Form of the Covered | ||||||
|  | Software is governed by the terms of this License, and how they can obtain | ||||||
|  | a copy of this License. You may not attempt to alter or restrict the recipients' | ||||||
|  | rights in the Source Code Form. | ||||||
|  | 
 | ||||||
|  |       3.2. Distribution of Executable Form | ||||||
|  | 
 | ||||||
|  |       If You distribute Covered Software in Executable Form then: | ||||||
|  | 
 | ||||||
|  | (a) such Covered Software must also be made available in Source Code Form, | ||||||
|  | as described in Section 3.1, and You must inform recipients of the Executable | ||||||
|  | Form how they can obtain a copy of such Source Code Form by reasonable means | ||||||
|  | in a timely manner, at a charge no more than the cost of distribution to the | ||||||
|  | recipient; and | ||||||
|  | 
 | ||||||
|  | (b) You may distribute such Executable Form under the terms of this License, | ||||||
|  | or sublicense it under different terms, provided that the license for the | ||||||
|  | Executable Form does not attempt to limit or alter the recipients' rights | ||||||
|  | in the Source Code Form under this License. | ||||||
|  | 
 | ||||||
|  |       3.3. Distribution of a Larger Work | ||||||
|  | 
 | ||||||
|  | You may create and distribute a Larger Work under terms of Your choice, provided | ||||||
|  | that You also comply with the requirements of this License for the Covered | ||||||
|  | Software. If the Larger Work is a combination of Covered Software with a work | ||||||
|  | governed by one or more Secondary Licenses, and the Covered Software is not | ||||||
|  | Incompatible With Secondary Licenses, this License permits You to additionally | ||||||
|  | distribute such Covered Software under the terms of such Secondary License(s), | ||||||
|  | so that the recipient of the Larger Work may, at their option, further distribute | ||||||
|  | the Covered Software under the terms of either this License or such Secondary | ||||||
|  | License(s). | ||||||
|  | 
 | ||||||
|  |       3.4. Notices | ||||||
|  | 
 | ||||||
|  | You may not remove or alter the substance of any license notices (including | ||||||
|  | copyright notices, patent notices, disclaimers of warranty, or limitations | ||||||
|  | of liability) contained within the Source Code Form of the Covered Software, | ||||||
|  | except that You may alter any license notices to the extent required to remedy | ||||||
|  | known factual inaccuracies. | ||||||
|  | 
 | ||||||
|  |       3.5. Application of Additional Terms | ||||||
|  | 
 | ||||||
|  | You may choose to offer, and to charge a fee for, warranty, support, indemnity | ||||||
|  | or liability obligations to one or more recipients of Covered Software. However, | ||||||
|  | You may do so only on Your own behalf, and not on behalf of any Contributor. | ||||||
|  | You must make it absolutely clear that any such warranty, support, indemnity, | ||||||
|  | or liability obligation is offered by You alone, and You hereby agree to indemnify | ||||||
|  | every Contributor for any liability incurred by such Contributor as a result | ||||||
|  | of warranty, support, indemnity or liability terms You offer. You may include | ||||||
|  | additional disclaimers of warranty and limitations of liability specific to | ||||||
|  | any jurisdiction. | ||||||
|  | 
 | ||||||
|  |    4. Inability to Comply Due to Statute or Regulation | ||||||
|  | 
 | ||||||
|  | If it is impossible for You to comply with any of the terms of this License | ||||||
|  | with respect to some or all of the Covered Software due to statute, judicial | ||||||
|  | order, or regulation then You must: (a) comply with the terms of this License | ||||||
|  | to the maximum extent possible; and (b) describe the limitations and the code | ||||||
|  | they affect. Such description must be placed in a text file included with | ||||||
|  | all distributions of the Covered Software under this License. Except to the | ||||||
|  | extent prohibited by statute or regulation, such description must be sufficiently | ||||||
|  | detailed for a recipient of ordinary skill to be able to understand it. | ||||||
|  | 
 | ||||||
|  |    5. Termination | ||||||
|  | 
 | ||||||
|  | 5.1. The rights granted under this License will terminate automatically if | ||||||
|  | You fail to comply with any of its terms. However, if You become compliant, | ||||||
|  | then the rights granted under this License from a particular Contributor are | ||||||
|  | reinstated (a) provisionally, unless and until such Contributor explicitly | ||||||
|  | and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor | ||||||
|  | fails to notify You of the non-compliance by some reasonable means prior to | ||||||
|  | 60 days after You have come back into compliance. Moreover, Your grants from | ||||||
|  | a particular Contributor are reinstated on an ongoing basis if such Contributor | ||||||
|  | notifies You of the non-compliance by some reasonable means, this is the first | ||||||
|  | time You have received notice of non-compliance with this License from such | ||||||
|  | Contributor, and You become compliant prior to 30 days after Your receipt | ||||||
|  | of the notice. | ||||||
|  | 
 | ||||||
|  | 5.2. If You initiate litigation against any entity by asserting a patent infringement | ||||||
|  | claim (excluding declaratory judgment actions, counter-claims, and cross-claims) | ||||||
|  | alleging that a Contributor Version directly or indirectly infringes any patent, | ||||||
|  | then the rights granted to You by any and all Contributors for the Covered | ||||||
|  | Software under Section 2.1 of this License shall terminate. | ||||||
|  | 
 | ||||||
|  | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end | ||||||
|  | user license agreements (excluding distributors and resellers) which have | ||||||
|  | been validly granted by You or Your distributors under this License prior | ||||||
|  | to termination shall survive termination. | ||||||
|  | 
 | ||||||
|  |    6. Disclaimer of Warranty | ||||||
|  | 
 | ||||||
|  | Covered Software is provided under this License on an "as is" basis, without | ||||||
|  | warranty of any kind, either expressed, implied, or statutory, including, | ||||||
|  | without limitation, warranties that the Covered Software is free of defects, | ||||||
|  | merchantable, fit for a particular purpose or non-infringing. The entire risk | ||||||
|  | as to the quality and performance of the Covered Software is with You. Should | ||||||
|  | any Covered Software prove defective in any respect, You (not any Contributor) | ||||||
|  | assume the cost of any necessary servicing, repair, or correction. This disclaimer | ||||||
|  | of warranty constitutes an essential part of this License. No use of any Covered | ||||||
|  | Software is authorized under this License except under this disclaimer. | ||||||
|  | 
 | ||||||
|  |    7. Limitation of Liability | ||||||
|  | 
 | ||||||
|  | Under no circumstances and under no legal theory, whether tort (including | ||||||
|  | negligence), contract, or otherwise, shall any Contributor, or anyone who | ||||||
|  | distributes Covered Software as permitted above, be liable to You for any | ||||||
|  | direct, indirect, special, incidental, or consequential damages of any character | ||||||
|  | including, without limitation, damages for lost profits, loss of goodwill, | ||||||
|  | work stoppage, computer failure or malfunction, or any and all other commercial | ||||||
|  | damages or losses, even if such party shall have been informed of the possibility | ||||||
|  | of such damages. This limitation of liability shall not apply to liability | ||||||
|  | for death or personal injury resulting from such party's negligence to the | ||||||
|  | extent applicable law prohibits such limitation. Some jurisdictions do not | ||||||
|  | allow the exclusion or limitation of incidental or consequential damages, | ||||||
|  | so this exclusion and limitation may not apply to You. | ||||||
|  | 
 | ||||||
|  |    8. Litigation | ||||||
|  | 
 | ||||||
|  | Any litigation relating to this License may be brought only in the courts | ||||||
|  | of a jurisdiction where the defendant maintains its principal place of business | ||||||
|  | and such litigation shall be governed by laws of that jurisdiction, without | ||||||
|  | reference to its conflict-of-law provisions. Nothing in this Section shall | ||||||
|  | prevent a party's ability to bring cross-claims or counter-claims. | ||||||
|  | 
 | ||||||
|  |    9. Miscellaneous | ||||||
|  | 
 | ||||||
|  | This License represents the complete agreement concerning the subject matter | ||||||
|  | hereof. If any provision of this License is held to be unenforceable, such | ||||||
|  | provision shall be reformed only to the extent necessary to make it enforceable. | ||||||
|  | Any law or regulation which provides that the language of a contract shall | ||||||
|  | be construed against the drafter shall not be used to construe this License | ||||||
|  | against a Contributor. | ||||||
|  | 
 | ||||||
|  |    10. Versions of the License | ||||||
|  | 
 | ||||||
|  |       10.1. New Versions | ||||||
|  | 
 | ||||||
|  | Mozilla Foundation is the license steward. Except as provided in Section 10.3, | ||||||
|  | no one other than the license steward has the right to modify or publish new | ||||||
|  | versions of this License. Each version will be given a distinguishing version | ||||||
|  | number. | ||||||
|  | 
 | ||||||
|  |       10.2. Effect of New Versions | ||||||
|  | 
 | ||||||
|  | You may distribute the Covered Software under the terms of the version of | ||||||
|  | the License under which You originally received the Covered Software, or under | ||||||
|  | the terms of any subsequent version published by the license steward. | ||||||
|  | 
 | ||||||
|  |       10.3. Modified Versions | ||||||
|  | 
 | ||||||
|  | If you create software not governed by this License, and you want to create | ||||||
|  | a new license for such software, you may create and use a modified version | ||||||
|  | of this License if you rename the license and remove any references to the | ||||||
|  | name of the license steward (except to note that such modified license differs | ||||||
|  | from this License). | ||||||
|  | 
 | ||||||
|  | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses | ||||||
|  | 
 | ||||||
|  | If You choose to distribute Source Code Form that is Incompatible With Secondary | ||||||
|  | Licenses under the terms of this version of the License, the notice described | ||||||
|  | in Exhibit B of this License must be attached. Exhibit A - Source Code Form | ||||||
|  | License Notice | ||||||
|  | 
 | ||||||
|  | This Source Code Form is subject to the terms of the Mozilla Public License, | ||||||
|  | v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain | ||||||
|  | one at http://mozilla.org/MPL/2.0/. | ||||||
|  | 
 | ||||||
|  | If it is not possible or desirable to put the notice in a particular file, | ||||||
|  | then You may include the notice in a location (such as a LICENSE file in a | ||||||
|  | relevant directory) where a recipient would be likely to look for such a notice. | ||||||
|  | 
 | ||||||
|  | You may add additional accurate notices of copyright ownership. | ||||||
|  | 
 | ||||||
|  | Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||||
|  | 
 | ||||||
|  | This Source Code Form is "Incompatible With Secondary Licenses", as defined | ||||||
|  | by the Mozilla Public License, v. 2.0. | ||||||
							
								
								
									
										256
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,256 @@ | |||||||
|  | # [Go GitVer](https://git.rootprojects.org/root/go-gitver) | ||||||
|  | 
 | ||||||
|  | Use **git tags** to add (GoRelesear-compatible) [**semver**](https://semver.org/) | ||||||
|  | to your go package in under 150 | ||||||
|  | [lines of code](https://git.rootprojects.org/root/go-gitver/src/branch/master/gitver/gitver.go). | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | Goals: | ||||||
|  | 
 | ||||||
|  |       1. Use an exact `git tag` version, like v1.0.0, when clean | ||||||
|  |       2. Translate the `git describe` version  (v1.0.0-4-g0000000) | ||||||
|  | 	     to semver (1.0.1-pre4+g0000000) in between releases | ||||||
|  |       3. Note when `dirty` (and have build timestamp) | ||||||
|  | 
 | ||||||
|  |       Fail gracefully when git repo isn't available. | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # GoDoc | ||||||
|  | 
 | ||||||
|  | See <https://pkg.go.dev/git.rootprojects.org/root/go-gitver>. | ||||||
|  | 
 | ||||||
|  | # How it works | ||||||
|  | 
 | ||||||
|  | 1. You define the fallback version and version printing in `main.go`: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | //go:generate go run git.rootprojects.org/root/go-gitver | ||||||
|  | 
 | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	commit  = "0000000" | ||||||
|  | 	version = "0.0.0-pre0+0000000" | ||||||
|  | 	date    = "0000-00-00T00:00:00+0000" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	if (len(os.Args) > 1 && "version" === os.Args[1]) { | ||||||
|  | 		fmt.Printf("Foobar v%s (%s) %s\n", version, commit[:7], date) | ||||||
|  | 	} | ||||||
|  | 	// ... | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 2. You `go generate` or `go run git.rootprojects.org/root/go-gitver` to generate `xversion.go`: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  |     commit  = "0921ed1e" | ||||||
|  |     version = "1.1.2" | ||||||
|  |     date    = "2019-07-01T02:32:58-06:00" | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Demo | ||||||
|  | 
 | ||||||
|  | Generate an `xversion.go` file: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | go run git.rootprojects.org/root/go-gitver | ||||||
|  | cat xversion.go | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | // Code generated by go generate; DO NOT EDIT. | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	commit  = "6dace8255b52e123297a44629bc32c015add310a" | ||||||
|  | 	version = "1.1.4-pre2+g6dace82" | ||||||
|  | 	date    = "2020-07-16T20:48:15-06:00" | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | <small>**Note**: The file is named `xversion.go` by default so that the | ||||||
|  | generated file's `init()` will come later, and thus take priority, over | ||||||
|  | most other files.</small> | ||||||
|  | 
 | ||||||
|  | See `go-gitver`s self-generated version: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | go run git.rootprojects.org/root/go-gitver version | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | 6dace8255b52e123297a44629bc32c015add310a | ||||||
|  | v1.1.4-pre2+g6dace82 | ||||||
|  | 2020-07-16T20:48:15-06:00 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # QuickStart | ||||||
|  | 
 | ||||||
|  | Add this to the top of your main file, so that it runs with `go generate`: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | //go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Add a file that imports go-gitver (for versioning) | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | // +build tools | ||||||
|  | 
 | ||||||
|  | package example | ||||||
|  | 
 | ||||||
|  | import _ "git.rootprojects.org/root/go-gitver" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Change you build instructions to be something like this: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | go mod vendor | ||||||
|  | go generate -mod=vendor ./... | ||||||
|  | go build -mod=vendor -o example cmd/example/*.go | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | You don't have to use `-mod=vendor`, but I highly recommend it (just `go mod tidy; go mod vendor` to start). | ||||||
|  | 
 | ||||||
|  | # Options | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | version           print version and exit | ||||||
|  | --fail            exit with non-zero status code on failure | ||||||
|  | --package <name>  will set the package name | ||||||
|  | --outfile <name>  will replace `xversion.go` with the given file path | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ENVs | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Alias for --fail | ||||||
|  | GITVER_FAIL=true | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | For example: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | //go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver --fail | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | go run -mod=vendor git.rootprojects.org/root/go-gitver version | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Usage | ||||||
|  | 
 | ||||||
|  | See `examples/basic` | ||||||
|  | 
 | ||||||
|  | 1. Create a `tools` package in your project | ||||||
|  | 2. Guard it against regular builds with `// +build tools` | ||||||
|  | 3. Include `_ "git.rootprojects.org/root/go-gitver"` in the imports | ||||||
|  | 4. Declare `var commit, version, date string` in your `package main` | ||||||
|  | 5. Include `//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver` as well | ||||||
|  | 
 | ||||||
|  | `tools/tools.go`: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | // +build tools | ||||||
|  | 
 | ||||||
|  | // This is a dummy package for build tooling | ||||||
|  | package tools | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	_ "git.rootprojects.org/root/go-gitver" | ||||||
|  | ) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `main.go`: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | //go:generate go run git.rootprojects.org/root/go-gitver --fail | ||||||
|  | 
 | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import "fmt" | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	commit  = "0000000" | ||||||
|  | 	version = "0.0.0-pre0+0000000" | ||||||
|  | 	date    = "0000-00-00T00:00:00+0000" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  |   fmt.Println(commit) | ||||||
|  |   fmt.Println(version) | ||||||
|  |   fmt.Println(date) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | If you're using `go mod vendor` (which I highly recommend that you do), | ||||||
|  | you'd modify the `go:generate` ever so slightly: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | //go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver --fail | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The only reason I didn't do that in the example is that I'd be included | ||||||
|  | the repository in itself and that would be... weird. | ||||||
|  | 
 | ||||||
|  | # Why a tools package? | ||||||
|  | 
 | ||||||
|  | > import "git.rootprojects.org/root/go-gitver" is a program, not an importable package | ||||||
|  | 
 | ||||||
|  | Having a tools package with a build tag that you don't use is a nice way to add exact | ||||||
|  | versions of a command package used for tooling to your `go.mod` with `go mod tidy`, | ||||||
|  | without getting the error above. | ||||||
|  | 
 | ||||||
|  | # git: behind the curtain | ||||||
|  | 
 | ||||||
|  | These are the commands that are used under the hood to produce the versions. | ||||||
|  | 
 | ||||||
|  | Shows the git tag + description. Assumes that you're using the semver format `v1.0.0` for your base tags. | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | git describe --tags --dirty --always | ||||||
|  | # v1.0.0 | ||||||
|  | # v1.0.0-1-g0000000 | ||||||
|  | # v1.0.0-dirty | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Show the commit date (when the commit made it into the current tree). | ||||||
|  | Internally we use the current date when the working tree is dirty. | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | git show v1.0.0-1-g0000000 --format=%cd --date=format:%Y-%m-%dT%H:%M:%SZ%z --no-patch | ||||||
|  | # 2010-01-01T20:30:00Z-0600 | ||||||
|  | # fatal: ambiguous argument 'v1.0.0-1-g0000000-dirty': unknown revision or path not in the working tree. | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Shows the most recent commit. | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | git rev-parse HEAD | ||||||
|  | # 0000000000000000000000000000000000000000 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Errors | ||||||
|  | 
 | ||||||
|  | ### cannot find package "." | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | package git.rootprojects.org/root/go-gitver: cannot find package "." in: | ||||||
|  | 	/Users/me/go-example/vendor/git.rootprojects.org/root/go-gitver | ||||||
|  | cmd/example/example.go:1: running "go": exit status 1 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | You forgot to update deps and re-vendor: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | go mod tidy | ||||||
|  | go mod vendor | ||||||
|  | ``` | ||||||
							
								
								
									
										104
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/gitver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/gitver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | |||||||
|  | //go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver | ||||||
|  | 
 | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/format" | ||||||
|  | 	"os" | ||||||
|  | 	"text/template" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.rootprojects.org/root/go-gitver/gitver" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var commit, version, date string | ||||||
|  | var exitCode int | ||||||
|  | var verFile = "xversion.go" | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	pkg := "main" | ||||||
|  | 
 | ||||||
|  | 	args := os.Args[1:] | ||||||
|  | 	for i := range args { | ||||||
|  | 		arg := args[i] | ||||||
|  | 		if "-f" == arg || "--fail" == arg { | ||||||
|  | 			exitCode = 1 | ||||||
|  | 		} else if ("--outfile" == arg || "-o" == arg) && len(args) > i+1 { | ||||||
|  | 			verFile = args[i+1] | ||||||
|  | 			args[i+1] = "" | ||||||
|  | 		} else if "--package" == arg && len(args) > i+1 { | ||||||
|  | 			pkg = args[i+1] | ||||||
|  | 			args[i+1] = "" | ||||||
|  | 		} else if "-V" == arg || "version" == arg || "-version" == arg || "--version" == arg { | ||||||
|  | 			fmt.Println(commit) | ||||||
|  | 			fmt.Println(version) | ||||||
|  | 			fmt.Println(date) | ||||||
|  | 			os.Exit(0) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if "" != os.Getenv("GITVER_FAIL") && "false" != os.Getenv("GITVER_FAIL") { | ||||||
|  | 		exitCode = 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	v, err := gitver.ExecAndParse() | ||||||
|  | 	if nil != err { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "Failed to get git version: %s\n", err) | ||||||
|  | 		if exitCode > 0 { | ||||||
|  | 			os.Exit(exitCode) | ||||||
|  | 		} | ||||||
|  | 		v = &gitver.Versions{ | ||||||
|  | 			Timestamp: time.Now(), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Create or overwrite the go file from template | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	if err := versionTpl.Execute(&buf, struct { | ||||||
|  | 		Package   string | ||||||
|  | 		Timestamp string | ||||||
|  | 		Version   string | ||||||
|  | 		Commit    string | ||||||
|  | 	}{ | ||||||
|  | 		Package:   pkg, | ||||||
|  | 		Timestamp: v.Timestamp.Format(time.RFC3339), | ||||||
|  | 		Version:   v.Version, | ||||||
|  | 		Commit:    v.Rev, | ||||||
|  | 	}); nil != err { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Format | ||||||
|  | 	src, err := format.Source(buf.Bytes()) | ||||||
|  | 	if nil != err { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Write to disk (in the Current Working Directory) | ||||||
|  | 	f, err := os.Create(verFile) | ||||||
|  | 	if nil != err { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := f.Write(src); nil != err { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	if err := f.Close(); nil != err { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var versionTpl = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. | ||||||
|  | package {{ .Package }} | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	{{ if .Commit -}} | ||||||
|  | 	commit = "{{ .Commit }}" | ||||||
|  | 	{{ end -}} | ||||||
|  | 	{{ if .Version -}} | ||||||
|  | 	version = "{{ .Version }}" | ||||||
|  | 	{{ end -}} | ||||||
|  | 	date = "{{ .Timestamp }}" | ||||||
|  | } | ||||||
|  | `)) | ||||||
							
								
								
									
										3
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | module git.rootprojects.org/root/go-gitver/v2 | ||||||
|  | 
 | ||||||
|  | go 1.12 | ||||||
							
								
								
									
										9
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/version.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/git.rootprojects.org/root/go-gitver/v2/version.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | // use recently generated version info as a fallback | ||||||
|  | // for when git isn't present (i.e. go run <url>) | ||||||
|  | func init() { | ||||||
|  | 	commit = "37c1fd4b5694fd62c9f0d6ad1df47d938accbeec" | ||||||
|  | 	version = "2.0.0-pre1-dirty" | ||||||
|  | 	date = "2020-10-10T16:05:59-06:00" | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								vendor/git.rootprojects.org/root/keypairs/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vendor/git.rootprojects.org/root/keypairs/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | /keypairs | ||||||
|  | /dist/ | ||||||
|  | 
 | ||||||
|  | .DS_Store | ||||||
|  | .*.sw* | ||||||
							
								
								
									
										1
									
								
								vendor/git.rootprojects.org/root/keypairs/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/git.rootprojects.org/root/keypairs/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | AJ ONeal <aj@therootcompany.com> (https://therootcompany.com) | ||||||
							
								
								
									
										21
									
								
								vendor/git.rootprojects.org/root/keypairs/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/git.rootprojects.org/root/keypairs/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | The MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2018-2019 Big Squid, Inc | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included in all | ||||||
|  | copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | SOFTWARE. | ||||||
							
								
								
									
										63
									
								
								vendor/git.rootprojects.org/root/keypairs/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								vendor/git.rootprojects.org/root/keypairs/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | # [keypairs](https://git.rootprojects.org/root/keypairs) | ||||||
|  | 
 | ||||||
|  | JSON Web Key (JWK) support and type safety lightly placed over top of Go's `crypto/ecdsa` and `crypto/rsa` | ||||||
|  | 
 | ||||||
|  | Useful for JWT, JOSE, etc. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | key, err := keypairs.ParsePrivateKey(bytesForJWKOrPEMOrDER) | ||||||
|  | 
 | ||||||
|  | pub, err := keypairs.ParsePublicKey(bytesForJWKOrPEMOrDER) | ||||||
|  | 
 | ||||||
|  | jwk, err := keypairs.MarshalJWKPublicKey(pub, time.Now().Add(2 * time.Day)) | ||||||
|  | 
 | ||||||
|  | kid, err := keypairs.ThumbprintPublicKey(pub) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # GoDoc API Documentation | ||||||
|  | 
 | ||||||
|  | See <https://pkg.go.dev/git.rootprojects.org/root/keypairs> | ||||||
|  | 
 | ||||||
|  | # Philosophy | ||||||
|  | 
 | ||||||
|  | Go's standard library is great. | ||||||
|  | 
 | ||||||
|  | Go has _excellent_ crytography support and provides wonderful | ||||||
|  | primitives for dealing with them. | ||||||
|  | 
 | ||||||
|  | I prefer to stay as close to Go's `crypto` package as possible, | ||||||
|  | just adding a light touch for JWT support and type safety. | ||||||
|  | 
 | ||||||
|  | # Type Safety | ||||||
|  | 
 | ||||||
|  | `crypto.PublicKey` is a "marker interface", meaning that it is **not typesafe**! | ||||||
|  | 
 | ||||||
|  | `go-keypairs` defines `type keypairs.PrivateKey interface { Public() crypto.PublicKey }`, | ||||||
|  | which is implemented by `crypto/rsa` and `crypto/ecdsa` | ||||||
|  | (but not `crypto/dsa`, which we really don't care that much about). | ||||||
|  | 
 | ||||||
|  | Go1.15 will add `[PublicKey.Equal(crypto.PublicKey)](https://github.com/golang/go/issues/21704)`, | ||||||
|  | which will make it possible to remove the additional wrapper over `PublicKey` | ||||||
|  | and use an interface instead. | ||||||
|  | 
 | ||||||
|  | Since there are no common methods between `rsa.PublicKey` and `ecdsa.PublicKey`, | ||||||
|  | go-keypairs lightly wraps each to implement `Thumbprint() string` (part of the JOSE/JWK spec). | ||||||
|  | 
 | ||||||
|  | ## JSON Web Key (JWK) as a "codec" | ||||||
|  | 
 | ||||||
|  | Although there are many, many ways that JWKs could be interpreted | ||||||
|  | (possibly why they haven't made it into the standard library), `go-keypairs` | ||||||
|  | follows the basic pattern of `encoding/x509` to `Parse` and `Marshal` | ||||||
|  | only the most basic and most meaningful parts of a key. | ||||||
|  | 
 | ||||||
|  | I highly recommend that you use `Thumbprint()` for `KeyID` you also | ||||||
|  | get the benefit of not losing information when encoding and decoding | ||||||
|  | between the ASN.1, x509, PEM, and JWK formats. | ||||||
|  | 
 | ||||||
|  | # LICENSE | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2020-present AJ ONeal \ | ||||||
|  | Copyright (c) 2018-2019 Big Squid, Inc. | ||||||
|  | 
 | ||||||
|  | This work is licensed under the terms of the MIT license. \ | ||||||
|  | For a copy, see <https://opensource.org/licenses/MIT>. | ||||||
							
								
								
									
										19
									
								
								vendor/git.rootprojects.org/root/keypairs/cli_test.sh
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/git.rootprojects.org/root/keypairs/cli_test.sh
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -u | ||||||
|  | 
 | ||||||
|  | go build -mod=vendor cmd/keypairs/*.go | ||||||
|  | ./keypairs gen > testkey.jwk.json 2> testpub.jwk.json | ||||||
|  | 
 | ||||||
|  | ./keypairs sign --exp 1h ./testkey.jwk.json '{"foo":"bar"}' > testjwt.txt 2> testjws.json | ||||||
|  | 
 | ||||||
|  | echo "" | ||||||
|  | echo "Should pass:" | ||||||
|  | ./keypairs verify ./testpub.jwk.json testjwt.txt > /dev/null | ||||||
|  | ./keypairs verify ./testpub.jwk.json "$(cat testjwt.txt)" > /dev/null | ||||||
|  | ./keypairs verify ./testpub.jwk.json testjws.json > /dev/null | ||||||
|  | ./keypairs verify ./testpub.jwk.json "$(cat testjws.json)" > /dev/null | ||||||
|  | 
 | ||||||
|  | echo "" | ||||||
|  | echo "Should fail:" | ||||||
|  | ./keypairs sign --exp -1m ./testkey.jwk.json '{"bar":"foo"}' > errjwt.txt 2> errjws.json | ||||||
|  | ./keypairs verify ./testpub.jwk.json errjwt.txt > /dev/null | ||||||
							
								
								
									
										365
									
								
								vendor/git.rootprojects.org/root/keypairs/cmd/keypairs/keypairs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								vendor/git.rootprojects.org/root/keypairs/cmd/keypairs/keypairs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,365 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.rootprojects.org/root/keypairs" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	name    = "keypairs" | ||||||
|  | 	version = "0.0.0" | ||||||
|  | 	date    = "0001-01-01T00:00:00Z" | ||||||
|  | 	commit  = "0000000" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func usage() { | ||||||
|  | 	ver() | ||||||
|  | 	fmt.Println("Usage") | ||||||
|  | 	fmt.Printf(" %s <command> [flags] args...\n", name) | ||||||
|  | 	fmt.Println("") | ||||||
|  | 	fmt.Printf("See usage: %s help <command>\n", name) | ||||||
|  | 	fmt.Println("") | ||||||
|  | 	fmt.Println("Commands:") | ||||||
|  | 	fmt.Println("    version") | ||||||
|  | 	fmt.Println("    gen") | ||||||
|  | 	fmt.Println("    sign") | ||||||
|  | 	fmt.Println("    verify") | ||||||
|  | 	fmt.Println("") | ||||||
|  | 	fmt.Println("Examples:") | ||||||
|  | 	fmt.Println("    keypairs gen -o key.jwk.json [--pub <public-key>]") | ||||||
|  | 	fmt.Println("") | ||||||
|  | 	fmt.Println("    keypairs sign --exp 15m key.jwk.json payload.json") | ||||||
|  | 	fmt.Println("    keypairs sign --exp 15m key.jwk.json '{ \"sub\": \"xxxx\" }'") | ||||||
|  | 	fmt.Println("") | ||||||
|  | 	fmt.Println("    keypairs verify ./pub.jwk.json 'xxxx.yyyy.zzzz'") | ||||||
|  | 	// TODO fmt.Println("    keypairs verify --issuer https://example.com '{ \"sub\": \"xxxx\" }'") | ||||||
|  | 	fmt.Println("") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ver() { | ||||||
|  | 	fmt.Printf("%s v%s %s (%s)\n", name, version, commit[:7], date) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	args := os.Args[:] | ||||||
|  | 
 | ||||||
|  | 	if "help" == args[1] { | ||||||
|  | 		// top-level help | ||||||
|  | 		if 2 == len(args) { | ||||||
|  | 			usage() | ||||||
|  | 			os.Exit(0) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		// move help to subcommand argument | ||||||
|  | 		self := args[0] | ||||||
|  | 		args = append([]string{self}, args[2:]...) | ||||||
|  | 		args = append(args, "--help") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch args[1] { | ||||||
|  | 	case "version": | ||||||
|  | 		ver() | ||||||
|  | 		os.Exit(0) | ||||||
|  | 		return | ||||||
|  | 	case "gen": | ||||||
|  | 		gen(args[2:]) | ||||||
|  | 	case "sign": | ||||||
|  | 		sign(args[2:]) | ||||||
|  | 	case "verify": | ||||||
|  | 		verify(args[2:]) | ||||||
|  | 	default: | ||||||
|  | 		usage() | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func gen(args []string) { | ||||||
|  | 	var keyname string | ||||||
|  | 	var pubname string | ||||||
|  | 	flags := flag.NewFlagSet("gen", flag.ExitOnError) | ||||||
|  | 	flags.StringVar(&keyname, "o", "", "private key file (ex: key.jwk.json or key.pem)") | ||||||
|  | 	flags.StringVar(&pubname, "pub", "", "public key file (ex: pub.jwk.json or pub.pem)") | ||||||
|  | 	flags.Parse(args) | ||||||
|  | 
 | ||||||
|  | 	key := keypairs.NewDefaultPrivateKey() | ||||||
|  | 	marshalPriv(key, keyname) | ||||||
|  | 	pub := keypairs.NewPublicKey(key.Public()) | ||||||
|  | 	marshalPub(pub, pubname) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func sign(args []string) { | ||||||
|  | 	var exp time.Duration | ||||||
|  | 	flags := flag.NewFlagSet("sign", flag.ExitOnError) | ||||||
|  | 	flags.DurationVar(&exp, "exp", 0, "duration until token expires (Default 15m)") | ||||||
|  | 	flags.Parse(args) | ||||||
|  | 	if len(flags.Args()) <= 1 { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "Usage: keypairs sign --exp 1h <private PEM or JWK> ./payload.json\n") | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	keyname := flags.Args()[0] | ||||||
|  | 	payload := flags.Args()[1] | ||||||
|  | 
 | ||||||
|  | 	key, err := readKey(keyname) | ||||||
|  | 	if nil != err { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "%v\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if "" == payload { | ||||||
|  | 		// TODO should this be null? I forget | ||||||
|  | 		payload = "{}" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	b, err := ioutil.ReadFile(payload) | ||||||
|  | 	claims := map[string]interface{}{} | ||||||
|  | 	if nil != err { | ||||||
|  | 		var err2 error | ||||||
|  | 		err2 = json.Unmarshal([]byte(payload), &claims) | ||||||
|  | 		if nil != err2 { | ||||||
|  | 			fmt.Fprintf(os.Stderr, | ||||||
|  | 				"could not read payload as file (or parse as string) %q: %s\n", payload, err) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if 0 == len(claims) { | ||||||
|  | 		var err3 error | ||||||
|  | 		err3 = json.Unmarshal(b, &claims) | ||||||
|  | 		if nil != err3 { | ||||||
|  | 			fmt.Fprintf(os.Stderr, | ||||||
|  | 				"could not parse palyoad from file %q: %s\n", payload, err3) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if 0 != exp { | ||||||
|  | 		claims["exp"] = exp.Seconds() | ||||||
|  | 	} | ||||||
|  | 	if _, ok := claims["exp"]; !ok { | ||||||
|  | 		claims["exp"] = (15 * time.Minute).Seconds() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	jws, err := keypairs.SignClaims(key, nil, claims) | ||||||
|  | 	if nil != err { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "could not sign claims: %v\n%#v\n", err, claims) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	b, _ = json.Marshal(&jws) | ||||||
|  | 	fmt.Fprintf(os.Stderr, "%s\n", indentJSON(b)) | ||||||
|  | 	fmt.Fprintf(os.Stdout, "%s\n", keypairs.JWSToJWT(jws)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func verify(args []string) { | ||||||
|  | 	flags := flag.NewFlagSet("verify", flag.ExitOnError) | ||||||
|  | 	flags.Usage = func() { | ||||||
|  | 		fmt.Println("Usage: keypairs verify <public key> <jwt-or-jwt>") | ||||||
|  | 		fmt.Println("") | ||||||
|  | 		fmt.Println("    <public key>: a File or String of an EC or RSA key in JWK or PEM format") | ||||||
|  | 		fmt.Println("    <jwt-or-jws>: a JWT or JWS File or String, if JWS the payload must be Base64") | ||||||
|  | 		fmt.Println("") | ||||||
|  | 	} | ||||||
|  | 	flags.Parse(args) | ||||||
|  | 	if len(flags.Args()) <= 1 { | ||||||
|  | 		flags.Usage() | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pubname := flags.Args()[0] | ||||||
|  | 	payload := flags.Args()[1] | ||||||
|  | 
 | ||||||
|  | 	pub, err := readPub(pubname) | ||||||
|  | 	if nil != err { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "%v\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	jws, err := readJWS(payload) | ||||||
|  | 	if nil != err { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "%v\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	b, _ := json.Marshal(&jws) | ||||||
|  | 	fmt.Fprintf(os.Stdout, "%s\n", indentJSON(b)) | ||||||
|  | 
 | ||||||
|  | 	errs := keypairs.VerifyClaims(pub, jws) | ||||||
|  | 	if nil != errs { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "error:\n") | ||||||
|  | 		for _, err := range errs { | ||||||
|  | 			fmt.Fprintf(os.Stderr, "\t%v\n", err) | ||||||
|  | 		} | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	fmt.Fprintf(os.Stderr, "Signature is Valid\n") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func readKey(keyname string) (keypairs.PrivateKey, error) { | ||||||
|  | 	var key keypairs.PrivateKey = nil | ||||||
|  | 
 | ||||||
|  | 	// Read as file | ||||||
|  | 	b, err := ioutil.ReadFile(keyname) | ||||||
|  | 	if nil != err { | ||||||
|  | 		// Tis not a file! Perhaps a string? | ||||||
|  | 		var err2 error | ||||||
|  | 		key, err2 = keypairs.ParsePrivateKey([]byte(keyname)) | ||||||
|  | 		if nil != err2 { | ||||||
|  | 			// Neither a valid string. Blast! | ||||||
|  | 			return nil, fmt.Errorf( | ||||||
|  | 				"could not read private key as file (or parse as string) %q:\n%s", | ||||||
|  | 				keyname, err2, | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if nil == key { | ||||||
|  | 		var err3 error | ||||||
|  | 		key, err3 = keypairs.ParsePrivateKey(b) | ||||||
|  | 		if nil != err3 { | ||||||
|  | 			return nil, fmt.Errorf( | ||||||
|  | 				"could not parse private key from file %q:\n%s", | ||||||
|  | 				keyname, err3, | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return key, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func readPub(pubname string) (keypairs.PublicKey, error) { | ||||||
|  | 	var pub keypairs.PublicKey = nil | ||||||
|  | 
 | ||||||
|  | 	// Read as file | ||||||
|  | 	b, err := ioutil.ReadFile(pubname) | ||||||
|  | 	if nil != err { | ||||||
|  | 		// No file? Try as string! | ||||||
|  | 		var err2 error | ||||||
|  | 		pub, err2 = keypairs.ParsePublicKey([]byte(pubname)) | ||||||
|  | 		if nil != err2 { | ||||||
|  | 			return nil, fmt.Errorf( | ||||||
|  | 				"could not read public key as file (or parse as string) %q:\n%w", | ||||||
|  | 				pubname, err, | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Oh, it was a file. | ||||||
|  | 	if nil == pub { | ||||||
|  | 		var err3 error | ||||||
|  | 		pub, err3 = keypairs.ParsePublicKey(b) | ||||||
|  | 		if nil != err3 { | ||||||
|  | 			return nil, fmt.Errorf( | ||||||
|  | 				"could not parse public key from file %q:\n%w", | ||||||
|  | 				pubname, err3, | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return pub, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func readJWS(payload string) (*keypairs.JWS, error) { | ||||||
|  | 	// Is it a file? | ||||||
|  | 	b, err := ioutil.ReadFile(payload) | ||||||
|  | 	if nil != err { | ||||||
|  | 		// Or a JWS or JWS String!? | ||||||
|  | 		b = []byte(payload) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Either way, we have some bytes now | ||||||
|  | 	jws := &keypairs.JWS{} | ||||||
|  | 	jwt := string(b) | ||||||
|  | 	jwsb := []byte(jwt) | ||||||
|  | 	if !strings.Contains(jwt, " \t\n{}[]") { | ||||||
|  | 		jws = keypairs.JWTToJWS(string(b)) | ||||||
|  | 		if nil != jws { | ||||||
|  | 			b, _ = json.Marshal(jws) | ||||||
|  | 			jwsb = (b) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// And now we have a string that may be a JWS | ||||||
|  | 	if err := json.Unmarshal(jwsb, &jws); nil != err { | ||||||
|  | 		// Nope, it's not | ||||||
|  | 		return nil, fmt.Errorf( | ||||||
|  | 			"could not read signed payload from file or string as JWT or JWS %q:\n%w", | ||||||
|  | 			payload, err, | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := jws.DecodeComponents(); nil != err { | ||||||
|  | 		// bah! so close! | ||||||
|  | 		return nil, fmt.Errorf( | ||||||
|  | 			"could not decode the JWS Header and Claims components: %w\n%s", | ||||||
|  | 			err, string(jwsb), | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return jws, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func marshalPriv(key keypairs.PrivateKey, keyname string) { | ||||||
|  | 	if "" == keyname { | ||||||
|  | 		b := indentJSON(keypairs.MarshalJWKPrivateKey(key)) | ||||||
|  | 
 | ||||||
|  | 		fmt.Fprintf(os.Stdout, string(b)+"\n") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var b []byte | ||||||
|  | 	if strings.HasSuffix(keyname, ".json") { | ||||||
|  | 		b = indentJSON(keypairs.MarshalJWKPrivateKey(key)) | ||||||
|  | 	} else if strings.HasSuffix(keyname, ".pem") { | ||||||
|  | 		b, _ = keypairs.MarshalPEMPrivateKey(key) | ||||||
|  | 	} else if strings.HasSuffix(keyname, ".der") { | ||||||
|  | 		b, _ = keypairs.MarshalDERPrivateKey(key) | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Fprintf(os.Stderr, "private key extension should be .jwk.json, .pem, or .der") | ||||||
|  | 		os.Exit(1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ioutil.WriteFile(keyname, b, 0600) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func marshalPub(pub keypairs.PublicKey, pubname string) { | ||||||
|  | 	var b []byte | ||||||
|  | 	if "" == pubname { | ||||||
|  | 		b = indentJSON(keypairs.MarshalJWKPublicKey(pub)) | ||||||
|  | 
 | ||||||
|  | 		fmt.Fprintf(os.Stderr, string(b)+"\n") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if strings.HasSuffix(pubname, ".json") { | ||||||
|  | 		b = indentJSON(keypairs.MarshalJWKPublicKey(pub)) | ||||||
|  | 	} else if strings.HasSuffix(pubname, ".pem") { | ||||||
|  | 		b, _ = keypairs.MarshalPEMPublicKey(pub) | ||||||
|  | 	} else if strings.HasSuffix(pubname, ".der") { | ||||||
|  | 		b, _ = keypairs.MarshalDERPublicKey(pub) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ioutil.WriteFile(pubname, b, 0644) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func indentJSON(b []byte) []byte { | ||||||
|  | 	m := map[string]interface{}{} | ||||||
|  | 	_ = json.Unmarshal(b, &m) | ||||||
|  | 	b, _ = json.MarshalIndent(&m, "", "  ") | ||||||
|  | 	return append(b, '\n') | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								vendor/git.rootprojects.org/root/keypairs/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								vendor/git.rootprojects.org/root/keypairs/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | /* | ||||||
|  | Package keypairs complements Go's standard keypair-related packages | ||||||
|  | (encoding/pem, crypto/x509, crypto/rsa, crypto/ecdsa, crypto/elliptic) | ||||||
|  | with JWK encoding support and typesafe PrivateKey and PublicKey interfaces. | ||||||
|  | 
 | ||||||
|  | Basics | ||||||
|  | 
 | ||||||
|  | 	key, err := keypairs.ParsePrivateKey(bytesForJWKOrPEMOrDER) | ||||||
|  | 
 | ||||||
|  | 	pub, err := keypairs.ParsePublicKey(bytesForJWKOrPEMOrDER) | ||||||
|  | 
 | ||||||
|  | 	jwk, err := keypairs.MarshalJWKPublicKey(pub, time.Now().Add(2 * time.Day)) | ||||||
|  | 
 | ||||||
|  | 	kid, err := keypairs.ThumbprintPublicKey(pub) | ||||||
|  | 
 | ||||||
|  | Convenience functions are available which will fetch keys | ||||||
|  | (or retrieve them from cache) via OIDC, .well-known/jwks.json, and direct urls. | ||||||
|  | All keys are cached by Thumbprint, as well as kid(@issuer), if available. | ||||||
|  | 
 | ||||||
|  | 	import "git.rootprojects.org/root/keypairs/keyfetch" | ||||||
|  | 
 | ||||||
|  | 	pubs, err := keyfetch.OIDCJWKs("https://example.com/") | ||||||
|  | 	pubs, err := keyfetch.OIDCJWK(ThumbOrKeyID, "https://example.com/") | ||||||
|  | 
 | ||||||
|  | 	pubs, err := keyfetch.WellKnownJWKs("https://example.com/") | ||||||
|  | 	pubs, err := keyfetch.WellKnownJWK(ThumbOrKeyID, "https://example.com/") | ||||||
|  | 
 | ||||||
|  | 	pubs, err := keyfetch.JWKs("https://example.com/path/to/jwks/") | ||||||
|  | 	pubs, err := keyfetch.JWK(ThumbOrKeyID, "https://example.com/path/to/jwks/") | ||||||
|  | 
 | ||||||
|  | 	// From URL | ||||||
|  | 	pub, err := keyfetch.Fetch("https://example.com/jwk.json") | ||||||
|  | 
 | ||||||
|  | 	// From Cache only | ||||||
|  | 	pub := keyfetch.Get(thumbprint, "https://example.com/jwk.json") | ||||||
|  | 
 | ||||||
|  | A non-caching version with the same capabilities is also available. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | package keypairs | ||||||
							
								
								
									
										69
									
								
								vendor/git.rootprojects.org/root/keypairs/generate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								vendor/git.rootprojects.org/root/keypairs/generate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | |||||||
|  | package keypairs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/elliptic" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"io" | ||||||
|  | 	mathrand "math/rand" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var randReader io.Reader = rand.Reader | ||||||
|  | var allowMocking = false | ||||||
|  | 
 | ||||||
|  | // KeyOptions are the things that we may need to know about a request to fulfill it properly | ||||||
|  | type keyOptions struct { | ||||||
|  | 	//Key     string `json:"key"` | ||||||
|  | 	KeyType  string `json:"kty"` | ||||||
|  | 	mockSeed int64  //`json:"-"` | ||||||
|  | 	//SeedStr string `json:"seed"` | ||||||
|  | 	//Claims  Object `json:"claims"` | ||||||
|  | 	//Header  Object `json:"header"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (o *keyOptions) nextReader() io.Reader { | ||||||
|  | 	if allowMocking { | ||||||
|  | 		return o.maybeMockReader() | ||||||
|  | 	} | ||||||
|  | 	return randReader | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewDefaultPrivateKey generates a key with reasonable strength. | ||||||
|  | // Today that means a 256-bit equivalent - either RSA 2048 or EC P-256. | ||||||
|  | func NewDefaultPrivateKey() PrivateKey { | ||||||
|  | 	// insecure random is okay here, | ||||||
|  | 	// it's just used for a coin toss | ||||||
|  | 	mathrand.Seed(time.Now().UnixNano()) | ||||||
|  | 	coin := mathrand.Int() | ||||||
|  | 
 | ||||||
|  | 	// the idea here is that we want to make | ||||||
|  | 	// it dead simple to support RSA and EC | ||||||
|  | 	// so it shouldn't matter which is used | ||||||
|  | 	if 0 == coin%2 { | ||||||
|  | 		return newPrivateKey(&keyOptions{ | ||||||
|  | 			KeyType: "RSA", | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return newPrivateKey(&keyOptions{ | ||||||
|  | 		KeyType: "EC", | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newPrivateKey generates a 256-bit entropy RSA or ECDSA private key | ||||||
|  | func newPrivateKey(opts *keyOptions) PrivateKey { | ||||||
|  | 	var privkey PrivateKey | ||||||
|  | 
 | ||||||
|  | 	if "RSA" == opts.KeyType { | ||||||
|  | 		keylen := 2048 | ||||||
|  | 		privkey, _ = rsa.GenerateKey(opts.nextReader(), keylen) | ||||||
|  | 		if allowMocking { | ||||||
|  | 			privkey = maybeDerandomizeMockKey(privkey, keylen, opts) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// TODO: EC keys may also suffer the same random problems in the future | ||||||
|  | 		privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.nextReader()) | ||||||
|  | 	} | ||||||
|  | 	return privkey | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								vendor/git.rootprojects.org/root/keypairs/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/git.rootprojects.org/root/keypairs/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | module git.rootprojects.org/root/keypairs | ||||||
|  | 
 | ||||||
|  | go 1.12 | ||||||
							
								
								
									
										69
									
								
								vendor/git.rootprojects.org/root/keypairs/jwk.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								vendor/git.rootprojects.org/root/keypairs/jwk.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | |||||||
|  | package keypairs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // JWK abstracts EC and RSA keys | ||||||
|  | type JWK interface { | ||||||
|  | 	marshalJWK() ([]byte, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ECJWK is the EC variant | ||||||
|  | type ECJWK struct { | ||||||
|  | 	KeyID string   `json:"kid,omitempty"` | ||||||
|  | 	Curve string   `json:"crv"` | ||||||
|  | 	X     string   `json:"x"` | ||||||
|  | 	Y     string   `json:"y"` | ||||||
|  | 	Use   []string `json:"use,omitempty"` | ||||||
|  | 	Seed  string   `json:"_seed,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *ECJWK) marshalJWK() ([]byte, error) { | ||||||
|  | 	return []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, k.Curve, k.X, k.Y)), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RSAJWK is the RSA variant | ||||||
|  | type RSAJWK struct { | ||||||
|  | 	KeyID string   `json:"kid,omitempty"` | ||||||
|  | 	Exp   string   `json:"e"` | ||||||
|  | 	N     string   `json:"n"` | ||||||
|  | 	Use   []string `json:"use,omitempty"` | ||||||
|  | 	Seed  string   `json:"_seed,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *RSAJWK) marshalJWK() ([]byte, error) { | ||||||
|  | 	return []byte(fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, k.Exp, k.N)), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | // ToPublicJWK exposes only the public parts | ||||||
|  | func ToPublicJWK(pubkey PublicKey) JWK { | ||||||
|  | 	switch k := pubkey.Key().(type) { | ||||||
|  | 	case *ecdsa.PublicKey: | ||||||
|  | 		return ECToPublicJWK(k) | ||||||
|  | 	case *rsa.PublicKey: | ||||||
|  | 		return RSAToPublicJWK(k) | ||||||
|  | 	default: | ||||||
|  | 		panic(errors.New("impossible key type")) | ||||||
|  | 		//return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ECToPublicJWK will output the most minimal version of an EC JWK (no key id, no "use" flag, nada) | ||||||
|  | func ECToPublicJWK(k *ecdsa.PublicKey) *ECJWK { | ||||||
|  | 	return &ECJWK{ | ||||||
|  | 		Curve: k.Curve.Params().Name, | ||||||
|  | 		X:     base64.RawURLEncoding.EncodeToString(k.X.Bytes()), | ||||||
|  | 		Y:     base64.RawURLEncoding.EncodeToString(k.Y.Bytes()), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RSAToPublicJWK will output the most minimal version of an RSA JWK (no key id, no "use" flag, nada) | ||||||
|  | func RSAToPublicJWK(p *rsa.PublicKey) *RSAJWK { | ||||||
|  | 	return &RSAJWK{ | ||||||
|  | 		Exp: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes()), | ||||||
|  | 		N:   base64.RawURLEncoding.EncodeToString(p.N.Bytes()), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | */ | ||||||
							
								
								
									
										63
									
								
								vendor/git.rootprojects.org/root/keypairs/jws.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								vendor/git.rootprojects.org/root/keypairs/jws.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | package keypairs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // JWS is a parsed JWT, representation as signable/verifiable and human-readable parts | ||||||
|  | type JWS struct { | ||||||
|  | 	Header    Object `json:"header"`    // JSON | ||||||
|  | 	Claims    Object `json:"claims"`    // JSON | ||||||
|  | 	Protected string `json:"protected"` // base64 | ||||||
|  | 	Payload   string `json:"payload"`   // base64 | ||||||
|  | 	Signature string `json:"signature"` // base64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // JWSToJWT joins JWS parts into a JWT as {ProtectedHeader}.{SerializedPayload}.{Signature}. | ||||||
|  | func JWSToJWT(jwt *JWS) string { | ||||||
|  | 	return fmt.Sprintf( | ||||||
|  | 		"%s.%s.%s", | ||||||
|  | 		jwt.Protected, | ||||||
|  | 		jwt.Payload, | ||||||
|  | 		jwt.Signature, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // JWTToJWS splits the JWT into its JWS segments | ||||||
|  | func JWTToJWS(jwt string) (jws *JWS) { | ||||||
|  | 	jwt = strings.TrimSpace(jwt) | ||||||
|  | 	parts := strings.Split(jwt, ".") | ||||||
|  | 	if 3 != len(parts) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return &JWS{ | ||||||
|  | 		Protected: parts[0], | ||||||
|  | 		Payload:   parts[1], | ||||||
|  | 		Signature: parts[2], | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeComponents decodes JWS Header and Claims | ||||||
|  | func (jws *JWS) DecodeComponents() error { | ||||||
|  | 	protected, err := base64.RawURLEncoding.DecodeString(jws.Protected) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return errors.New("invalid JWS header base64Url encoding") | ||||||
|  | 	} | ||||||
|  | 	if err := json.Unmarshal([]byte(protected), &jws.Header); nil != err { | ||||||
|  | 		return errors.New("invalid JWS header") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	payload, err := base64.RawURLEncoding.DecodeString(jws.Payload) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return errors.New("invalid JWS payload base64Url encoding") | ||||||
|  | 	} | ||||||
|  | 	if err := json.Unmarshal([]byte(payload), &jws.Claims); nil != err { | ||||||
|  | 		return errors.New("invalid JWS claims") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										516
									
								
								vendor/git.rootprojects.org/root/keypairs/keyfetch/fetch.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										516
									
								
								vendor/git.rootprojects.org/root/keypairs/keyfetch/fetch.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,516 @@ | |||||||
|  | // Package keyfetch retrieve and cache PublicKeys | ||||||
|  | // from OIDC (https://example.com/.well-known/openid-configuration) | ||||||
|  | // and Auth0 (https://example.com/.well-known/jwks.json) | ||||||
|  | // JWKs URLs and expires them when `exp` is reached | ||||||
|  | // (or a default expiry if the key does not provide one). | ||||||
|  | // It uses the keypairs package to Unmarshal the JWKs into their | ||||||
|  | // native types (with a very thin shim to provide the type safety | ||||||
|  | // that Go's crypto.PublicKey and crypto.PrivateKey interfaces lack). | ||||||
|  | package keyfetch | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.rootprojects.org/root/keypairs" | ||||||
|  | 	"git.rootprojects.org/root/keypairs/keyfetch/uncached" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // TODO should be ErrInvalidJWKURL | ||||||
|  | 
 | ||||||
|  | // EInvalidJWKURL means that the url did not provide JWKs | ||||||
|  | var EInvalidJWKURL = errors.New("url does not lead to valid JWKs") | ||||||
|  | 
 | ||||||
|  | // KeyCache is an in-memory key cache | ||||||
|  | var KeyCache = map[string]CachableKey{} | ||||||
|  | 
 | ||||||
|  | // KeyCacheMux is used to guard the in-memory cache | ||||||
|  | var KeyCacheMux = sync.Mutex{} | ||||||
|  | 
 | ||||||
|  | // ErrInsecureDomain means that plain http was used where https was expected | ||||||
|  | var ErrInsecureDomain = errors.New("Whitelists should only allow secure URLs (i.e. https://). To allow unsecured private networking (i.e. Docker) pass PrivateWhitelist as a list of private URLs") | ||||||
|  | 
 | ||||||
|  | // TODO Cacheable key (shouldn't this be private)? | ||||||
|  | 
 | ||||||
|  | // CachableKey represents | ||||||
|  | type CachableKey struct { | ||||||
|  | 	Key    keypairs.PublicKey | ||||||
|  | 	Expiry time.Time | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // maybe TODO use this poor-man's enum to allow kids thumbs to be accepted by the same method? | ||||||
|  | /* | ||||||
|  | type KeyID string | ||||||
|  | 
 | ||||||
|  | func (kid KeyID) ID() string { | ||||||
|  | 	return string(kid) | ||||||
|  | } | ||||||
|  | func (kid KeyID) isID() {} | ||||||
|  | 
 | ||||||
|  | type Thumbprint string | ||||||
|  | 
 | ||||||
|  | func (thumb Thumbprint) ID() string { | ||||||
|  | 	return string(thumb) | ||||||
|  | } | ||||||
|  | func (thumb Thumbprint) isID() {} | ||||||
|  | 
 | ||||||
|  | type ID interface { | ||||||
|  | 	ID() string | ||||||
|  | 	isID() | ||||||
|  | } | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | // StaleTime defines when public keys should be renewed (15 minutes by default) | ||||||
|  | var StaleTime = 15 * time.Minute | ||||||
|  | 
 | ||||||
|  | // DefaultKeyDuration defines how long a key should be considered fresh (48 hours by default) | ||||||
|  | var DefaultKeyDuration = 48 * time.Hour | ||||||
|  | 
 | ||||||
|  | // MinimumKeyDuration defines the minimum time that a key will be cached (1 hour by default) | ||||||
|  | var MinimumKeyDuration = time.Hour | ||||||
|  | 
 | ||||||
|  | // MaximumKeyDuration defines the maximum time that a key will be cached (72 hours by default) | ||||||
|  | var MaximumKeyDuration = 72 * time.Hour | ||||||
|  | 
 | ||||||
|  | // PublicKeysMap is a newtype for a map of keypairs.PublicKey | ||||||
|  | type PublicKeysMap map[string]keypairs.PublicKey | ||||||
|  | 
 | ||||||
|  | // OIDCJWKs fetches baseURL + ".well-known/openid-configuration" and then fetches and returns the Public Keys. | ||||||
|  | func OIDCJWKs(baseURL string) (PublicKeysMap, error) { | ||||||
|  | 	maps, keys, err := uncached.OIDCJWKs(baseURL) | ||||||
|  | 
 | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	cacheKeys(maps, keys, baseURL) | ||||||
|  | 	return keys, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OIDCJWK fetches baseURL + ".well-known/openid-configuration" and then returns the key matching kid (or thumbprint) | ||||||
|  | func OIDCJWK(kidOrThumb, iss string) (keypairs.PublicKey, error) { | ||||||
|  | 	return immediateOneOrFetch(kidOrThumb, iss, uncached.OIDCJWKs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WellKnownJWKs fetches baseURL + ".well-known/jwks.json" and caches and returns the keys | ||||||
|  | func WellKnownJWKs(kidOrThumb, iss string) (PublicKeysMap, error) { | ||||||
|  | 	maps, keys, err := uncached.WellKnownJWKs(iss) | ||||||
|  | 
 | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	cacheKeys(maps, keys, iss) | ||||||
|  | 	return keys, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WellKnownJWK fetches baseURL + ".well-known/jwks.json" and returns the key matching kid (or thumbprint) | ||||||
|  | func WellKnownJWK(kidOrThumb, iss string) (keypairs.PublicKey, error) { | ||||||
|  | 	return immediateOneOrFetch(kidOrThumb, iss, uncached.WellKnownJWKs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // JWKs returns a map of keys identified by their thumbprint | ||||||
|  | // (since kid may or may not be present) | ||||||
|  | func JWKs(jwksurl string) (PublicKeysMap, error) { | ||||||
|  | 	maps, keys, err := uncached.JWKs(jwksurl) | ||||||
|  | 
 | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	iss := strings.Replace(jwksurl, ".well-known/jwks.json", "", 1) | ||||||
|  | 	cacheKeys(maps, keys, iss) | ||||||
|  | 	return keys, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // JWK tries to return a key from cache, falling back to the /.well-known/jwks.json of the issuer | ||||||
|  | func JWK(kidOrThumb, iss string) (keypairs.PublicKey, error) { | ||||||
|  | 	return immediateOneOrFetch(kidOrThumb, iss, uncached.JWKs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PEM tries to return a key from cache, falling back to the specified PEM url | ||||||
|  | func PEM(url string) (keypairs.PublicKey, error) { | ||||||
|  | 	// url is kid in this case | ||||||
|  | 	return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { | ||||||
|  | 		m, key, err := uncached.PEM(url) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// put in a map, just for caching | ||||||
|  | 		maps := map[string]map[string]string{} | ||||||
|  | 		maps[key.Thumbprint()] = m | ||||||
|  | 		maps[url] = m | ||||||
|  | 
 | ||||||
|  | 		keys := map[string]keypairs.PublicKey{} | ||||||
|  | 		keys[key.Thumbprint()] = key | ||||||
|  | 		keys[url] = key | ||||||
|  | 
 | ||||||
|  | 		return maps, keys, nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Fetch returns a key from cache, falling back to an exact url as the "issuer" | ||||||
|  | func Fetch(url string) (keypairs.PublicKey, error) { | ||||||
|  | 	// url is kid in this case | ||||||
|  | 	return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { | ||||||
|  | 		m, key, err := uncached.Fetch(url) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// put in a map, just for caching | ||||||
|  | 		maps := map[string]map[string]string{} | ||||||
|  | 		maps[key.Thumbprint()] = m | ||||||
|  | 
 | ||||||
|  | 		keys := map[string]keypairs.PublicKey{} | ||||||
|  | 		keys[key.Thumbprint()] = key | ||||||
|  | 
 | ||||||
|  | 		return maps, keys, nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get retrieves a key from cache, or returns an error. | ||||||
|  | // The issuer string may be empty if using a thumbprint rather than a kid. | ||||||
|  | func Get(kidOrThumb, iss string) keypairs.PublicKey { | ||||||
|  | 	if pub := get(kidOrThumb, iss); nil != pub { | ||||||
|  | 		return pub.Key | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func get(kidOrThumb, iss string) *CachableKey { | ||||||
|  | 	iss = normalizeIssuer(iss) | ||||||
|  | 	KeyCacheMux.Lock() | ||||||
|  | 	defer KeyCacheMux.Unlock() | ||||||
|  | 
 | ||||||
|  | 	// we're safe to check the cache by kid alone | ||||||
|  | 	// by virtue that we never set it by kid alone | ||||||
|  | 	hit, ok := KeyCache[kidOrThumb] | ||||||
|  | 	if ok { | ||||||
|  | 		if now := time.Now(); hit.Expiry.Sub(now) > 0 { | ||||||
|  | 			// only return non-expired keys | ||||||
|  | 			return &hit | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	id := kidOrThumb + "@" + iss | ||||||
|  | 	hit, ok = KeyCache[id] | ||||||
|  | 	if ok { | ||||||
|  | 		if now := time.Now(); hit.Expiry.Sub(now) > 0 { | ||||||
|  | 			// only return non-expired keys | ||||||
|  | 			return &hit | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func immediateOneOrFetch(kidOrThumb, iss string, fetcher myfetcher) (keypairs.PublicKey, error) { | ||||||
|  | 	now := time.Now() | ||||||
|  | 	key := get(kidOrThumb, iss) | ||||||
|  | 
 | ||||||
|  | 	if nil == key { | ||||||
|  | 		return fetchAndSelect(kidOrThumb, iss, fetcher) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Fetch just a little before the key actually expires | ||||||
|  | 	if key.Expiry.Sub(now) <= StaleTime { | ||||||
|  | 		go fetchAndSelect(kidOrThumb, iss, fetcher) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return key.Key, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type myfetcher func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) | ||||||
|  | 
 | ||||||
|  | func fetchAndSelect(id, baseURL string, fetcher myfetcher) (keypairs.PublicKey, error) { | ||||||
|  | 	maps, keys, err := fetcher(baseURL) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	cacheKeys(maps, keys, baseURL) | ||||||
|  | 
 | ||||||
|  | 	for i := range keys { | ||||||
|  | 		key := keys[i] | ||||||
|  | 
 | ||||||
|  | 		if id == key.Thumbprint() { | ||||||
|  | 			return key, nil | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if id == key.KeyID() { | ||||||
|  | 			return key, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, fmt.Errorf("Key identified by '%s' was not found at %s", id, baseURL) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func cacheKeys(maps map[string]map[string]string, keys map[string]keypairs.PublicKey, issuer string) { | ||||||
|  | 	for i := range keys { | ||||||
|  | 		key := keys[i] | ||||||
|  | 		m := maps[i] | ||||||
|  | 		iss := issuer | ||||||
|  | 		if "" != m["iss"] { | ||||||
|  | 			iss = m["iss"] | ||||||
|  | 		} | ||||||
|  | 		iss = normalizeIssuer(iss) | ||||||
|  | 		cacheKey(m["kid"], iss, m["exp"], key) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func cacheKey(kid, iss, expstr string, pub keypairs.PublicKey) error { | ||||||
|  | 	var expiry time.Time | ||||||
|  | 	iss = normalizeIssuer(iss) | ||||||
|  | 
 | ||||||
|  | 	exp, _ := strconv.ParseInt(expstr, 10, 64) | ||||||
|  | 	if 0 == exp { | ||||||
|  | 		// use default | ||||||
|  | 		expiry = time.Now().Add(DefaultKeyDuration) | ||||||
|  | 	} else if exp < time.Now().Add(MinimumKeyDuration).Unix() || exp > time.Now().Add(MaximumKeyDuration).Unix() { | ||||||
|  | 		// use at least one hour | ||||||
|  | 		expiry = time.Now().Add(MinimumKeyDuration) | ||||||
|  | 	} else { | ||||||
|  | 		expiry = time.Unix(exp, 0) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	KeyCacheMux.Lock() | ||||||
|  | 	defer KeyCacheMux.Unlock() | ||||||
|  | 	// Put the key in the cache by both kid and thumbprint, and set the expiry | ||||||
|  | 	id := kid + "@" + iss | ||||||
|  | 	KeyCache[id] = CachableKey{ | ||||||
|  | 		Key:    pub, | ||||||
|  | 		Expiry: expiry, | ||||||
|  | 	} | ||||||
|  | 	// Since thumbprints are crypto secure, iss isn't needed | ||||||
|  | 	thumb := pub.Thumbprint() | ||||||
|  | 	KeyCache[thumb] = CachableKey{ | ||||||
|  | 		Key:    pub, | ||||||
|  | 		Expiry: expiry, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func clear() { | ||||||
|  | 	KeyCacheMux.Lock() | ||||||
|  | 	defer KeyCacheMux.Unlock() | ||||||
|  | 	KeyCache = map[string]CachableKey{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func normalizeIssuer(iss string) string { | ||||||
|  | 	return strings.TrimRight(iss, "/") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isTrustedIssuer(iss string, whitelist Whitelist, rs ...*http.Request) bool { | ||||||
|  | 	if "" == iss { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Normalize the http:// and https:// and parse | ||||||
|  | 	iss = strings.TrimRight(iss, "/") + "/" | ||||||
|  | 	if strings.HasPrefix(iss, "http://") { | ||||||
|  | 		// ignore | ||||||
|  | 	} else if strings.HasPrefix(iss, "//") { | ||||||
|  | 		return false // TODO | ||||||
|  | 	} else if !strings.HasPrefix(iss, "https://") { | ||||||
|  | 		iss = "https://" + iss | ||||||
|  | 	} | ||||||
|  | 	issURL, err := url.Parse(iss) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check that | ||||||
|  | 	// * schemes match (https: == https:) | ||||||
|  | 	// * paths match (/foo/ == /foo/, always with trailing slash added) | ||||||
|  | 	// * hostnames are compatible (a == b or "sub.foo.com".HasSufix(".foo.com")) | ||||||
|  | 	for i := range []*url.URL(whitelist) { | ||||||
|  | 		u := whitelist[i] | ||||||
|  | 
 | ||||||
|  | 		if issURL.Scheme != u.Scheme { | ||||||
|  | 			continue | ||||||
|  | 		} else if u.Path != strings.TrimRight(issURL.Path, "/")+"/" { | ||||||
|  | 			continue | ||||||
|  | 		} else if issURL.Host != u.Host { | ||||||
|  | 			if '.' == u.Host[0] && strings.HasSuffix(issURL.Host, u.Host) { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		// All failures have been handled | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check if implicit issuer is available | ||||||
|  | 	if 0 == len(rs) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return hasImplicitTrust(issURL, rs[0]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // hasImplicitTrust relies on the security of DNS and TLS to determine if the | ||||||
|  | // headers of the request can be trusted as identifying the server itself as | ||||||
|  | // a valid issuer, without additional configuration. | ||||||
|  | // | ||||||
|  | // Helpful for testing, but in the wrong hands could easily lead to a zero-day. | ||||||
|  | func hasImplicitTrust(issURL *url.URL, r *http.Request) bool { | ||||||
|  | 	if nil == r { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Sanity check that, if a load balancer exists, it isn't misconfigured | ||||||
|  | 	proto := r.Header.Get("X-Forwarded-Proto") | ||||||
|  | 	if "" != proto && proto != "https" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get the host | ||||||
|  | 	// * If TLS, block Domain Fronting | ||||||
|  | 	// * Otherwise assume trusted proxy | ||||||
|  | 	// * Otherwise assume test environment | ||||||
|  | 	var host string | ||||||
|  | 	if nil != r.TLS { | ||||||
|  | 		// Note that if this were to be implemented for HTTP/2 it would need to | ||||||
|  | 		// check all names on the certificate, not just the one with which the | ||||||
|  | 		// original connection was established. However, not our problem here. | ||||||
|  | 		// See https://serverfault.com/a/908087/93930 | ||||||
|  | 		if r.TLS.ServerName != r.Host { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		host = r.Host | ||||||
|  | 	} else { | ||||||
|  | 		host = r.Header.Get("X-Forwarded-Host") | ||||||
|  | 		if "" == host { | ||||||
|  | 			host = r.Host | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Same tests as above, adjusted since it can't handle wildcards and, since | ||||||
|  | 	// the path is variable, we make the assumption that a child can trust a | ||||||
|  | 	// parent, but that a parent cannot trust a child. | ||||||
|  | 	if r.Host != issURL.Host { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if !strings.HasPrefix(strings.TrimRight(r.URL.Path, "/")+"/", issURL.Path) { | ||||||
|  | 		// Ex: Request URL                                   Token Issuer | ||||||
|  | 		// !"https:example.com/johndoe/api/dothing".HasPrefix("https:example.com/") | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Whitelist is a newtype for an array of URLs | ||||||
|  | type Whitelist []*url.URL | ||||||
|  | 
 | ||||||
|  | // NewWhitelist turns an array of URLs (such as https://example.com/) into | ||||||
|  | // a parsed array of *url.URLs that can be used by the IsTrustedIssuer function | ||||||
|  | func NewWhitelist(issuers []string, privateList ...[]string) (Whitelist, error) { | ||||||
|  | 	var err error | ||||||
|  | 
 | ||||||
|  | 	list := []*url.URL{} | ||||||
|  | 	if 0 != len(issuers) { | ||||||
|  | 		insecure := false | ||||||
|  | 		list, err = newWhitelist(list, issuers, insecure) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if 0 != len(privateList) && 0 != len(privateList[0]) { | ||||||
|  | 		insecure := true | ||||||
|  | 		list, err = newWhitelist(list, privateList[0], insecure) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return Whitelist(list), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newWhitelist(list []*url.URL, issuers []string, insecure bool) (Whitelist, error) { | ||||||
|  | 	for i := range issuers { | ||||||
|  | 		iss := issuers[i] | ||||||
|  | 		if "" == strings.TrimSpace(iss) { | ||||||
|  | 			fmt.Println("[Warning] You have an empty string in your keyfetch whitelist.") | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Should have a valid http or https prefix | ||||||
|  | 		// TODO support custom prefixes (i.e. app://) ? | ||||||
|  | 		if strings.HasPrefix(iss, "http://") { | ||||||
|  | 			if !insecure { | ||||||
|  | 				log.Println("Oops! You have an insecure domain in your whitelist: ", iss) | ||||||
|  | 				return nil, ErrInsecureDomain | ||||||
|  | 			} | ||||||
|  | 		} else if strings.HasPrefix(iss, "//") { | ||||||
|  | 			// TODO | ||||||
|  | 			return nil, errors.New("Rather than prefixing with // to support multiple protocols, add them seperately:" + iss) | ||||||
|  | 		} else if !strings.HasPrefix(iss, "https://") { | ||||||
|  | 			iss = "https://" + iss | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// trailing slash as a boundary character, which may or may not denote a directory | ||||||
|  | 		iss = strings.TrimRight(iss, "/") + "/" | ||||||
|  | 		u, err := url.Parse(iss) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Strip any * prefix, for easier comparison later | ||||||
|  | 		// *.example.com => .example.com | ||||||
|  | 		if strings.HasPrefix(u.Host, "*.") { | ||||||
|  | 			u.Host = u.Host[1:] | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		list = append(list, u) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return list, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |   IsTrustedIssuer returns true when the `iss` (i.e. from a token) matches one | ||||||
|  |   in the provided whitelist (also matches wildcard domains). | ||||||
|  | 
 | ||||||
|  |   You may explicitly allow insecure http (i.e. for automated testing) by | ||||||
|  |   including http:// Otherwise the scheme in each item of the whitelist should | ||||||
|  | 	include the "https://" prefix. | ||||||
|  | 
 | ||||||
|  |   SECURITY CONSIDERATIONS (Please Read) | ||||||
|  | 
 | ||||||
|  |   You'll notice that *http.Request is optional. It should only be used under these | ||||||
|  |   three circumstances: | ||||||
|  | 
 | ||||||
|  |     1) Something else guarantees http -> https redirection happens before the | ||||||
|  |        connection gets here AND this server directly handles TLS/SSL. | ||||||
|  | 
 | ||||||
|  |     2) If you're using a load balancer or web server, and this doesn't handle | ||||||
|  |        TLS/SSL directly, that server is _explicitly_ configured to protect | ||||||
|  |        against Domain Fronting attacks. As of 2019, most web servers and load | ||||||
|  |        balancers do not protect against that by default. | ||||||
|  | 
 | ||||||
|  |     3) If you only use it to make your automated integration testing more | ||||||
|  |        and it isn't enabled in production. | ||||||
|  | 
 | ||||||
|  |   Otherwise, DO NOT pass in *http.Request as you will introduce a 0-day | ||||||
|  |   vulnerability allowing an attacker to spoof any token issuer of their choice. | ||||||
|  |   The only reason I allowed this in a public library where non-experts would | ||||||
|  |   encounter it is to make testing easier. | ||||||
|  | */ | ||||||
|  | func (w Whitelist) IsTrustedIssuer(iss string, rs ...*http.Request) bool { | ||||||
|  | 	return isTrustedIssuer(iss, w, rs...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String will generate a space-delimited list of whitelisted URLs | ||||||
|  | func (w Whitelist) String() string { | ||||||
|  | 	s := []string{} | ||||||
|  | 	for i := range w { | ||||||
|  | 		s = append(s, w[i].String()) | ||||||
|  | 	} | ||||||
|  | 	return strings.Join(s, " ") | ||||||
|  | } | ||||||
							
								
								
									
										183
									
								
								vendor/git.rootprojects.org/root/keypairs/keyfetch/uncached/fetch.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								vendor/git.rootprojects.org/root/keypairs/keyfetch/uncached/fetch.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,183 @@ | |||||||
|  | // Package uncached provides uncached versions of go-keypairs/keyfetch | ||||||
|  | package uncached | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"git.rootprojects.org/root/keypairs" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // OIDCJWKs gets the OpenID Connect configuration from the baseURL and then calls JWKs with the specified jwks_uri | ||||||
|  | func OIDCJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { | ||||||
|  | 	baseURL = normalizeBaseURL(baseURL) | ||||||
|  | 	oidcConf := struct { | ||||||
|  | 		JWKSURI string `json:"jwks_uri"` | ||||||
|  | 	}{} | ||||||
|  | 
 | ||||||
|  | 	// must come in as https://<domain>/ | ||||||
|  | 	url := baseURL + ".well-known/openid-configuration" | ||||||
|  | 	err := safeFetch(url, func(body io.Reader) error { | ||||||
|  | 		decoder := json.NewDecoder(body) | ||||||
|  | 		decoder.UseNumber() | ||||||
|  | 		return decoder.Decode(&oidcConf) | ||||||
|  | 	}) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return JWKs(oidcConf.JWKSURI) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WellKnownJWKs calls JWKs with baseURL + /.well-known/jwks.json as constructs the jwks_uri | ||||||
|  | func WellKnownJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { | ||||||
|  | 	baseURL = normalizeBaseURL(baseURL) | ||||||
|  | 	url := baseURL + ".well-known/jwks.json" | ||||||
|  | 
 | ||||||
|  | 	return JWKs(url) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // JWKs fetches and parses a jwks.json (assuming well-known format) | ||||||
|  | func JWKs(jwksurl string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { | ||||||
|  | 	keys := map[string]keypairs.PublicKey{} | ||||||
|  | 	maps := map[string]map[string]string{} | ||||||
|  | 	resp := struct { | ||||||
|  | 		Keys []map[string]interface{} `json:"keys"` | ||||||
|  | 	}{ | ||||||
|  | 		Keys: make([]map[string]interface{}, 0, 1), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := safeFetch(jwksurl, func(body io.Reader) error { | ||||||
|  | 		decoder := json.NewDecoder(body) | ||||||
|  | 		decoder.UseNumber() | ||||||
|  | 		return decoder.Decode(&resp) | ||||||
|  | 	}); nil != err { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i := range resp.Keys { | ||||||
|  | 		k := resp.Keys[i] | ||||||
|  | 		m := getStringMap(k) | ||||||
|  | 
 | ||||||
|  | 		key, err := keypairs.NewJWKPublicKey(m) | ||||||
|  | 
 | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, nil, err | ||||||
|  | 		} | ||||||
|  | 		keys[key.Thumbprint()] = key | ||||||
|  | 		maps[key.Thumbprint()] = m | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return maps, keys, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PEM fetches and parses a PEM (assuming well-known format) | ||||||
|  | func PEM(pemurl string) (map[string]string, keypairs.PublicKey, error) { | ||||||
|  | 	var pub keypairs.PublicKey | ||||||
|  | 	if err := safeFetch(pemurl, func(body io.Reader) error { | ||||||
|  | 		pem, err := ioutil.ReadAll(body) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		pub, err = keypairs.ParsePublicKey(pem) | ||||||
|  | 		return err | ||||||
|  | 	}); nil != err { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	jwk := map[string]interface{}{} | ||||||
|  | 	body := bytes.NewBuffer(keypairs.MarshalJWKPublicKey(pub)) | ||||||
|  | 	decoder := json.NewDecoder(body) | ||||||
|  | 	decoder.UseNumber() | ||||||
|  | 	_ = decoder.Decode(&jwk) | ||||||
|  | 
 | ||||||
|  | 	m := getStringMap(jwk) | ||||||
|  | 	m["kid"] = pemurl | ||||||
|  | 
 | ||||||
|  | 	switch p := pub.(type) { | ||||||
|  | 	case *keypairs.ECPublicKey: | ||||||
|  | 		p.KID = pemurl | ||||||
|  | 	case *keypairs.RSAPublicKey: | ||||||
|  | 		p.KID = pemurl | ||||||
|  | 	default: | ||||||
|  | 		return nil, nil, errors.New("impossible key type") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return m, pub, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Fetch retrieves a single JWK (plain, bare jwk) from a URL (off-spec) | ||||||
|  | func Fetch(url string) (map[string]string, keypairs.PublicKey, error) { | ||||||
|  | 	var m map[string]interface{} | ||||||
|  | 	if err := safeFetch(url, func(body io.Reader) error { | ||||||
|  | 		decoder := json.NewDecoder(body) | ||||||
|  | 		decoder.UseNumber() | ||||||
|  | 		return decoder.Decode(&m) | ||||||
|  | 	}); nil != err { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	n := getStringMap(m) | ||||||
|  | 	key, err := keypairs.NewJWKPublicKey(n) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return n, key, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getStringMap(m map[string]interface{}) map[string]string { | ||||||
|  | 	n := make(map[string]string) | ||||||
|  | 
 | ||||||
|  | 	// TODO get issuer from x5c, if exists | ||||||
|  | 
 | ||||||
|  | 	// convert map[string]interface{} to map[string]string | ||||||
|  | 	for j := range m { | ||||||
|  | 		switch s := m[j].(type) { | ||||||
|  | 		case string: | ||||||
|  | 			n[j] = s | ||||||
|  | 		default: | ||||||
|  | 			// safely ignore | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type decodeFunc func(io.Reader) error | ||||||
|  | 
 | ||||||
|  | // TODO: also limit the body size | ||||||
|  | func safeFetch(url string, decoder decodeFunc) error { | ||||||
|  | 	var netTransport = &http.Transport{ | ||||||
|  | 		Dial: (&net.Dialer{ | ||||||
|  | 			Timeout: 5 * time.Second, | ||||||
|  | 		}).Dial, | ||||||
|  | 		TLSHandshakeTimeout: 5 * time.Second, | ||||||
|  | 	} | ||||||
|  | 	var client = &http.Client{ | ||||||
|  | 		Timeout:   time.Second * 10, | ||||||
|  | 		Transport: netTransport, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := http.NewRequest("GET", url, nil) | ||||||
|  | 	req.Header.Set("User-Agent", "go-keypairs/keyfetch") | ||||||
|  | 	req.Header.Set("Accept", "application/json;q=0.9,*/*;q=0.8") | ||||||
|  | 	res, err := client.Do(req) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer res.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	return decoder(res.Body) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func normalizeBaseURL(iss string) string { | ||||||
|  | 	return strings.TrimRight(iss, "/") + "/" | ||||||
|  | } | ||||||
							
								
								
									
										645
									
								
								vendor/git.rootprojects.org/root/keypairs/keypairs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										645
									
								
								vendor/git.rootprojects.org/root/keypairs/keypairs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,645 @@ | |||||||
|  | package keypairs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto" | ||||||
|  | 	"crypto/dsa" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/elliptic" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"crypto/sha256" | ||||||
|  | 	"crypto/x509" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"encoding/pem" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"log" | ||||||
|  | 	"math/big" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ErrInvalidPrivateKey means that the key is not a valid Private Key | ||||||
|  | var ErrInvalidPrivateKey = errors.New("PrivateKey must be of type *rsa.PrivateKey or *ecdsa.PrivateKey") | ||||||
|  | 
 | ||||||
|  | // ErrInvalidPublicKey means that the key is not a valid Public Key | ||||||
|  | var ErrInvalidPublicKey = errors.New("PublicKey must be of type *rsa.PublicKey or *ecdsa.PublicKey") | ||||||
|  | 
 | ||||||
|  | // ErrParsePublicKey means that the bytes cannot be parsed in any known format | ||||||
|  | var ErrParsePublicKey = errors.New("PublicKey bytes could not be parsed as PEM or DER (PKIX/SPKI, PKCS1, or X509 Certificate) or JWK") | ||||||
|  | 
 | ||||||
|  | // ErrParsePrivateKey means that the bytes cannot be parsed in any known format | ||||||
|  | var ErrParsePrivateKey = errors.New("PrivateKey bytes could not be parsed as PEM or DER (PKCS8, SEC1, or PKCS1) or JWK") | ||||||
|  | 
 | ||||||
|  | // ErrParseJWK means that the JWK is valid JSON but not a valid JWK | ||||||
|  | var ErrParseJWK = errors.New("JWK is missing required base64-encoded JSON fields") | ||||||
|  | 
 | ||||||
|  | // ErrInvalidKeyType means that the key is not an acceptable type | ||||||
|  | var ErrInvalidKeyType = errors.New("The JWK's 'kty' must be either 'RSA' or 'EC'") | ||||||
|  | 
 | ||||||
|  | // ErrInvalidCurve means that a non-standard curve was used | ||||||
|  | var ErrInvalidCurve = errors.New("The JWK's 'crv' must be either of the NIST standards 'P-256' or 'P-384'") | ||||||
|  | 
 | ||||||
|  | // ErrUnexpectedPublicKey means that a Private Key was expected | ||||||
|  | var ErrUnexpectedPublicKey = errors.New("PrivateKey was given where PublicKey was expected") | ||||||
|  | 
 | ||||||
|  | // ErrUnexpectedPrivateKey means that a Public Key was expected | ||||||
|  | var ErrUnexpectedPrivateKey = errors.New("PublicKey was given where PrivateKey was expected") | ||||||
|  | 
 | ||||||
|  | // ErrDevSwapPrivatePublic means that the developer compiled bad code that swapped public and private keys | ||||||
|  | const ErrDevSwapPrivatePublic = "[Developer Error] You passed either crypto.PrivateKey or crypto.PublicKey where the other was expected." | ||||||
|  | 
 | ||||||
|  | // ErrDevBadKeyType means that the developer compiled bad code that passes the wrong type | ||||||
|  | const ErrDevBadKeyType = "[Developer Error] crypto.PublicKey and crypto.PrivateKey are somewhat deceptive. They're actually empty interfaces that accept any object, even non-crypto objects. You passed an object of type '%T' by mistake." | ||||||
|  | 
 | ||||||
|  | // PrivateKey is a zero-cost typesafe substitue for crypto.PrivateKey | ||||||
|  | type PrivateKey interface { | ||||||
|  | 	Public() crypto.PublicKey | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PublicKey thinly veils crypto.PublicKey for type safety | ||||||
|  | type PublicKey interface { | ||||||
|  | 	crypto.PublicKey | ||||||
|  | 	Thumbprint() string | ||||||
|  | 	KeyID() string | ||||||
|  | 	Key() crypto.PublicKey | ||||||
|  | 	ExpiresAt() time.Time | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ECPublicKey adds common methods to *ecdsa.PublicKey for type safety | ||||||
|  | type ECPublicKey struct { | ||||||
|  | 	PublicKey *ecdsa.PublicKey // empty interface | ||||||
|  | 	KID       string | ||||||
|  | 	Expiry    time.Time | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RSAPublicKey adds common methods to *rsa.PublicKey for type safety | ||||||
|  | type RSAPublicKey struct { | ||||||
|  | 	PublicKey *rsa.PublicKey // empty interface | ||||||
|  | 	KID       string | ||||||
|  | 	Expiry    time.Time | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Thumbprint returns a JWK thumbprint. See https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk | ||||||
|  | func (p *ECPublicKey) Thumbprint() string { | ||||||
|  | 	return ThumbprintUntypedPublicKey(p.PublicKey) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // KeyID returns the JWK `kid`, which will be the Thumbprint for keys generated with this library | ||||||
|  | func (p *ECPublicKey) KeyID() string { | ||||||
|  | 	return p.KID | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Key returns the PublicKey | ||||||
|  | func (p *ECPublicKey) Key() crypto.PublicKey { | ||||||
|  | 	return p.PublicKey | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ExpireAt sets the time at which this Public Key should be considered invalid | ||||||
|  | func (p *ECPublicKey) ExpireAt(t time.Time) { | ||||||
|  | 	p.Expiry = t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ExpiresAt gets the time at which this Public Key should be considered invalid | ||||||
|  | func (p *ECPublicKey) ExpiresAt() time.Time { | ||||||
|  | 	return p.Expiry | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Thumbprint returns a JWK thumbprint. See https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk | ||||||
|  | func (p *RSAPublicKey) Thumbprint() string { | ||||||
|  | 	return ThumbprintUntypedPublicKey(p.PublicKey) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // KeyID returns the JWK `kid`, which will be the Thumbprint for keys generated with this library | ||||||
|  | func (p *RSAPublicKey) KeyID() string { | ||||||
|  | 	return p.KID | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Key returns the PublicKey | ||||||
|  | func (p *RSAPublicKey) Key() crypto.PublicKey { | ||||||
|  | 	return p.PublicKey | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ExpireAt sets the time at which this Public Key should be considered invalid | ||||||
|  | func (p *RSAPublicKey) ExpireAt(t time.Time) { | ||||||
|  | 	p.Expiry = t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ExpiresAt gets the time at which this Public Key should be considered invalid | ||||||
|  | func (p *RSAPublicKey) ExpiresAt() time.Time { | ||||||
|  | 	return p.Expiry | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewPublicKey wraps a crypto.PublicKey to make it typesafe. | ||||||
|  | func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKey { | ||||||
|  | 	var k PublicKey | ||||||
|  | 	switch p := pub.(type) { | ||||||
|  | 	case *ecdsa.PublicKey: | ||||||
|  | 		eckey := &ECPublicKey{ | ||||||
|  | 			PublicKey: p, | ||||||
|  | 		} | ||||||
|  | 		if 0 != len(kid) { | ||||||
|  | 			eckey.KID = kid[0] | ||||||
|  | 		} else { | ||||||
|  | 			eckey.KID = ThumbprintECPublicKey(p) | ||||||
|  | 		} | ||||||
|  | 		k = eckey | ||||||
|  | 	case *rsa.PublicKey: | ||||||
|  | 		rsakey := &RSAPublicKey{ | ||||||
|  | 			PublicKey: p, | ||||||
|  | 		} | ||||||
|  | 		if 0 != len(kid) { | ||||||
|  | 			rsakey.KID = kid[0] | ||||||
|  | 		} else { | ||||||
|  | 			rsakey.KID = ThumbprintRSAPublicKey(p) | ||||||
|  | 		} | ||||||
|  | 		k = rsakey | ||||||
|  | 	case *ecdsa.PrivateKey: | ||||||
|  | 		panic(errors.New(ErrDevSwapPrivatePublic)) | ||||||
|  | 	case *rsa.PrivateKey: | ||||||
|  | 		panic(errors.New(ErrDevSwapPrivatePublic)) | ||||||
|  | 	case *dsa.PublicKey: | ||||||
|  | 		panic(ErrInvalidPublicKey) | ||||||
|  | 	case *dsa.PrivateKey: | ||||||
|  | 		panic(ErrInvalidPrivateKey) | ||||||
|  | 	default: | ||||||
|  | 		panic(fmt.Errorf(ErrDevBadKeyType, pub)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return k | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalJWKPublicKey outputs a JWK with its key id (kid) and an optional expiration, | ||||||
|  | // making it suitable for use as an OIDC public key. | ||||||
|  | func MarshalJWKPublicKey(key PublicKey, exp ...time.Time) []byte { | ||||||
|  | 	// thumbprint keys are alphabetically sorted and only include the necessary public parts | ||||||
|  | 	switch k := key.Key().(type) { | ||||||
|  | 	case *rsa.PublicKey: | ||||||
|  | 		return MarshalRSAPublicKey(k, exp...) | ||||||
|  | 	case *ecdsa.PublicKey: | ||||||
|  | 		return MarshalECPublicKey(k, exp...) | ||||||
|  | 	case *dsa.PublicKey: | ||||||
|  | 		panic(ErrInvalidPublicKey) | ||||||
|  | 	default: | ||||||
|  | 		// this is unreachable because we know the types that we pass in | ||||||
|  | 		log.Printf("keytype: %t, %+v\n", key, key) | ||||||
|  | 		panic(ErrInvalidPublicKey) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ThumbprintPublicKey returns the SHA256 RFC-spec JWK thumbprint | ||||||
|  | func ThumbprintPublicKey(pub PublicKey) string { | ||||||
|  | 	return ThumbprintUntypedPublicKey(pub.Key()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ThumbprintUntypedPublicKey is a non-typesafe version of ThumbprintPublicKey | ||||||
|  | // (but will still panic, to help you discover bugs in development rather than production). | ||||||
|  | func ThumbprintUntypedPublicKey(pub crypto.PublicKey) string { | ||||||
|  | 	switch p := pub.(type) { | ||||||
|  | 	case PublicKey: | ||||||
|  | 		return ThumbprintUntypedPublicKey(p.Key()) | ||||||
|  | 	case *ecdsa.PublicKey: | ||||||
|  | 		return ThumbprintECPublicKey(p) | ||||||
|  | 	case *rsa.PublicKey: | ||||||
|  | 		return ThumbprintRSAPublicKey(p) | ||||||
|  | 	default: | ||||||
|  | 		panic(ErrInvalidPublicKey) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalECPublicKey will take an EC key and output a JWK, with optional expiration date | ||||||
|  | func MarshalECPublicKey(k *ecdsa.PublicKey, exp ...time.Time) []byte { | ||||||
|  | 	thumb := ThumbprintECPublicKey(k) | ||||||
|  | 	crv := k.Curve.Params().Name | ||||||
|  | 	x := base64.RawURLEncoding.EncodeToString(k.X.Bytes()) | ||||||
|  | 	y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes()) | ||||||
|  | 	expstr := "" | ||||||
|  | 	if 0 != len(exp) { | ||||||
|  | 		expstr = fmt.Sprintf(`"exp":%d,`, exp[0].Unix()) | ||||||
|  | 	} | ||||||
|  | 	return []byte(fmt.Sprintf(`{"kid":%q,"use":"sig",%s"crv":%q,"kty":"EC","x":%q,"y":%q}`, thumb, expstr, crv, x, y)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalECPublicKeyWithoutKeyID will output the most minimal version of an EC JWK (no key id, no "use" flag, nada) | ||||||
|  | func MarshalECPublicKeyWithoutKeyID(k *ecdsa.PublicKey) []byte { | ||||||
|  | 	crv := k.Curve.Params().Name | ||||||
|  | 	x := base64.RawURLEncoding.EncodeToString(k.X.Bytes()) | ||||||
|  | 	y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes()) | ||||||
|  | 	return []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, crv, x, y)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ThumbprintECPublicKey will output a RFC-spec SHA256 JWK thumbprint of an EC public key | ||||||
|  | func ThumbprintECPublicKey(k *ecdsa.PublicKey) string { | ||||||
|  | 	thumbprintable := MarshalECPublicKeyWithoutKeyID(k) | ||||||
|  | 	sha := sha256.Sum256(thumbprintable) | ||||||
|  | 	return base64.RawURLEncoding.EncodeToString(sha[:]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalRSAPublicKey will take an RSA key and output a JWK, with optional expiration date | ||||||
|  | func MarshalRSAPublicKey(p *rsa.PublicKey, exp ...time.Time) []byte { | ||||||
|  | 	thumb := ThumbprintRSAPublicKey(p) | ||||||
|  | 	e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes()) | ||||||
|  | 	n := base64.RawURLEncoding.EncodeToString(p.N.Bytes()) | ||||||
|  | 	expstr := "" | ||||||
|  | 	if 0 != len(exp) { | ||||||
|  | 		expstr = fmt.Sprintf(`"exp":%d,`, exp[0].Unix()) | ||||||
|  | 	} | ||||||
|  | 	return []byte(fmt.Sprintf(`{"kid":%q,"use":"sig",%s"e":%q,"kty":"RSA","n":%q}`, thumb, expstr, e, n)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalRSAPublicKeyWithoutKeyID will output the most minimal version of an RSA JWK (no key id, no "use" flag, nada) | ||||||
|  | func MarshalRSAPublicKeyWithoutKeyID(p *rsa.PublicKey) []byte { | ||||||
|  | 	e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes()) | ||||||
|  | 	n := base64.RawURLEncoding.EncodeToString(p.N.Bytes()) | ||||||
|  | 	return []byte(fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, e, n)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ThumbprintRSAPublicKey will output a RFC-spec SHA256 JWK thumbprint of an EC public key | ||||||
|  | func ThumbprintRSAPublicKey(p *rsa.PublicKey) string { | ||||||
|  | 	thumbprintable := MarshalRSAPublicKeyWithoutKeyID(p) | ||||||
|  | 	sha := sha256.Sum256([]byte(thumbprintable)) | ||||||
|  | 	return base64.RawURLEncoding.EncodeToString(sha[:]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ParsePrivateKey will try to parse the bytes you give it | ||||||
|  | // in any of the supported formats: PEM, DER, PKCS8, PKCS1, SEC1, and JWK | ||||||
|  | func ParsePrivateKey(block []byte) (PrivateKey, error) { | ||||||
|  | 	blocks, err := getPEMBytes(block) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, ErrParsePrivateKey | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Parse PEM blocks (openssl generates junk metadata blocks for ECs) | ||||||
|  | 	// or the original DER, or the JWK | ||||||
|  | 	for i := range blocks { | ||||||
|  | 		block = blocks[i] | ||||||
|  | 		if key, err := parsePrivateKey(block); nil == err { | ||||||
|  | 			return key, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i := range blocks { | ||||||
|  | 		block = blocks[i] | ||||||
|  | 		if _, err := parsePublicKey(block); nil == err { | ||||||
|  | 			return nil, ErrUnexpectedPublicKey | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If we didn't parse a key arleady, we failed | ||||||
|  | 	return nil, ErrParsePrivateKey | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ParsePrivateKeyString calls ParsePrivateKey([]byte(key)) for all you lazy folk. | ||||||
|  | func ParsePrivateKeyString(block string) (PrivateKey, error) { | ||||||
|  | 	return ParsePrivateKey([]byte(block)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parsePrivateKey(der []byte) (PrivateKey, error) { | ||||||
|  | 	var key PrivateKey | ||||||
|  | 
 | ||||||
|  | 	//fmt.Println("1. ParsePKCS8PrivateKey") | ||||||
|  | 	xkey, err := x509.ParsePKCS8PrivateKey(der) | ||||||
|  | 	if nil == err { | ||||||
|  | 		switch k := xkey.(type) { | ||||||
|  | 		case *rsa.PrivateKey: | ||||||
|  | 			key = k | ||||||
|  | 		case *ecdsa.PrivateKey: | ||||||
|  | 			key = k | ||||||
|  | 		default: | ||||||
|  | 			err = errors.New("Only RSA and ECDSA (EC) Private Keys are supported") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if nil != err { | ||||||
|  | 		//fmt.Println("2. ParseECPrivateKey") | ||||||
|  | 		key, err = x509.ParseECPrivateKey(der) | ||||||
|  | 		if nil != err { | ||||||
|  | 			//fmt.Println("3. ParsePKCS1PrivateKey") | ||||||
|  | 			key, err = x509.ParsePKCS1PrivateKey(der) | ||||||
|  | 			if nil != err { | ||||||
|  | 				//fmt.Println("4. ParseJWKPrivateKey") | ||||||
|  | 				key, err = ParseJWKPrivateKey(der) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// But did you know? | ||||||
|  | 	// You must return nil explicitly for interfaces | ||||||
|  | 	// https://golang.org/doc/faq#nil_error | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return key, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getPEMBytes(block []byte) ([][]byte, error) { | ||||||
|  | 	var pemblock *pem.Block | ||||||
|  | 	var blocks = make([][]byte, 0, 1) | ||||||
|  | 
 | ||||||
|  | 	// Parse the PEM, if it's a pem | ||||||
|  | 	for { | ||||||
|  | 		pemblock, block = pem.Decode(block) | ||||||
|  | 		if nil != pemblock { | ||||||
|  | 			// got one block, there may be more | ||||||
|  | 			blocks = append(blocks, pemblock.Bytes) | ||||||
|  | 		} else { | ||||||
|  | 			// the last block was not a PEM block | ||||||
|  | 			// therefore the next isn't either | ||||||
|  | 			if 0 != len(block) { | ||||||
|  | 				blocks = append(blocks, block) | ||||||
|  | 			} | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(blocks) > 0 { | ||||||
|  | 		return blocks, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, errors.New("no PEM blocks found") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ParsePublicKey will try to parse the bytes you give it | ||||||
|  | // in any of the supported formats: PEM, DER, PKIX/SPKI, PKCS1, x509 Certificate, and JWK | ||||||
|  | func ParsePublicKey(block []byte) (PublicKey, error) { | ||||||
|  | 	blocks, err := getPEMBytes(block) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, ErrParsePublicKey | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Parse PEM blocks (openssl generates junk metadata blocks for ECs) | ||||||
|  | 	// or the original DER, or the JWK | ||||||
|  | 	for i := range blocks { | ||||||
|  | 		block = blocks[i] | ||||||
|  | 		if key, err := parsePublicKey(block); nil == err { | ||||||
|  | 			return key, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i := range blocks { | ||||||
|  | 		block = blocks[i] | ||||||
|  | 		if _, err := parsePrivateKey(block); nil == err { | ||||||
|  | 			return nil, ErrUnexpectedPrivateKey | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If we didn't parse a key arleady, we failed | ||||||
|  | 	return nil, ErrParsePublicKey | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ParsePublicKeyString calls ParsePublicKey([]byte(key)) for all you lazy folk. | ||||||
|  | func ParsePublicKeyString(block string) (PublicKey, error) { | ||||||
|  | 	return ParsePublicKey([]byte(block)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parsePublicKey(der []byte) (PublicKey, error) { | ||||||
|  | 	cert, err := x509.ParseCertificate(der) | ||||||
|  | 	if nil == err { | ||||||
|  | 		switch k := cert.PublicKey.(type) { | ||||||
|  | 		case *rsa.PublicKey: | ||||||
|  | 			return NewPublicKey(k), nil | ||||||
|  | 		case *ecdsa.PublicKey: | ||||||
|  | 			return NewPublicKey(k), nil | ||||||
|  | 		default: | ||||||
|  | 			return nil, errors.New("Only RSA and ECDSA (EC) Public Keys are supported") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	//fmt.Println("1. ParsePKIXPublicKey") | ||||||
|  | 	xkey, err := x509.ParsePKIXPublicKey(der) | ||||||
|  | 	if nil == err { | ||||||
|  | 		switch k := xkey.(type) { | ||||||
|  | 		case *rsa.PublicKey: | ||||||
|  | 			return NewPublicKey(k), nil | ||||||
|  | 		case *ecdsa.PublicKey: | ||||||
|  | 			return NewPublicKey(k), nil | ||||||
|  | 		default: | ||||||
|  | 			return nil, errors.New("Only RSA and ECDSA (EC) Public Keys are supported") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	//fmt.Println("3. ParsePKCS1PrublicKey") | ||||||
|  | 	rkey, err := x509.ParsePKCS1PublicKey(der) | ||||||
|  | 	if nil == err { | ||||||
|  | 		//fmt.Println("4. ParseJWKPublicKey") | ||||||
|  | 		return NewPublicKey(rkey), nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ParseJWKPublicKey(der) | ||||||
|  | 
 | ||||||
|  | 	/* | ||||||
|  | 		// But did you know? | ||||||
|  | 		// You must return nil explicitly for interfaces | ||||||
|  | 		// https://golang.org/doc/faq#nil_error | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	*/ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewJWKPublicKey contstructs a PublicKey from the relevant pieces a map[string]string (generic JSON) | ||||||
|  | func NewJWKPublicKey(m map[string]string) (PublicKey, error) { | ||||||
|  | 	switch m["kty"] { | ||||||
|  | 	case "RSA": | ||||||
|  | 		return parseRSAPublicKey(m) | ||||||
|  | 	case "EC": | ||||||
|  | 		return parseECPublicKey(m) | ||||||
|  | 	default: | ||||||
|  | 		return nil, ErrInvalidKeyType | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ParseJWKPublicKey parses a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message | ||||||
|  | func ParseJWKPublicKey(b []byte) (PublicKey, error) { | ||||||
|  | 	// RSA and EC have "d" as a private part | ||||||
|  | 	if bytes.Contains(b, []byte(`"d"`)) { | ||||||
|  | 		return nil, ErrUnexpectedPrivateKey | ||||||
|  | 	} | ||||||
|  | 	return newJWKPublicKey(b) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ParseJWKPublicKeyString calls ParseJWKPublicKey([]byte(key)) for all you lazy folk. | ||||||
|  | func ParseJWKPublicKeyString(s string) (PublicKey, error) { | ||||||
|  | 	if strings.Contains(s, `"d"`) { | ||||||
|  | 		return nil, ErrUnexpectedPrivateKey | ||||||
|  | 	} | ||||||
|  | 	return newJWKPublicKey(s) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DecodeJWKPublicKey stream-decodes a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message | ||||||
|  | func DecodeJWKPublicKey(r io.Reader) (PublicKey, error) { | ||||||
|  | 	m := make(map[string]string) | ||||||
|  | 	if err := json.NewDecoder(r).Decode(&m); nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if d := m["d"]; "" != d { | ||||||
|  | 		return nil, ErrUnexpectedPrivateKey | ||||||
|  | 	} | ||||||
|  | 	return newJWKPublicKey(m) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // the underpinnings of the parser as used by the typesafe wrappers | ||||||
|  | func newJWKPublicKey(data interface{}) (PublicKey, error) { | ||||||
|  | 	var m map[string]string | ||||||
|  | 
 | ||||||
|  | 	switch d := data.(type) { | ||||||
|  | 	case map[string]string: | ||||||
|  | 		m = d | ||||||
|  | 	case string: | ||||||
|  | 		if err := json.Unmarshal([]byte(d), &m); nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	case []byte: | ||||||
|  | 		if err := json.Unmarshal(d, &m); nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		panic("Developer Error: unsupported interface type") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return NewJWKPublicKey(m) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ParseJWKPrivateKey parses a JSON-encoded JWK and returns a PrivateKey, or a (hopefully) helpful error message | ||||||
|  | func ParseJWKPrivateKey(b []byte) (PrivateKey, error) { | ||||||
|  | 	var m map[string]string | ||||||
|  | 	if err := json.Unmarshal(b, &m); nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch m["kty"] { | ||||||
|  | 	case "RSA": | ||||||
|  | 		return parseRSAPrivateKey(m) | ||||||
|  | 	case "EC": | ||||||
|  | 		return parseECPrivateKey(m) | ||||||
|  | 	default: | ||||||
|  | 		return nil, ErrInvalidKeyType | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseRSAPublicKey(m map[string]string) (*RSAPublicKey, error) { | ||||||
|  | 	// TODO grab expiry? | ||||||
|  | 	kid, _ := m["kid"] | ||||||
|  | 	n, _ := base64.RawURLEncoding.DecodeString(m["n"]) | ||||||
|  | 	e, _ := base64.RawURLEncoding.DecodeString(m["e"]) | ||||||
|  | 	if 0 == len(n) || 0 == len(e) { | ||||||
|  | 		return nil, ErrParseJWK | ||||||
|  | 	} | ||||||
|  | 	ni := &big.Int{} | ||||||
|  | 	ni.SetBytes(n) | ||||||
|  | 	ei := &big.Int{} | ||||||
|  | 	ei.SetBytes(e) | ||||||
|  | 
 | ||||||
|  | 	pub := &rsa.PublicKey{ | ||||||
|  | 		N: ni, | ||||||
|  | 		E: int(ei.Int64()), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &RSAPublicKey{ | ||||||
|  | 		PublicKey: pub, | ||||||
|  | 		KID:       kid, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseRSAPrivateKey(m map[string]string) (key *rsa.PrivateKey, err error) { | ||||||
|  | 	pub, err := parseRSAPublicKey(m) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	d, _ := base64.RawURLEncoding.DecodeString(m["d"]) | ||||||
|  | 	p, _ := base64.RawURLEncoding.DecodeString(m["p"]) | ||||||
|  | 	q, _ := base64.RawURLEncoding.DecodeString(m["q"]) | ||||||
|  | 	dp, _ := base64.RawURLEncoding.DecodeString(m["dp"]) | ||||||
|  | 	dq, _ := base64.RawURLEncoding.DecodeString(m["dq"]) | ||||||
|  | 	qinv, _ := base64.RawURLEncoding.DecodeString(m["qi"]) | ||||||
|  | 	if 0 == len(d) || 0 == len(p) || 0 == len(dp) || 0 == len(dq) || 0 == len(qinv) { | ||||||
|  | 		return nil, ErrParseJWK | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	di := &big.Int{} | ||||||
|  | 	di.SetBytes(d) | ||||||
|  | 	pi := &big.Int{} | ||||||
|  | 	pi.SetBytes(p) | ||||||
|  | 	qi := &big.Int{} | ||||||
|  | 	qi.SetBytes(q) | ||||||
|  | 	dpi := &big.Int{} | ||||||
|  | 	dpi.SetBytes(dp) | ||||||
|  | 	dqi := &big.Int{} | ||||||
|  | 	dqi.SetBytes(dq) | ||||||
|  | 	qinvi := &big.Int{} | ||||||
|  | 	qinvi.SetBytes(qinv) | ||||||
|  | 
 | ||||||
|  | 	key = &rsa.PrivateKey{ | ||||||
|  | 		PublicKey: *pub.PublicKey, | ||||||
|  | 		D:         di, | ||||||
|  | 		Primes:    []*big.Int{pi, qi}, | ||||||
|  | 		Precomputed: rsa.PrecomputedValues{ | ||||||
|  | 			Dp:   dpi, | ||||||
|  | 			Dq:   dqi, | ||||||
|  | 			Qinv: qinvi, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseECPublicKey(m map[string]string) (*ECPublicKey, error) { | ||||||
|  | 	// TODO grab expiry? | ||||||
|  | 	kid, _ := m["kid"] | ||||||
|  | 	x, _ := base64.RawURLEncoding.DecodeString(m["x"]) | ||||||
|  | 	y, _ := base64.RawURLEncoding.DecodeString(m["y"]) | ||||||
|  | 	if 0 == len(x) || 0 == len(y) || 0 == len(m["crv"]) { | ||||||
|  | 		return nil, ErrParseJWK | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	xi := &big.Int{} | ||||||
|  | 	xi.SetBytes(x) | ||||||
|  | 
 | ||||||
|  | 	yi := &big.Int{} | ||||||
|  | 	yi.SetBytes(y) | ||||||
|  | 
 | ||||||
|  | 	var crv elliptic.Curve | ||||||
|  | 	switch m["crv"] { | ||||||
|  | 	case "P-256": | ||||||
|  | 		crv = elliptic.P256() | ||||||
|  | 	case "P-384": | ||||||
|  | 		crv = elliptic.P384() | ||||||
|  | 	case "P-521": | ||||||
|  | 		crv = elliptic.P521() | ||||||
|  | 	default: | ||||||
|  | 		return nil, ErrInvalidCurve | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub := &ecdsa.PublicKey{ | ||||||
|  | 		Curve: crv, | ||||||
|  | 		X:     xi, | ||||||
|  | 		Y:     yi, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &ECPublicKey{ | ||||||
|  | 		PublicKey: pub, | ||||||
|  | 		KID:       kid, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseECPrivateKey(m map[string]string) (*ecdsa.PrivateKey, error) { | ||||||
|  | 	pub, err := parseECPublicKey(m) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	d, _ := base64.RawURLEncoding.DecodeString(m["d"]) | ||||||
|  | 	if 0 == len(d) { | ||||||
|  | 		return nil, ErrParseJWK | ||||||
|  | 	} | ||||||
|  | 	di := &big.Int{} | ||||||
|  | 	di.SetBytes(d) | ||||||
|  | 
 | ||||||
|  | 	return &ecdsa.PrivateKey{ | ||||||
|  | 		PublicKey: *pub.PublicKey, | ||||||
|  | 		D:         di, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
							
								
								
									
										171
									
								
								vendor/git.rootprojects.org/root/keypairs/marshal.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								vendor/git.rootprojects.org/root/keypairs/marshal.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | |||||||
|  | package keypairs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"crypto/x509" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/pem" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"math/big" | ||||||
|  | 	mathrand "math/rand" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // MarshalPEMPublicKey outputs the given public key as JWK | ||||||
|  | func MarshalPEMPublicKey(pubkey crypto.PublicKey) ([]byte, error) { | ||||||
|  | 	block, err := marshalDERPublicKey(pubkey) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return pem.EncodeToMemory(block), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalDERPublicKey outputs the given public key as JWK | ||||||
|  | func MarshalDERPublicKey(pubkey crypto.PublicKey) ([]byte, error) { | ||||||
|  | 	block, err := marshalDERPublicKey(pubkey) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return block.Bytes, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // marshalDERPublicKey outputs the given public key as JWK | ||||||
|  | func marshalDERPublicKey(pubkey crypto.PublicKey) (*pem.Block, error) { | ||||||
|  | 
 | ||||||
|  | 	var der []byte | ||||||
|  | 	var typ string | ||||||
|  | 	var err error | ||||||
|  | 	switch k := pubkey.(type) { | ||||||
|  | 	case *rsa.PublicKey: | ||||||
|  | 		der = x509.MarshalPKCS1PublicKey(k) | ||||||
|  | 		typ = "RSA PUBLIC KEY" | ||||||
|  | 	case *ecdsa.PublicKey: | ||||||
|  | 		typ = "PUBLIC KEY" | ||||||
|  | 		der, err = x509.MarshalPKIXPublicKey(k) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		panic("Developer Error: impossible key type") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &pem.Block{ | ||||||
|  | 		Bytes: der, | ||||||
|  | 		Type:  typ, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalJWKPrivateKey outputs the given private key as JWK | ||||||
|  | func MarshalJWKPrivateKey(privkey PrivateKey) []byte { | ||||||
|  | 	// thumbprint keys are alphabetically sorted and only include the necessary public parts | ||||||
|  | 	switch k := privkey.(type) { | ||||||
|  | 	case *rsa.PrivateKey: | ||||||
|  | 		return MarshalRSAPrivateKey(k) | ||||||
|  | 	case *ecdsa.PrivateKey: | ||||||
|  | 		return MarshalECPrivateKey(k) | ||||||
|  | 	default: | ||||||
|  | 		// this is unreachable because we know the types that we pass in | ||||||
|  | 		log.Printf("keytype: %t, %+v\n", privkey, privkey) | ||||||
|  | 		panic(ErrInvalidPublicKey) | ||||||
|  | 		//return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalDERPrivateKey outputs the given private key as ASN.1 DER | ||||||
|  | func MarshalDERPrivateKey(privkey PrivateKey) ([]byte, error) { | ||||||
|  | 	// thumbprint keys are alphabetically sorted and only include the necessary public parts | ||||||
|  | 	switch k := privkey.(type) { | ||||||
|  | 	case *rsa.PrivateKey: | ||||||
|  | 		return x509.MarshalPKCS1PrivateKey(k), nil | ||||||
|  | 	case *ecdsa.PrivateKey: | ||||||
|  | 		return x509.MarshalECPrivateKey(k) | ||||||
|  | 	default: | ||||||
|  | 		// this is unreachable because we know the types that we pass in | ||||||
|  | 		log.Printf("keytype: %t, %+v\n", privkey, privkey) | ||||||
|  | 		panic(ErrInvalidPublicKey) | ||||||
|  | 		//return nil, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func marshalDERPrivateKey(privkey PrivateKey) (*pem.Block, error) { | ||||||
|  | 	var typ string | ||||||
|  | 	var bytes []byte | ||||||
|  | 	var err error | ||||||
|  | 
 | ||||||
|  | 	switch k := privkey.(type) { | ||||||
|  | 	case *rsa.PrivateKey: | ||||||
|  | 		if 0 == mathrand.Intn(2) { | ||||||
|  | 			typ = "PRIVATE KEY" | ||||||
|  | 			bytes, err = x509.MarshalPKCS8PrivateKey(k) | ||||||
|  | 			if nil != err { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			typ = "RSA PRIVATE KEY" | ||||||
|  | 			bytes = x509.MarshalPKCS1PrivateKey(k) | ||||||
|  | 		} | ||||||
|  | 		return &pem.Block{ | ||||||
|  | 			Type:  typ, | ||||||
|  | 			Bytes: bytes, | ||||||
|  | 		}, nil | ||||||
|  | 	case *ecdsa.PrivateKey: | ||||||
|  | 		if 0 == mathrand.Intn(2) { | ||||||
|  | 			typ = "PRIVATE KEY" | ||||||
|  | 			bytes, err = x509.MarshalPKCS8PrivateKey(k) | ||||||
|  | 		} else { | ||||||
|  | 			typ = "EC PRIVATE KEY" | ||||||
|  | 			bytes, err = x509.MarshalECPrivateKey(k) | ||||||
|  | 		} | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return &pem.Block{ | ||||||
|  | 			Type:  typ, | ||||||
|  | 			Bytes: bytes, | ||||||
|  | 		}, nil | ||||||
|  | 	default: | ||||||
|  | 		// this is unreachable because we know the types that we pass in | ||||||
|  | 		log.Printf("keytype: %t, %+v\n", privkey, privkey) | ||||||
|  | 		panic(ErrInvalidPublicKey) | ||||||
|  | 		//return nil, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalPEMPrivateKey outputs the given private key as ASN.1 PEM | ||||||
|  | func MarshalPEMPrivateKey(privkey PrivateKey) ([]byte, error) { | ||||||
|  | 	block, err := marshalDERPrivateKey(privkey) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return pem.EncodeToMemory(block), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalECPrivateKey will output the given private key as JWK | ||||||
|  | func MarshalECPrivateKey(k *ecdsa.PrivateKey) []byte { | ||||||
|  | 	crv := k.Curve.Params().Name | ||||||
|  | 	d := base64.RawURLEncoding.EncodeToString(k.D.Bytes()) | ||||||
|  | 	x := base64.RawURLEncoding.EncodeToString(k.X.Bytes()) | ||||||
|  | 	y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes()) | ||||||
|  | 	return []byte(fmt.Sprintf( | ||||||
|  | 		`{"crv":%q,"d":%q,"kty":"EC","x":%q,"y":%q}`, | ||||||
|  | 		crv, d, x, y, | ||||||
|  | 	)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalRSAPrivateKey will output the given private key as JWK | ||||||
|  | func MarshalRSAPrivateKey(pk *rsa.PrivateKey) []byte { | ||||||
|  | 	e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pk.E)).Bytes()) | ||||||
|  | 	n := base64.RawURLEncoding.EncodeToString(pk.N.Bytes()) | ||||||
|  | 	d := base64.RawURLEncoding.EncodeToString(pk.D.Bytes()) | ||||||
|  | 	p := base64.RawURLEncoding.EncodeToString(pk.Primes[0].Bytes()) | ||||||
|  | 	q := base64.RawURLEncoding.EncodeToString(pk.Primes[1].Bytes()) | ||||||
|  | 	dp := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dp.Bytes()) | ||||||
|  | 	dq := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dq.Bytes()) | ||||||
|  | 	qi := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Qinv.Bytes()) | ||||||
|  | 	return []byte(fmt.Sprintf( | ||||||
|  | 		`{"d":%q,"dp":%q,"dq":%q,"e":%q,"kty":"RSA","n":%q,"p":%q,"q":%q,"qi":%q}`, | ||||||
|  | 		d, dp, dq, e, n, p, q, qi, | ||||||
|  | 	)) | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								vendor/git.rootprojects.org/root/keypairs/mock.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								vendor/git.rootprojects.org/root/keypairs/mock.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | package keypairs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"io" | ||||||
|  | 	"log" | ||||||
|  | 	mathrand "math/rand" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // this shananigans is only for testing and debug API stuff | ||||||
|  | func (o *keyOptions) maybeMockReader() io.Reader { | ||||||
|  | 	if !allowMocking { | ||||||
|  | 		panic("mock method called when mocking is not allowed") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if 0 == o.mockSeed { | ||||||
|  | 		return randReader | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	log.Println("WARNING: MOCK: using insecure reader") | ||||||
|  | 	return mathrand.New(mathrand.NewSource(o.mockSeed)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const maxRetry = 16 | ||||||
|  | 
 | ||||||
|  | func maybeDerandomizeMockKey(privkey PrivateKey, keylen int, opts *keyOptions) PrivateKey { | ||||||
|  | 	if 0 != opts.mockSeed { | ||||||
|  | 		for i := 0; i < maxRetry; i++ { | ||||||
|  | 			otherkey, _ := rsa.GenerateKey(opts.nextReader(), keylen) | ||||||
|  | 			otherCmp := otherkey.D.Cmp(privkey.(*rsa.PrivateKey).D) | ||||||
|  | 			if 0 != otherCmp { | ||||||
|  | 				// There are two possible keys, choose the lesser D value | ||||||
|  | 				// See https://github.com/square/go-jose/issues/189 | ||||||
|  | 				if otherCmp < 0 { | ||||||
|  | 					privkey = otherkey | ||||||
|  | 				} | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			if maxRetry == i-1 { | ||||||
|  | 				log.Printf("error: coinflip landed on heads %d times", maxRetry) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return privkey | ||||||
|  | } | ||||||
							
								
								
									
										165
									
								
								vendor/git.rootprojects.org/root/keypairs/sign.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								vendor/git.rootprojects.org/root/keypairs/sign.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,165 @@ | |||||||
|  | package keypairs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"crypto/sha256" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	mathrand "math/rand" // to be used for good, not evil | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Object is a type alias representing generic JSON data | ||||||
|  | type Object = map[string]interface{} | ||||||
|  | 
 | ||||||
|  | // SignClaims adds `typ`, `kid` (or `jwk`), and `alg` in the header and expects claims for `jti`, `exp`, `iss`, and `iat` | ||||||
|  | func SignClaims(privkey PrivateKey, header Object, claims Object) (*JWS, error) { | ||||||
|  | 	var randsrc io.Reader = randReader | ||||||
|  | 	seed, _ := header["_seed"].(int64) | ||||||
|  | 	if 0 != seed { | ||||||
|  | 		randsrc = mathrand.New(mathrand.NewSource(seed)) | ||||||
|  | 		//delete(header, "_seed") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	protected, header, err := headerToProtected(NewPublicKey(privkey.Public()), header) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	protected64 := base64.RawURLEncoding.EncodeToString(protected) | ||||||
|  | 
 | ||||||
|  | 	payload, err := claimsToPayload(claims) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	payload64 := base64.RawURLEncoding.EncodeToString(payload) | ||||||
|  | 
 | ||||||
|  | 	signable := fmt.Sprintf(`%s.%s`, protected64, payload64) | ||||||
|  | 	hash := sha256.Sum256([]byte(signable)) | ||||||
|  | 
 | ||||||
|  | 	sig := Sign(privkey, hash[:], randsrc) | ||||||
|  | 	sig64 := base64.RawURLEncoding.EncodeToString(sig) | ||||||
|  | 	//log.Printf("\n(Sign)\nSignable: %s", signable) | ||||||
|  | 	//log.Printf("Hash: %s", hash) | ||||||
|  | 	//log.Printf("Sig: %s", sig64) | ||||||
|  | 
 | ||||||
|  | 	return &JWS{ | ||||||
|  | 		Header:    header, | ||||||
|  | 		Claims:    claims, | ||||||
|  | 		Protected: protected64, | ||||||
|  | 		Payload:   payload64, | ||||||
|  | 		Signature: sig64, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func headerToProtected(pub PublicKey, header Object) ([]byte, Object, error) { | ||||||
|  | 	if nil == header { | ||||||
|  | 		header = Object{} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Only supporting 2048-bit and P256 keys right now | ||||||
|  | 	// because that's all that's practical and well-supported. | ||||||
|  | 	// No security theatre here. | ||||||
|  | 	alg := "ES256" | ||||||
|  | 	switch pub.Key().(type) { | ||||||
|  | 	case *rsa.PublicKey: | ||||||
|  | 		alg = "RS256" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if selfSign, _ := header["_jwk"].(bool); selfSign { | ||||||
|  | 		delete(header, "_jwk") | ||||||
|  | 		any := Object{} | ||||||
|  | 		_ = json.Unmarshal(MarshalJWKPublicKey(pub), &any) | ||||||
|  | 		header["jwk"] = any | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// TODO what are the acceptable values? JWT. JWS? others? | ||||||
|  | 	header["typ"] = "JWT" | ||||||
|  | 	if _, ok := header["jwk"]; !ok { | ||||||
|  | 		thumbprint := ThumbprintPublicKey(pub) | ||||||
|  | 		kid, _ := header["kid"].(string) | ||||||
|  | 		if "" != kid && thumbprint != kid { | ||||||
|  | 			return nil, nil, errors.New("'kid' should be the key's thumbprint") | ||||||
|  | 		} | ||||||
|  | 		header["kid"] = thumbprint | ||||||
|  | 	} | ||||||
|  | 	header["alg"] = alg | ||||||
|  | 
 | ||||||
|  | 	protected, err := json.Marshal(header) | ||||||
|  | 	if nil != err { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 	return protected, header, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func claimsToPayload(claims Object) ([]byte, error) { | ||||||
|  | 	if nil == claims { | ||||||
|  | 		claims = Object{} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var dur time.Duration | ||||||
|  | 	jti, _ := claims["jti"].(string) | ||||||
|  | 	insecure, _ := claims["insecure"].(bool) | ||||||
|  | 
 | ||||||
|  | 	switch exp := claims["exp"].(type) { | ||||||
|  | 	case time.Duration: | ||||||
|  | 		// TODO: MUST this go first? | ||||||
|  | 		// int64(time.Duration) vs time.Duration(int64) | ||||||
|  | 		dur = exp | ||||||
|  | 	case string: | ||||||
|  | 		var err error | ||||||
|  | 		dur, err = time.ParseDuration(exp) | ||||||
|  | 		// TODO s, err := time.ParseDuration(dur) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	case int: | ||||||
|  | 		dur = time.Second * time.Duration(exp) | ||||||
|  | 	case int64: | ||||||
|  | 		dur = time.Second * time.Duration(exp) | ||||||
|  | 	case float64: | ||||||
|  | 		dur = time.Second * time.Duration(exp) | ||||||
|  | 	default: | ||||||
|  | 		dur = 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if "" == jti && 0 == dur && !insecure { | ||||||
|  | 		return nil, errors.New("token must have jti or exp as to be expirable / cancellable") | ||||||
|  | 	} | ||||||
|  | 	claims["exp"] = time.Now().Add(dur).Unix() | ||||||
|  | 
 | ||||||
|  | 	return json.Marshal(claims) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sign signs both RSA and ECDSA. Use `nil` or `crypto/rand.Reader` except for debugging. | ||||||
|  | func Sign(privkey PrivateKey, hash []byte, rand io.Reader) []byte { | ||||||
|  | 	if nil == rand { | ||||||
|  | 		rand = randReader | ||||||
|  | 	} | ||||||
|  | 	var sig []byte | ||||||
|  | 
 | ||||||
|  | 	if len(hash) != 32 { | ||||||
|  | 		panic("only 256-bit hashes for 2048-bit and 256-bit keys are supported") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch k := privkey.(type) { | ||||||
|  | 	case *rsa.PrivateKey: | ||||||
|  | 		sig, _ = rsa.SignPKCS1v15(rand, k, crypto.SHA256, hash) | ||||||
|  | 	case *ecdsa.PrivateKey: | ||||||
|  | 		r, s, _ := ecdsa.Sign(rand, k, hash[:]) | ||||||
|  | 		rb := r.Bytes() | ||||||
|  | 		for len(rb) < 32 { | ||||||
|  | 			rb = append([]byte{0}, rb...) | ||||||
|  | 		} | ||||||
|  | 		sb := s.Bytes() | ||||||
|  | 		for len(rb) < 32 { | ||||||
|  | 			sb = append([]byte{0}, sb...) | ||||||
|  | 		} | ||||||
|  | 		sig = append(rb, sb...) | ||||||
|  | 	} | ||||||
|  | 	return sig | ||||||
|  | } | ||||||
							
								
								
									
										174
									
								
								vendor/git.rootprojects.org/root/keypairs/verify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								vendor/git.rootprojects.org/root/keypairs/verify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | |||||||
|  | package keypairs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"crypto/sha256" | ||||||
|  | 	"crypto/subtle" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"math/big" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // VerifyClaims will check the signature of a parsed JWT | ||||||
|  | func VerifyClaims(pubkey PublicKey, jws *JWS) (errs []error) { | ||||||
|  | 	kid, _ := jws.Header["kid"].(string) | ||||||
|  | 	jwkmap, hasJWK := jws.Header["jwk"].(Object) | ||||||
|  | 	//var jwk JWK = nil | ||||||
|  | 
 | ||||||
|  | 	seed, _ := jws.Header["_seed"].(int64) | ||||||
|  | 	seedf64, _ := jws.Header["_seed"].(float64) | ||||||
|  | 	kty, _ := jws.Header["_kty"].(string) | ||||||
|  | 	if 0 == seed { | ||||||
|  | 		seed = int64(seedf64) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var pub PublicKey = nil | ||||||
|  | 	if hasJWK { | ||||||
|  | 		pub, errs = selfsignCheck(jwkmap, errs) | ||||||
|  | 	} else { | ||||||
|  | 		opts := &keyOptions{mockSeed: seed, KeyType: kty} | ||||||
|  | 		pub, errs = pubkeyCheck(pubkey, kid, opts, errs) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	jti, _ := jws.Claims["jti"].(string) | ||||||
|  | 	expf64, _ := jws.Claims["exp"].(float64) | ||||||
|  | 	exp := int64(expf64) | ||||||
|  | 	if 0 == exp { | ||||||
|  | 		if "" == jti { | ||||||
|  | 			err := errors.New("one of 'jti' or 'exp' must exist for token expiry") | ||||||
|  | 			errs = append(errs, err) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if time.Now().Unix() > exp { | ||||||
|  | 			err := fmt.Errorf("token expired at %d (%s)", exp, time.Unix(exp, 0)) | ||||||
|  | 			errs = append(errs, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	signable := fmt.Sprintf("%s.%s", jws.Protected, jws.Payload) | ||||||
|  | 	hash := sha256.Sum256([]byte(signable)) | ||||||
|  | 	sig, err := base64.RawURLEncoding.DecodeString(jws.Signature) | ||||||
|  | 	if nil != err { | ||||||
|  | 		err := fmt.Errorf("could not decode signature: %w", err) | ||||||
|  | 		errs = append(errs, err) | ||||||
|  | 		return errs | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	//log.Printf("\n(Verify)\nSignable: %s", signable) | ||||||
|  | 	//log.Printf("Hash: %s", hash) | ||||||
|  | 	//log.Printf("Sig: %s", jws.Signature) | ||||||
|  | 	if nil == pub { | ||||||
|  | 		err := fmt.Errorf("token signature could not be verified") | ||||||
|  | 		errs = append(errs, err) | ||||||
|  | 	} else if !Verify(pub, hash[:], sig) { | ||||||
|  | 		err := fmt.Errorf("token signature is not valid") | ||||||
|  | 		errs = append(errs, err) | ||||||
|  | 	} | ||||||
|  | 	return errs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func selfsignCheck(jwkmap Object, errs []error) (PublicKey, []error) { | ||||||
|  | 	var pub PublicKey = nil | ||||||
|  | 	log.Println("Security TODO: did not check jws.Claims[\"sub\"] against 'jwk'") | ||||||
|  | 	log.Println("Security TODO: did not check jws.Claims[\"iss\"]") | ||||||
|  | 	kty := jwkmap["kty"] | ||||||
|  | 	var err error | ||||||
|  | 	if "RSA" == kty { | ||||||
|  | 		e, _ := jwkmap["e"].(string) | ||||||
|  | 		n, _ := jwkmap["n"].(string) | ||||||
|  | 		k, _ := (&RSAJWK{ | ||||||
|  | 			Exp: e, | ||||||
|  | 			N:   n, | ||||||
|  | 		}).marshalJWK() | ||||||
|  | 		pub, err = ParseJWKPublicKey(k) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, append(errs, err) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		crv, _ := jwkmap["crv"].(string) | ||||||
|  | 		x, _ := jwkmap["x"].(string) | ||||||
|  | 		y, _ := jwkmap["y"].(string) | ||||||
|  | 		k, _ := (&ECJWK{ | ||||||
|  | 			Curve: crv, | ||||||
|  | 			X:     x, | ||||||
|  | 			Y:     y, | ||||||
|  | 		}).marshalJWK() | ||||||
|  | 		pub, err = ParseJWKPublicKey(k) | ||||||
|  | 		if nil != err { | ||||||
|  | 			return nil, append(errs, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return pub, errs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func pubkeyCheck(pubkey PublicKey, kid string, opts *keyOptions, errs []error) (PublicKey, []error) { | ||||||
|  | 	var pub PublicKey = nil | ||||||
|  | 
 | ||||||
|  | 	if "" == kid { | ||||||
|  | 		err := errors.New("token should have 'kid' or 'jwk' in header to identify the public key") | ||||||
|  | 		errs = append(errs, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if nil == pubkey { | ||||||
|  | 		if allowMocking { | ||||||
|  | 			if 0 == opts.mockSeed { | ||||||
|  | 				err := errors.New("the debug API requires '_seed' to accompany 'kid'") | ||||||
|  | 				errs = append(errs, err) | ||||||
|  | 			} | ||||||
|  | 			if "" == opts.KeyType { | ||||||
|  | 				err := errors.New("the debug API requires '_kty' to accompany '_seed'") | ||||||
|  | 				errs = append(errs, err) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if 0 == opts.mockSeed || "" == opts.KeyType { | ||||||
|  | 				return nil, errs | ||||||
|  | 			} | ||||||
|  | 			privkey := newPrivateKey(opts) | ||||||
|  | 			pub = NewPublicKey(privkey.Public()) | ||||||
|  | 			return pub, errs | ||||||
|  | 		} | ||||||
|  | 		err := errors.New("no matching public key") | ||||||
|  | 		errs = append(errs, err) | ||||||
|  | 	} else { | ||||||
|  | 		pub = pubkey | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if nil != pub && "" != kid { | ||||||
|  | 		if 1 != subtle.ConstantTimeCompare([]byte(kid), []byte(pub.Thumbprint())) { | ||||||
|  | 			err := errors.New("'kid' does not match the public key thumbprint") | ||||||
|  | 			errs = append(errs, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return pub, errs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Verify will check the signature of a hash | ||||||
|  | func Verify(pubkey PublicKey, hash []byte, sig []byte) bool { | ||||||
|  | 
 | ||||||
|  | 	switch pub := pubkey.Key().(type) { | ||||||
|  | 	case *rsa.PublicKey: | ||||||
|  | 		//log.Printf("RSA VERIFY") | ||||||
|  | 		// TODO Size(key) to detect key size ? | ||||||
|  | 		//alg := "SHA256" | ||||||
|  | 		// TODO: this hasn't been tested yet | ||||||
|  | 		if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash, sig); nil != err { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		return true | ||||||
|  | 	case *ecdsa.PublicKey: | ||||||
|  | 		r := &big.Int{} | ||||||
|  | 		r.SetBytes(sig[0:32]) | ||||||
|  | 		s := &big.Int{} | ||||||
|  | 		s.SetBytes(sig[32:]) | ||||||
|  | 		return ecdsa.Verify(pub, hash, r, s) | ||||||
|  | 	default: | ||||||
|  | 		panic("impossible condition: non-rsa/non-ecdsa key") | ||||||
|  | 		//return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								vendor/golang.org/x/mod/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/golang.org/x/mod/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | Copyright (c) 2009 The Go 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 Google Inc. nor the names of its | ||||||
|  | contributors may be used to endorse or promote products derived from | ||||||
|  | this software without specific prior written permission. | ||||||
|  | 
 | ||||||
|  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||||
|  | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||||
|  | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||||
|  | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||||
|  | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||
|  | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||||
|  | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
							
								
								
									
										22
									
								
								vendor/golang.org/x/mod/PATENTS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/golang.org/x/mod/PATENTS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | Additional IP Rights Grant (Patents) | ||||||
|  | 
 | ||||||
|  | "This implementation" means the copyrightable works distributed by | ||||||
|  | Google as part of the Go project. | ||||||
|  | 
 | ||||||
|  | Google hereby grants to You a perpetual, worldwide, non-exclusive, | ||||||
|  | no-charge, royalty-free, irrevocable (except as stated in this section) | ||||||
|  | patent license to make, have made, use, offer to sell, sell, import, | ||||||
|  | transfer and otherwise run, modify and propagate the contents of this | ||||||
|  | implementation of Go, where such license applies only to those patent | ||||||
|  | claims, both currently owned or controlled by Google and acquired in | ||||||
|  | the future, licensable by Google that are necessarily infringed by this | ||||||
|  | implementation of Go.  This grant does not include claims that would be | ||||||
|  | infringed only as a consequence of further modification of this | ||||||
|  | implementation.  If you or your agent or exclusive licensee institute or | ||||||
|  | order or agree to the institution of patent litigation against any | ||||||
|  | entity (including a cross-claim or counterclaim in a lawsuit) alleging | ||||||
|  | that this implementation of Go or any code incorporated within this | ||||||
|  | implementation of Go constitutes direct or contributory patent | ||||||
|  | infringement, or inducement of patent infringement, then any patent | ||||||
|  | rights granted to you under this License for this implementation of Go | ||||||
|  | shall terminate as of the date such litigation is filed. | ||||||
							
								
								
									
										388
									
								
								vendor/golang.org/x/mod/semver/semver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										388
									
								
								vendor/golang.org/x/mod/semver/semver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,388 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | // Package semver implements comparison of semantic version strings. | ||||||
|  | // In this package, semantic version strings must begin with a leading "v", | ||||||
|  | // as in "v1.0.0". | ||||||
|  | // | ||||||
|  | // The general form of a semantic version string accepted by this package is | ||||||
|  | // | ||||||
|  | //	vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] | ||||||
|  | // | ||||||
|  | // where square brackets indicate optional parts of the syntax; | ||||||
|  | // MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; | ||||||
|  | // PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers | ||||||
|  | // using only alphanumeric characters and hyphens; and | ||||||
|  | // all-numeric PRERELEASE identifiers must not have leading zeros. | ||||||
|  | // | ||||||
|  | // This package follows Semantic Versioning 2.0.0 (see semver.org) | ||||||
|  | // with two exceptions. First, it requires the "v" prefix. Second, it recognizes | ||||||
|  | // vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) | ||||||
|  | // as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. | ||||||
|  | package semver | ||||||
|  | 
 | ||||||
|  | // parsed returns the parsed form of a semantic version string. | ||||||
|  | type parsed struct { | ||||||
|  | 	major      string | ||||||
|  | 	minor      string | ||||||
|  | 	patch      string | ||||||
|  | 	short      string | ||||||
|  | 	prerelease string | ||||||
|  | 	build      string | ||||||
|  | 	err        string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsValid reports whether v is a valid semantic version string. | ||||||
|  | func IsValid(v string) bool { | ||||||
|  | 	_, ok := parse(v) | ||||||
|  | 	return ok | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Canonical returns the canonical formatting of the semantic version v. | ||||||
|  | // It fills in any missing .MINOR or .PATCH and discards build metadata. | ||||||
|  | // Two semantic versions compare equal only if their canonical formattings | ||||||
|  | // are identical strings. | ||||||
|  | // The canonical invalid semantic version is the empty string. | ||||||
|  | func Canonical(v string) string { | ||||||
|  | 	p, ok := parse(v) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	if p.build != "" { | ||||||
|  | 		return v[:len(v)-len(p.build)] | ||||||
|  | 	} | ||||||
|  | 	if p.short != "" { | ||||||
|  | 		return v + p.short | ||||||
|  | 	} | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Major returns the major version prefix of the semantic version v. | ||||||
|  | // For example, Major("v2.1.0") == "v2". | ||||||
|  | // If v is an invalid semantic version string, Major returns the empty string. | ||||||
|  | func Major(v string) string { | ||||||
|  | 	pv, ok := parse(v) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return v[:1+len(pv.major)] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MajorMinor returns the major.minor version prefix of the semantic version v. | ||||||
|  | // For example, MajorMinor("v2.1.0") == "v2.1". | ||||||
|  | // If v is an invalid semantic version string, MajorMinor returns the empty string. | ||||||
|  | func MajorMinor(v string) string { | ||||||
|  | 	pv, ok := parse(v) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	i := 1 + len(pv.major) | ||||||
|  | 	if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor { | ||||||
|  | 		return v[:j] | ||||||
|  | 	} | ||||||
|  | 	return v[:i] + "." + pv.minor | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Prerelease returns the prerelease suffix of the semantic version v. | ||||||
|  | // For example, Prerelease("v2.1.0-pre+meta") == "-pre". | ||||||
|  | // If v is an invalid semantic version string, Prerelease returns the empty string. | ||||||
|  | func Prerelease(v string) string { | ||||||
|  | 	pv, ok := parse(v) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return pv.prerelease | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Build returns the build suffix of the semantic version v. | ||||||
|  | // For example, Build("v2.1.0+meta") == "+meta". | ||||||
|  | // If v is an invalid semantic version string, Build returns the empty string. | ||||||
|  | func Build(v string) string { | ||||||
|  | 	pv, ok := parse(v) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return pv.build | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Compare returns an integer comparing two versions according to | ||||||
|  | // semantic version precedence. | ||||||
|  | // The result will be 0 if v == w, -1 if v < w, or +1 if v > w. | ||||||
|  | // | ||||||
|  | // An invalid semantic version string is considered less than a valid one. | ||||||
|  | // All invalid semantic version strings compare equal to each other. | ||||||
|  | func Compare(v, w string) int { | ||||||
|  | 	pv, ok1 := parse(v) | ||||||
|  | 	pw, ok2 := parse(w) | ||||||
|  | 	if !ok1 && !ok2 { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	if !ok1 { | ||||||
|  | 		return -1 | ||||||
|  | 	} | ||||||
|  | 	if !ok2 { | ||||||
|  | 		return +1 | ||||||
|  | 	} | ||||||
|  | 	if c := compareInt(pv.major, pw.major); c != 0 { | ||||||
|  | 		return c | ||||||
|  | 	} | ||||||
|  | 	if c := compareInt(pv.minor, pw.minor); c != 0 { | ||||||
|  | 		return c | ||||||
|  | 	} | ||||||
|  | 	if c := compareInt(pv.patch, pw.patch); c != 0 { | ||||||
|  | 		return c | ||||||
|  | 	} | ||||||
|  | 	return comparePrerelease(pv.prerelease, pw.prerelease) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Max canonicalizes its arguments and then returns the version string | ||||||
|  | // that compares greater. | ||||||
|  | func Max(v, w string) string { | ||||||
|  | 	v = Canonical(v) | ||||||
|  | 	w = Canonical(w) | ||||||
|  | 	if Compare(v, w) > 0 { | ||||||
|  | 		return v | ||||||
|  | 	} | ||||||
|  | 	return w | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parse(v string) (p parsed, ok bool) { | ||||||
|  | 	if v == "" || v[0] != 'v' { | ||||||
|  | 		p.err = "missing v prefix" | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	p.major, v, ok = parseInt(v[1:]) | ||||||
|  | 	if !ok { | ||||||
|  | 		p.err = "bad major version" | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if v == "" { | ||||||
|  | 		p.minor = "0" | ||||||
|  | 		p.patch = "0" | ||||||
|  | 		p.short = ".0.0" | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if v[0] != '.' { | ||||||
|  | 		p.err = "bad minor prefix" | ||||||
|  | 		ok = false | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	p.minor, v, ok = parseInt(v[1:]) | ||||||
|  | 	if !ok { | ||||||
|  | 		p.err = "bad minor version" | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if v == "" { | ||||||
|  | 		p.patch = "0" | ||||||
|  | 		p.short = ".0" | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if v[0] != '.' { | ||||||
|  | 		p.err = "bad patch prefix" | ||||||
|  | 		ok = false | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	p.patch, v, ok = parseInt(v[1:]) | ||||||
|  | 	if !ok { | ||||||
|  | 		p.err = "bad patch version" | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if len(v) > 0 && v[0] == '-' { | ||||||
|  | 		p.prerelease, v, ok = parsePrerelease(v) | ||||||
|  | 		if !ok { | ||||||
|  | 			p.err = "bad prerelease" | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(v) > 0 && v[0] == '+' { | ||||||
|  | 		p.build, v, ok = parseBuild(v) | ||||||
|  | 		if !ok { | ||||||
|  | 			p.err = "bad build" | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if v != "" { | ||||||
|  | 		p.err = "junk on end" | ||||||
|  | 		ok = false | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ok = true | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseInt(v string) (t, rest string, ok bool) { | ||||||
|  | 	if v == "" { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if v[0] < '0' || '9' < v[0] { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	i := 1 | ||||||
|  | 	for i < len(v) && '0' <= v[i] && v[i] <= '9' { | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	if v[0] == '0' && i != 1 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	return v[:i], v[i:], true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parsePrerelease(v string) (t, rest string, ok bool) { | ||||||
|  | 	// "A pre-release version MAY be denoted by appending a hyphen and | ||||||
|  | 	// a series of dot separated identifiers immediately following the patch version. | ||||||
|  | 	// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. | ||||||
|  | 	// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." | ||||||
|  | 	if v == "" || v[0] != '-' { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	i := 1 | ||||||
|  | 	start := 1 | ||||||
|  | 	for i < len(v) && v[i] != '+' { | ||||||
|  | 		if !isIdentChar(v[i]) && v[i] != '.' { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if v[i] == '.' { | ||||||
|  | 			if start == i || isBadNum(v[start:i]) { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			start = i + 1 | ||||||
|  | 		} | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	if start == i || isBadNum(v[start:i]) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	return v[:i], v[i:], true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseBuild(v string) (t, rest string, ok bool) { | ||||||
|  | 	if v == "" || v[0] != '+' { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	i := 1 | ||||||
|  | 	start := 1 | ||||||
|  | 	for i < len(v) { | ||||||
|  | 		if !isIdentChar(v[i]) && v[i] != '.' { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if v[i] == '.' { | ||||||
|  | 			if start == i { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			start = i + 1 | ||||||
|  | 		} | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	if start == i { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	return v[:i], v[i:], true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isIdentChar(c byte) bool { | ||||||
|  | 	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isBadNum(v string) bool { | ||||||
|  | 	i := 0 | ||||||
|  | 	for i < len(v) && '0' <= v[i] && v[i] <= '9' { | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	return i == len(v) && i > 1 && v[0] == '0' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isNum(v string) bool { | ||||||
|  | 	i := 0 | ||||||
|  | 	for i < len(v) && '0' <= v[i] && v[i] <= '9' { | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	return i == len(v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func compareInt(x, y string) int { | ||||||
|  | 	if x == y { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	if len(x) < len(y) { | ||||||
|  | 		return -1 | ||||||
|  | 	} | ||||||
|  | 	if len(x) > len(y) { | ||||||
|  | 		return +1 | ||||||
|  | 	} | ||||||
|  | 	if x < y { | ||||||
|  | 		return -1 | ||||||
|  | 	} else { | ||||||
|  | 		return +1 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func comparePrerelease(x, y string) int { | ||||||
|  | 	// "When major, minor, and patch are equal, a pre-release version has | ||||||
|  | 	// lower precedence than a normal version. | ||||||
|  | 	// Example: 1.0.0-alpha < 1.0.0. | ||||||
|  | 	// Precedence for two pre-release versions with the same major, minor, | ||||||
|  | 	// and patch version MUST be determined by comparing each dot separated | ||||||
|  | 	// identifier from left to right until a difference is found as follows: | ||||||
|  | 	// identifiers consisting of only digits are compared numerically and | ||||||
|  | 	// identifiers with letters or hyphens are compared lexically in ASCII | ||||||
|  | 	// sort order. Numeric identifiers always have lower precedence than | ||||||
|  | 	// non-numeric identifiers. A larger set of pre-release fields has a | ||||||
|  | 	// higher precedence than a smaller set, if all of the preceding | ||||||
|  | 	// identifiers are equal. | ||||||
|  | 	// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < | ||||||
|  | 	// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." | ||||||
|  | 	if x == y { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	if x == "" { | ||||||
|  | 		return +1 | ||||||
|  | 	} | ||||||
|  | 	if y == "" { | ||||||
|  | 		return -1 | ||||||
|  | 	} | ||||||
|  | 	for x != "" && y != "" { | ||||||
|  | 		x = x[1:] // skip - or . | ||||||
|  | 		y = y[1:] // skip - or . | ||||||
|  | 		var dx, dy string | ||||||
|  | 		dx, x = nextIdent(x) | ||||||
|  | 		dy, y = nextIdent(y) | ||||||
|  | 		if dx != dy { | ||||||
|  | 			ix := isNum(dx) | ||||||
|  | 			iy := isNum(dy) | ||||||
|  | 			if ix != iy { | ||||||
|  | 				if ix { | ||||||
|  | 					return -1 | ||||||
|  | 				} else { | ||||||
|  | 					return +1 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if ix { | ||||||
|  | 				if len(dx) < len(dy) { | ||||||
|  | 					return -1 | ||||||
|  | 				} | ||||||
|  | 				if len(dx) > len(dy) { | ||||||
|  | 					return +1 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if dx < dy { | ||||||
|  | 				return -1 | ||||||
|  | 			} else { | ||||||
|  | 				return +1 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if x == "" { | ||||||
|  | 		return -1 | ||||||
|  | 	} else { | ||||||
|  | 		return +1 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func nextIdent(x string) (dx, rest string) { | ||||||
|  | 	i := 0 | ||||||
|  | 	for i < len(x) && x[i] != '.' { | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	return x[:i], x[i:] | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								vendor/golang.org/x/tools/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/golang.org/x/tools/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | # This source code refers to The Go Authors for copyright purposes. | ||||||
|  | # The master list of authors is in the main Go distribution, | ||||||
|  | # visible at http://tip.golang.org/AUTHORS. | ||||||
							
								
								
									
										3
									
								
								vendor/golang.org/x/tools/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/golang.org/x/tools/CONTRIBUTORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | # This source code was written by the Go contributors. | ||||||
|  | # The master list of contributors is in the main Go distribution, | ||||||
|  | # visible at http://tip.golang.org/CONTRIBUTORS. | ||||||
							
								
								
									
										27
									
								
								vendor/golang.org/x/tools/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/golang.org/x/tools/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | Copyright (c) 2009 The Go 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 Google Inc. nor the names of its | ||||||
|  | contributors may be used to endorse or promote products derived from | ||||||
|  | this software without specific prior written permission. | ||||||
|  | 
 | ||||||
|  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||||
|  | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||||
|  | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||||
|  | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||||
|  | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||
|  | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||||
|  | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
							
								
								
									
										22
									
								
								vendor/golang.org/x/tools/PATENTS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/golang.org/x/tools/PATENTS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | Additional IP Rights Grant (Patents) | ||||||
|  | 
 | ||||||
|  | "This implementation" means the copyrightable works distributed by | ||||||
|  | Google as part of the Go project. | ||||||
|  | 
 | ||||||
|  | Google hereby grants to You a perpetual, worldwide, non-exclusive, | ||||||
|  | no-charge, royalty-free, irrevocable (except as stated in this section) | ||||||
|  | patent license to make, have made, use, offer to sell, sell, import, | ||||||
|  | transfer and otherwise run, modify and propagate the contents of this | ||||||
|  | implementation of Go, where such license applies only to those patent | ||||||
|  | claims, both currently owned or controlled by Google and acquired in | ||||||
|  | the future, licensable by Google that are necessarily infringed by this | ||||||
|  | implementation of Go.  This grant does not include claims that would be | ||||||
|  | infringed only as a consequence of further modification of this | ||||||
|  | implementation.  If you or your agent or exclusive licensee institute or | ||||||
|  | order or agree to the institution of patent litigation against any | ||||||
|  | entity (including a cross-claim or counterclaim in a lawsuit) alleging | ||||||
|  | that this implementation of Go or any code incorporated within this | ||||||
|  | implementation of Go constitutes direct or contributory patent | ||||||
|  | infringement, or inducement of patent infringement, then any patent | ||||||
|  | rights granted to you under this License for this implementation of Go | ||||||
|  | shall terminate as of the date such litigation is filed. | ||||||
							
								
								
									
										655
									
								
								vendor/golang.org/x/tools/cmd/stringer/stringer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										655
									
								
								vendor/golang.org/x/tools/cmd/stringer/stringer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,655 @@ | |||||||
|  | // Copyright 2014 The Go Authors. All rights reserved. | ||||||
|  | // Use of this source code is governed by a BSD-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
|  | 
 | ||||||
|  | // Stringer is a tool to automate the creation of methods that satisfy the fmt.Stringer | ||||||
|  | // interface. Given the name of a (signed or unsigned) integer type T that has constants | ||||||
|  | // defined, stringer will create a new self-contained Go source file implementing | ||||||
|  | //	func (t T) String() string | ||||||
|  | // The file is created in the same package and directory as the package that defines T. | ||||||
|  | // It has helpful defaults designed for use with go generate. | ||||||
|  | // | ||||||
|  | // Stringer works best with constants that are consecutive values such as created using iota, | ||||||
|  | // but creates good code regardless. In the future it might also provide custom support for | ||||||
|  | // constant sets that are bit patterns. | ||||||
|  | // | ||||||
|  | // For example, given this snippet, | ||||||
|  | // | ||||||
|  | //	package painkiller | ||||||
|  | // | ||||||
|  | //	type Pill int | ||||||
|  | // | ||||||
|  | //	const ( | ||||||
|  | //		Placebo Pill = iota | ||||||
|  | //		Aspirin | ||||||
|  | //		Ibuprofen | ||||||
|  | //		Paracetamol | ||||||
|  | //		Acetaminophen = Paracetamol | ||||||
|  | //	) | ||||||
|  | // | ||||||
|  | // running this command | ||||||
|  | // | ||||||
|  | //	stringer -type=Pill | ||||||
|  | // | ||||||
|  | // in the same directory will create the file pill_string.go, in package painkiller, | ||||||
|  | // containing a definition of | ||||||
|  | // | ||||||
|  | //	func (Pill) String() string | ||||||
|  | // | ||||||
|  | // That method will translate the value of a Pill constant to the string representation | ||||||
|  | // of the respective constant name, so that the call fmt.Print(painkiller.Aspirin) will | ||||||
|  | // print the string "Aspirin". | ||||||
|  | // | ||||||
|  | // Typically this process would be run using go generate, like this: | ||||||
|  | // | ||||||
|  | //	//go:generate stringer -type=Pill | ||||||
|  | // | ||||||
|  | // If multiple constants have the same value, the lexically first matching name will | ||||||
|  | // be used (in the example, Acetaminophen will print as "Paracetamol"). | ||||||
|  | // | ||||||
|  | // With no arguments, it processes the package in the current directory. | ||||||
|  | // Otherwise, the arguments must name a single directory holding a Go package | ||||||
|  | // or a set of Go source files that represent a single Go package. | ||||||
|  | // | ||||||
|  | // The -type flag accepts a comma-separated list of types so a single run can | ||||||
|  | // generate methods for multiple types. The default output file is t_string.go, | ||||||
|  | // where t is the lower-cased name of the first type listed. It can be overridden | ||||||
|  | // with the -output flag. | ||||||
|  | // | ||||||
|  | // The -linecomment flag tells stringer to generate the text of any line comment, trimmed | ||||||
|  | // of leading spaces, instead of the constant name. For instance, if the constants above had a | ||||||
|  | // Pill prefix, one could write | ||||||
|  | // | ||||||
|  | //	PillAspirin // Aspirin | ||||||
|  | // | ||||||
|  | // to suppress it in the output. | ||||||
|  | package main // import "golang.org/x/tools/cmd/stringer" | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/constant" | ||||||
|  | 	"go/format" | ||||||
|  | 	"go/token" | ||||||
|  | 	"go/types" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"sort" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/packages" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	typeNames   = flag.String("type", "", "comma-separated list of type names; must be set") | ||||||
|  | 	output      = flag.String("output", "", "output file name; default srcdir/<type>_string.go") | ||||||
|  | 	trimprefix  = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") | ||||||
|  | 	linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") | ||||||
|  | 	buildTags   = flag.String("tags", "", "comma-separated list of build tags to apply") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Usage is a replacement usage function for the flags package. | ||||||
|  | func Usage() { | ||||||
|  | 	fmt.Fprintf(os.Stderr, "Usage of stringer:\n") | ||||||
|  | 	fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n") | ||||||
|  | 	fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T files... # Must be a single package\n") | ||||||
|  | 	fmt.Fprintf(os.Stderr, "For more information, see:\n") | ||||||
|  | 	fmt.Fprintf(os.Stderr, "\thttp://godoc.org/golang.org/x/tools/cmd/stringer\n") | ||||||
|  | 	fmt.Fprintf(os.Stderr, "Flags:\n") | ||||||
|  | 	flag.PrintDefaults() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	log.SetFlags(0) | ||||||
|  | 	log.SetPrefix("stringer: ") | ||||||
|  | 	flag.Usage = Usage | ||||||
|  | 	flag.Parse() | ||||||
|  | 	if len(*typeNames) == 0 { | ||||||
|  | 		flag.Usage() | ||||||
|  | 		os.Exit(2) | ||||||
|  | 	} | ||||||
|  | 	types := strings.Split(*typeNames, ",") | ||||||
|  | 	var tags []string | ||||||
|  | 	if len(*buildTags) > 0 { | ||||||
|  | 		tags = strings.Split(*buildTags, ",") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We accept either one directory or a list of files. Which do we have? | ||||||
|  | 	args := flag.Args() | ||||||
|  | 	if len(args) == 0 { | ||||||
|  | 		// Default: process whole package in current directory. | ||||||
|  | 		args = []string{"."} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Parse the package once. | ||||||
|  | 	var dir string | ||||||
|  | 	g := Generator{ | ||||||
|  | 		trimPrefix:  *trimprefix, | ||||||
|  | 		lineComment: *linecomment, | ||||||
|  | 	} | ||||||
|  | 	// TODO(suzmue): accept other patterns for packages (directories, list of files, import paths, etc). | ||||||
|  | 	if len(args) == 1 && isDirectory(args[0]) { | ||||||
|  | 		dir = args[0] | ||||||
|  | 	} else { | ||||||
|  | 		if len(tags) != 0 { | ||||||
|  | 			log.Fatal("-tags option applies only to directories, not when files are specified") | ||||||
|  | 		} | ||||||
|  | 		dir = filepath.Dir(args[0]) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	g.parsePackage(args, tags) | ||||||
|  | 
 | ||||||
|  | 	// Print the header and package clause. | ||||||
|  | 	g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) | ||||||
|  | 	g.Printf("\n") | ||||||
|  | 	g.Printf("package %s", g.pkg.name) | ||||||
|  | 	g.Printf("\n") | ||||||
|  | 	g.Printf("import \"strconv\"\n") // Used by all methods. | ||||||
|  | 
 | ||||||
|  | 	// Run generate for each type. | ||||||
|  | 	for _, typeName := range types { | ||||||
|  | 		g.generate(typeName) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Format the output. | ||||||
|  | 	src := g.format() | ||||||
|  | 
 | ||||||
|  | 	// Write to file. | ||||||
|  | 	outputName := *output | ||||||
|  | 	if outputName == "" { | ||||||
|  | 		baseName := fmt.Sprintf("%s_string.go", types[0]) | ||||||
|  | 		outputName = filepath.Join(dir, strings.ToLower(baseName)) | ||||||
|  | 	} | ||||||
|  | 	err := ioutil.WriteFile(outputName, src, 0644) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("writing output: %s", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isDirectory reports whether the named file is a directory. | ||||||
|  | func isDirectory(name string) bool { | ||||||
|  | 	info, err := os.Stat(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	return info.IsDir() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Generator holds the state of the analysis. Primarily used to buffer | ||||||
|  | // the output for format.Source. | ||||||
|  | type Generator struct { | ||||||
|  | 	buf bytes.Buffer // Accumulated output. | ||||||
|  | 	pkg *Package     // Package we are scanning. | ||||||
|  | 
 | ||||||
|  | 	trimPrefix  string | ||||||
|  | 	lineComment bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (g *Generator) Printf(format string, args ...interface{}) { | ||||||
|  | 	fmt.Fprintf(&g.buf, format, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // File holds a single parsed file and associated data. | ||||||
|  | type File struct { | ||||||
|  | 	pkg  *Package  // Package to which this file belongs. | ||||||
|  | 	file *ast.File // Parsed AST. | ||||||
|  | 	// These fields are reset for each type being generated. | ||||||
|  | 	typeName string  // Name of the constant type. | ||||||
|  | 	values   []Value // Accumulator for constant values of that type. | ||||||
|  | 
 | ||||||
|  | 	trimPrefix  string | ||||||
|  | 	lineComment bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Package struct { | ||||||
|  | 	name  string | ||||||
|  | 	defs  map[*ast.Ident]types.Object | ||||||
|  | 	files []*File | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // parsePackage analyzes the single package constructed from the patterns and tags. | ||||||
|  | // parsePackage exits if there is an error. | ||||||
|  | func (g *Generator) parsePackage(patterns []string, tags []string) { | ||||||
|  | 	cfg := &packages.Config{ | ||||||
|  | 		Mode: packages.LoadSyntax, | ||||||
|  | 		// TODO: Need to think about constants in test files. Maybe write type_string_test.go | ||||||
|  | 		// in a separate pass? For later. | ||||||
|  | 		Tests:      false, | ||||||
|  | 		BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))}, | ||||||
|  | 	} | ||||||
|  | 	pkgs, err := packages.Load(cfg, patterns...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if len(pkgs) != 1 { | ||||||
|  | 		log.Fatalf("error: %d packages found", len(pkgs)) | ||||||
|  | 	} | ||||||
|  | 	g.addPackage(pkgs[0]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // addPackage adds a type checked Package and its syntax files to the generator. | ||||||
|  | func (g *Generator) addPackage(pkg *packages.Package) { | ||||||
|  | 	g.pkg = &Package{ | ||||||
|  | 		name:  pkg.Name, | ||||||
|  | 		defs:  pkg.TypesInfo.Defs, | ||||||
|  | 		files: make([]*File, len(pkg.Syntax)), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, file := range pkg.Syntax { | ||||||
|  | 		g.pkg.files[i] = &File{ | ||||||
|  | 			file:        file, | ||||||
|  | 			pkg:         g.pkg, | ||||||
|  | 			trimPrefix:  g.trimPrefix, | ||||||
|  | 			lineComment: g.lineComment, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // generate produces the String method for the named type. | ||||||
|  | func (g *Generator) generate(typeName string) { | ||||||
|  | 	values := make([]Value, 0, 100) | ||||||
|  | 	for _, file := range g.pkg.files { | ||||||
|  | 		// Set the state for this run of the walker. | ||||||
|  | 		file.typeName = typeName | ||||||
|  | 		file.values = nil | ||||||
|  | 		if file.file != nil { | ||||||
|  | 			ast.Inspect(file.file, file.genDecl) | ||||||
|  | 			values = append(values, file.values...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(values) == 0 { | ||||||
|  | 		log.Fatalf("no values defined for type %s", typeName) | ||||||
|  | 	} | ||||||
|  | 	// Generate code that will fail if the constants change value. | ||||||
|  | 	g.Printf("func _() {\n") | ||||||
|  | 	g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n") | ||||||
|  | 	g.Printf("\t// Re-run the stringer command to generate them again.\n") | ||||||
|  | 	g.Printf("\tvar x [1]struct{}\n") | ||||||
|  | 	for _, v := range values { | ||||||
|  | 		g.Printf("\t_ = x[%s - %s]\n", v.originalName, v.str) | ||||||
|  | 	} | ||||||
|  | 	g.Printf("}\n") | ||||||
|  | 	runs := splitIntoRuns(values) | ||||||
|  | 	// The decision of which pattern to use depends on the number of | ||||||
|  | 	// runs in the numbers. If there's only one, it's easy. For more than | ||||||
|  | 	// one, there's a tradeoff between complexity and size of the data | ||||||
|  | 	// and code vs. the simplicity of a map. A map takes more space, | ||||||
|  | 	// but so does the code. The decision here (crossover at 10) is | ||||||
|  | 	// arbitrary, but considers that for large numbers of runs the cost | ||||||
|  | 	// of the linear scan in the switch might become important, and | ||||||
|  | 	// rather than use yet another algorithm such as binary search, | ||||||
|  | 	// we punt and use a map. In any case, the likelihood of a map | ||||||
|  | 	// being necessary for any realistic example other than bitmasks | ||||||
|  | 	// is very low. And bitmasks probably deserve their own analysis, | ||||||
|  | 	// to be done some other day. | ||||||
|  | 	switch { | ||||||
|  | 	case len(runs) == 1: | ||||||
|  | 		g.buildOneRun(runs, typeName) | ||||||
|  | 	case len(runs) <= 10: | ||||||
|  | 		g.buildMultipleRuns(runs, typeName) | ||||||
|  | 	default: | ||||||
|  | 		g.buildMap(runs, typeName) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // splitIntoRuns breaks the values into runs of contiguous sequences. | ||||||
|  | // For example, given 1,2,3,5,6,7 it returns {1,2,3},{5,6,7}. | ||||||
|  | // The input slice is known to be non-empty. | ||||||
|  | func splitIntoRuns(values []Value) [][]Value { | ||||||
|  | 	// We use stable sort so the lexically first name is chosen for equal elements. | ||||||
|  | 	sort.Stable(byValue(values)) | ||||||
|  | 	// Remove duplicates. Stable sort has put the one we want to print first, | ||||||
|  | 	// so use that one. The String method won't care about which named constant | ||||||
|  | 	// was the argument, so the first name for the given value is the only one to keep. | ||||||
|  | 	// We need to do this because identical values would cause the switch or map | ||||||
|  | 	// to fail to compile. | ||||||
|  | 	j := 1 | ||||||
|  | 	for i := 1; i < len(values); i++ { | ||||||
|  | 		if values[i].value != values[i-1].value { | ||||||
|  | 			values[j] = values[i] | ||||||
|  | 			j++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	values = values[:j] | ||||||
|  | 	runs := make([][]Value, 0, 10) | ||||||
|  | 	for len(values) > 0 { | ||||||
|  | 		// One contiguous sequence per outer loop. | ||||||
|  | 		i := 1 | ||||||
|  | 		for i < len(values) && values[i].value == values[i-1].value+1 { | ||||||
|  | 			i++ | ||||||
|  | 		} | ||||||
|  | 		runs = append(runs, values[:i]) | ||||||
|  | 		values = values[i:] | ||||||
|  | 	} | ||||||
|  | 	return runs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // format returns the gofmt-ed contents of the Generator's buffer. | ||||||
|  | func (g *Generator) format() []byte { | ||||||
|  | 	src, err := format.Source(g.buf.Bytes()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// Should never happen, but can arise when developing this code. | ||||||
|  | 		// The user can compile the output to see the error. | ||||||
|  | 		log.Printf("warning: internal error: invalid Go generated: %s", err) | ||||||
|  | 		log.Printf("warning: compile the package to analyze the error") | ||||||
|  | 		return g.buf.Bytes() | ||||||
|  | 	} | ||||||
|  | 	return src | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Value represents a declared constant. | ||||||
|  | type Value struct { | ||||||
|  | 	originalName string // The name of the constant. | ||||||
|  | 	name         string // The name with trimmed prefix. | ||||||
|  | 	// The value is stored as a bit pattern alone. The boolean tells us | ||||||
|  | 	// whether to interpret it as an int64 or a uint64; the only place | ||||||
|  | 	// this matters is when sorting. | ||||||
|  | 	// Much of the time the str field is all we need; it is printed | ||||||
|  | 	// by Value.String. | ||||||
|  | 	value  uint64 // Will be converted to int64 when needed. | ||||||
|  | 	signed bool   // Whether the constant is a signed type. | ||||||
|  | 	str    string // The string representation given by the "go/constant" package. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *Value) String() string { | ||||||
|  | 	return v.str | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // byValue lets us sort the constants into increasing order. | ||||||
|  | // We take care in the Less method to sort in signed or unsigned order, | ||||||
|  | // as appropriate. | ||||||
|  | type byValue []Value | ||||||
|  | 
 | ||||||
|  | func (b byValue) Len() int      { return len(b) } | ||||||
|  | func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] } | ||||||
|  | func (b byValue) Less(i, j int) bool { | ||||||
|  | 	if b[i].signed { | ||||||
|  | 		return int64(b[i].value) < int64(b[j].value) | ||||||
|  | 	} | ||||||
|  | 	return b[i].value < b[j].value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // genDecl processes one declaration clause. | ||||||
|  | func (f *File) genDecl(node ast.Node) bool { | ||||||
|  | 	decl, ok := node.(*ast.GenDecl) | ||||||
|  | 	if !ok || decl.Tok != token.CONST { | ||||||
|  | 		// We only care about const declarations. | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	// The name of the type of the constants we are declaring. | ||||||
|  | 	// Can change if this is a multi-element declaration. | ||||||
|  | 	typ := "" | ||||||
|  | 	// Loop over the elements of the declaration. Each element is a ValueSpec: | ||||||
|  | 	// a list of names possibly followed by a type, possibly followed by values. | ||||||
|  | 	// If the type and value are both missing, we carry down the type (and value, | ||||||
|  | 	// but the "go/types" package takes care of that). | ||||||
|  | 	for _, spec := range decl.Specs { | ||||||
|  | 		vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST. | ||||||
|  | 		if vspec.Type == nil && len(vspec.Values) > 0 { | ||||||
|  | 			// "X = 1". With no type but a value. If the constant is untyped, | ||||||
|  | 			// skip this vspec and reset the remembered type. | ||||||
|  | 			typ = "" | ||||||
|  | 
 | ||||||
|  | 			// If this is a simple type conversion, remember the type. | ||||||
|  | 			// We don't mind if this is actually a call; a qualified call won't | ||||||
|  | 			// be matched (that will be SelectorExpr, not Ident), and only unusual | ||||||
|  | 			// situations will result in a function call that appears to be | ||||||
|  | 			// a type conversion. | ||||||
|  | 			ce, ok := vspec.Values[0].(*ast.CallExpr) | ||||||
|  | 			if !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			id, ok := ce.Fun.(*ast.Ident) | ||||||
|  | 			if !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			typ = id.Name | ||||||
|  | 		} | ||||||
|  | 		if vspec.Type != nil { | ||||||
|  | 			// "X T". We have a type. Remember it. | ||||||
|  | 			ident, ok := vspec.Type.(*ast.Ident) | ||||||
|  | 			if !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			typ = ident.Name | ||||||
|  | 		} | ||||||
|  | 		if typ != f.typeName { | ||||||
|  | 			// This is not the type we're looking for. | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		// We now have a list of names (from one line of source code) all being | ||||||
|  | 		// declared with the desired type. | ||||||
|  | 		// Grab their names and actual values and store them in f.values. | ||||||
|  | 		for _, name := range vspec.Names { | ||||||
|  | 			if name.Name == "_" { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			// This dance lets the type checker find the values for us. It's a | ||||||
|  | 			// bit tricky: look up the object declared by the name, find its | ||||||
|  | 			// types.Const, and extract its value. | ||||||
|  | 			obj, ok := f.pkg.defs[name] | ||||||
|  | 			if !ok { | ||||||
|  | 				log.Fatalf("no value for constant %s", name) | ||||||
|  | 			} | ||||||
|  | 			info := obj.Type().Underlying().(*types.Basic).Info() | ||||||
|  | 			if info&types.IsInteger == 0 { | ||||||
|  | 				log.Fatalf("can't handle non-integer constant type %s", typ) | ||||||
|  | 			} | ||||||
|  | 			value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST. | ||||||
|  | 			if value.Kind() != constant.Int { | ||||||
|  | 				log.Fatalf("can't happen: constant is not an integer %s", name) | ||||||
|  | 			} | ||||||
|  | 			i64, isInt := constant.Int64Val(value) | ||||||
|  | 			u64, isUint := constant.Uint64Val(value) | ||||||
|  | 			if !isInt && !isUint { | ||||||
|  | 				log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String()) | ||||||
|  | 			} | ||||||
|  | 			if !isInt { | ||||||
|  | 				u64 = uint64(i64) | ||||||
|  | 			} | ||||||
|  | 			v := Value{ | ||||||
|  | 				originalName: name.Name, | ||||||
|  | 				value:        u64, | ||||||
|  | 				signed:       info&types.IsUnsigned == 0, | ||||||
|  | 				str:          value.String(), | ||||||
|  | 			} | ||||||
|  | 			if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 { | ||||||
|  | 				v.name = strings.TrimSpace(c.Text()) | ||||||
|  | 			} else { | ||||||
|  | 				v.name = strings.TrimPrefix(v.originalName, f.trimPrefix) | ||||||
|  | 			} | ||||||
|  | 			f.values = append(f.values, v) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Helpers | ||||||
|  | 
 | ||||||
|  | // usize returns the number of bits of the smallest unsigned integer | ||||||
|  | // type that will hold n. Used to create the smallest possible slice of | ||||||
|  | // integers to use as indexes into the concatenated strings. | ||||||
|  | func usize(n int) int { | ||||||
|  | 	switch { | ||||||
|  | 	case n < 1<<8: | ||||||
|  | 		return 8 | ||||||
|  | 	case n < 1<<16: | ||||||
|  | 		return 16 | ||||||
|  | 	default: | ||||||
|  | 		// 2^32 is enough constants for anyone. | ||||||
|  | 		return 32 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // declareIndexAndNameVars declares the index slices and concatenated names | ||||||
|  | // strings representing the runs of values. | ||||||
|  | func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) { | ||||||
|  | 	var indexes, names []string | ||||||
|  | 	for i, run := range runs { | ||||||
|  | 		index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i)) | ||||||
|  | 		if len(run) != 1 { | ||||||
|  | 			indexes = append(indexes, index) | ||||||
|  | 		} | ||||||
|  | 		names = append(names, name) | ||||||
|  | 	} | ||||||
|  | 	g.Printf("const (\n") | ||||||
|  | 	for _, name := range names { | ||||||
|  | 		g.Printf("\t%s\n", name) | ||||||
|  | 	} | ||||||
|  | 	g.Printf(")\n\n") | ||||||
|  | 
 | ||||||
|  | 	if len(indexes) > 0 { | ||||||
|  | 		g.Printf("var (") | ||||||
|  | 		for _, index := range indexes { | ||||||
|  | 			g.Printf("\t%s\n", index) | ||||||
|  | 		} | ||||||
|  | 		g.Printf(")\n\n") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // declareIndexAndNameVar is the single-run version of declareIndexAndNameVars | ||||||
|  | func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) { | ||||||
|  | 	index, name := g.createIndexAndNameDecl(run, typeName, "") | ||||||
|  | 	g.Printf("const %s\n", name) | ||||||
|  | 	g.Printf("var %s\n", index) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // createIndexAndNameDecl returns the pair of declarations for the run. The caller will add "const" and "var". | ||||||
|  | func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) { | ||||||
|  | 	b := new(bytes.Buffer) | ||||||
|  | 	indexes := make([]int, len(run)) | ||||||
|  | 	for i := range run { | ||||||
|  | 		b.WriteString(run[i].name) | ||||||
|  | 		indexes[i] = b.Len() | ||||||
|  | 	} | ||||||
|  | 	nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String()) | ||||||
|  | 	nameLen := b.Len() | ||||||
|  | 	b.Reset() | ||||||
|  | 	fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen)) | ||||||
|  | 	for i, v := range indexes { | ||||||
|  | 		if i > 0 { | ||||||
|  | 			fmt.Fprintf(b, ", ") | ||||||
|  | 		} | ||||||
|  | 		fmt.Fprintf(b, "%d", v) | ||||||
|  | 	} | ||||||
|  | 	fmt.Fprintf(b, "}") | ||||||
|  | 	return b.String(), nameConst | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // declareNameVars declares the concatenated names string representing all the values in the runs. | ||||||
|  | func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) { | ||||||
|  | 	g.Printf("const _%s_name%s = \"", typeName, suffix) | ||||||
|  | 	for _, run := range runs { | ||||||
|  | 		for i := range run { | ||||||
|  | 			g.Printf("%s", run[i].name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	g.Printf("\"\n") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // buildOneRun generates the variables and String method for a single run of contiguous values. | ||||||
|  | func (g *Generator) buildOneRun(runs [][]Value, typeName string) { | ||||||
|  | 	values := runs[0] | ||||||
|  | 	g.Printf("\n") | ||||||
|  | 	g.declareIndexAndNameVar(values, typeName) | ||||||
|  | 	// The generated code is simple enough to write as a Printf format. | ||||||
|  | 	lessThanZero := "" | ||||||
|  | 	if values[0].signed { | ||||||
|  | 		lessThanZero = "i < 0 || " | ||||||
|  | 	} | ||||||
|  | 	if values[0].value == 0 { // Signed or unsigned, 0 is still 0. | ||||||
|  | 		g.Printf(stringOneRun, typeName, usize(len(values)), lessThanZero) | ||||||
|  | 	} else { | ||||||
|  | 		g.Printf(stringOneRunWithOffset, typeName, values[0].String(), usize(len(values)), lessThanZero) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Arguments to format are: | ||||||
|  | //	[1]: type name | ||||||
|  | //	[2]: size of index element (8 for uint8 etc.) | ||||||
|  | //	[3]: less than zero check (for signed types) | ||||||
|  | const stringOneRun = `func (i %[1]s) String() string { | ||||||
|  | 	if %[3]si >= %[1]s(len(_%[1]s_index)-1) { | ||||||
|  | 		return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" | ||||||
|  | 	} | ||||||
|  | 	return _%[1]s_name[_%[1]s_index[i]:_%[1]s_index[i+1]] | ||||||
|  | } | ||||||
|  | ` | ||||||
|  | 
 | ||||||
|  | // Arguments to format are: | ||||||
|  | //	[1]: type name | ||||||
|  | //	[2]: lowest defined value for type, as a string | ||||||
|  | //	[3]: size of index element (8 for uint8 etc.) | ||||||
|  | //	[4]: less than zero check (for signed types) | ||||||
|  | /* | ||||||
|  |  */ | ||||||
|  | const stringOneRunWithOffset = `func (i %[1]s) String() string { | ||||||
|  | 	i -= %[2]s | ||||||
|  | 	if %[4]si >= %[1]s(len(_%[1]s_index)-1) { | ||||||
|  | 		return "%[1]s(" + strconv.FormatInt(int64(i + %[2]s), 10) + ")" | ||||||
|  | 	} | ||||||
|  | 	return _%[1]s_name[_%[1]s_index[i] : _%[1]s_index[i+1]] | ||||||
|  | } | ||||||
|  | ` | ||||||
|  | 
 | ||||||
|  | // buildMultipleRuns generates the variables and String method for multiple runs of contiguous values. | ||||||
|  | // For this pattern, a single Printf format won't do. | ||||||
|  | func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) { | ||||||
|  | 	g.Printf("\n") | ||||||
|  | 	g.declareIndexAndNameVars(runs, typeName) | ||||||
|  | 	g.Printf("func (i %s) String() string {\n", typeName) | ||||||
|  | 	g.Printf("\tswitch {\n") | ||||||
|  | 	for i, values := range runs { | ||||||
|  | 		if len(values) == 1 { | ||||||
|  | 			g.Printf("\tcase i == %s:\n", &values[0]) | ||||||
|  | 			g.Printf("\t\treturn _%s_name_%d\n", typeName, i) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if values[0].value == 0 && !values[0].signed { | ||||||
|  | 			// For an unsigned lower bound of 0, "0 <= i" would be redundant. | ||||||
|  | 			g.Printf("\tcase i <= %s:\n", &values[len(values)-1]) | ||||||
|  | 		} else { | ||||||
|  | 			g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1]) | ||||||
|  | 		} | ||||||
|  | 		if values[0].value != 0 { | ||||||
|  | 			g.Printf("\t\ti -= %s\n", &values[0]) | ||||||
|  | 		} | ||||||
|  | 		g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n", | ||||||
|  | 			typeName, i, typeName, i, typeName, i) | ||||||
|  | 	} | ||||||
|  | 	g.Printf("\tdefault:\n") | ||||||
|  | 	g.Printf("\t\treturn \"%s(\" + strconv.FormatInt(int64(i), 10) + \")\"\n", typeName) | ||||||
|  | 	g.Printf("\t}\n") | ||||||
|  | 	g.Printf("}\n") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // buildMap handles the case where the space is so sparse a map is a reasonable fallback. | ||||||
|  | // It's a rare situation but has simple code. | ||||||
|  | func (g *Generator) buildMap(runs [][]Value, typeName string) { | ||||||
|  | 	g.Printf("\n") | ||||||
|  | 	g.declareNameVars(runs, typeName, "") | ||||||
|  | 	g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName) | ||||||
|  | 	n := 0 | ||||||
|  | 	for _, values := range runs { | ||||||
|  | 		for _, value := range values { | ||||||
|  | 			g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name)) | ||||||
|  | 			n += len(value.name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	g.Printf("}\n\n") | ||||||
|  | 	g.Printf(stringMap, typeName) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Argument to format is the type name. | ||||||
|  | const stringMap = `func (i %[1]s) String() string { | ||||||
|  | 	if str, ok := _%[1]s_map[i]; ok { | ||||||
|  | 		return str | ||||||
|  | 	} | ||||||
|  | 	return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" | ||||||
|  | } | ||||||
|  | ` | ||||||
							
								
								
									
										109
									
								
								vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | |||||||
|  | // Copyright 2016 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 gcexportdata provides functions for locating, reading, and | ||||||
|  | // writing export data files containing type information produced by the | ||||||
|  | // gc compiler.  This package supports go1.7 export data format and all | ||||||
|  | // later versions. | ||||||
|  | // | ||||||
|  | // Although it might seem convenient for this package to live alongside | ||||||
|  | // go/types in the standard library, this would cause version skew | ||||||
|  | // problems for developer tools that use it, since they must be able to | ||||||
|  | // consume the outputs of the gc compiler both before and after a Go | ||||||
|  | // update such as from Go 1.7 to Go 1.8.  Because this package lives in | ||||||
|  | // golang.org/x/tools, sites can update their version of this repo some | ||||||
|  | // time before the Go 1.8 release and rebuild and redeploy their | ||||||
|  | // developer tools, which will then be able to consume both Go 1.7 and | ||||||
|  | // Go 1.8 export data files, so they will work before and after the | ||||||
|  | // Go update. (See discussion at https://golang.org/issue/15651.) | ||||||
|  | // | ||||||
|  | package gcexportdata // import "golang.org/x/tools/go/gcexportdata" | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/token" | ||||||
|  | 	"go/types" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/internal/gcimporter" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Find returns the name of an object (.o) or archive (.a) file | ||||||
|  | // containing type information for the specified import path, | ||||||
|  | // using the workspace layout conventions of go/build. | ||||||
|  | // If no file was found, an empty filename is returned. | ||||||
|  | // | ||||||
|  | // A relative srcDir is interpreted relative to the current working directory. | ||||||
|  | // | ||||||
|  | // Find also returns the package's resolved (canonical) import path, | ||||||
|  | // reflecting the effects of srcDir and vendoring on importPath. | ||||||
|  | func Find(importPath, srcDir string) (filename, path string) { | ||||||
|  | 	return gcimporter.FindPkg(importPath, srcDir) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewReader returns a reader for the export data section of an object | ||||||
|  | // (.o) or archive (.a) file read from r.  The new reader may provide | ||||||
|  | // additional trailing data beyond the end of the export data. | ||||||
|  | func NewReader(r io.Reader) (io.Reader, error) { | ||||||
|  | 	buf := bufio.NewReader(r) | ||||||
|  | 	_, err := gcimporter.FindExportData(buf) | ||||||
|  | 	// If we ever switch to a zip-like archive format with the ToC | ||||||
|  | 	// at the end, we can return the correct portion of export data, | ||||||
|  | 	// but for now we must return the entire rest of the file. | ||||||
|  | 	return buf, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Read reads export data from in, decodes it, and returns type | ||||||
|  | // information for the package. | ||||||
|  | // The package name is specified by path. | ||||||
|  | // File position information is added to fset. | ||||||
|  | // | ||||||
|  | // Read may inspect and add to the imports map to ensure that references | ||||||
|  | // within the export data to other packages are consistent.  The caller | ||||||
|  | // must ensure that imports[path] does not exist, or exists but is | ||||||
|  | // incomplete (see types.Package.Complete), and Read inserts the | ||||||
|  | // resulting package into this map entry. | ||||||
|  | // | ||||||
|  | // On return, the state of the reader is undefined. | ||||||
|  | func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) { | ||||||
|  | 	data, err := ioutil.ReadAll(in) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("reading export data for %q: %v", path, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if bytes.HasPrefix(data, []byte("!<arch>")) { | ||||||
|  | 		return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// The App Engine Go runtime v1.6 uses the old export data format. | ||||||
|  | 	// TODO(adonovan): delete once v1.7 has been around for a while. | ||||||
|  | 	if bytes.HasPrefix(data, []byte("package ")) { | ||||||
|  | 		return gcimporter.ImportData(imports, path, path, bytes.NewReader(data)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// The indexed export format starts with an 'i'; the older | ||||||
|  | 	// binary export format starts with a 'c', 'd', or 'v' | ||||||
|  | 	// (from "version"). Select appropriate importer. | ||||||
|  | 	if len(data) > 0 && data[0] == 'i' { | ||||||
|  | 		_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) | ||||||
|  | 		return pkg, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_, pkg, err := gcimporter.BImportData(fset, imports, data, path) | ||||||
|  | 	return pkg, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Write writes encoded type information for the specified package to out. | ||||||
|  | // The FileSet provides file position information for named objects. | ||||||
|  | func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error { | ||||||
|  | 	b, err := gcimporter.IExportData(fset, pkg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	_, err = out.Write(b) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
							
								
								
									
										73
									
								
								vendor/golang.org/x/tools/go/gcexportdata/importer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								vendor/golang.org/x/tools/go/gcexportdata/importer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | |||||||
|  | // Copyright 2016 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 gcexportdata | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/token" | ||||||
|  | 	"go/types" | ||||||
|  | 	"os" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // NewImporter returns a new instance of the types.Importer interface | ||||||
|  | // that reads type information from export data files written by gc. | ||||||
|  | // The Importer also satisfies types.ImporterFrom. | ||||||
|  | // | ||||||
|  | // Export data files are located using "go build" workspace conventions | ||||||
|  | // and the build.Default context. | ||||||
|  | // | ||||||
|  | // Use this importer instead of go/importer.For("gc", ...) to avoid the | ||||||
|  | // version-skew problems described in the documentation of this package, | ||||||
|  | // or to control the FileSet or access the imports map populated during | ||||||
|  | // package loading. | ||||||
|  | // | ||||||
|  | func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom { | ||||||
|  | 	return importer{fset, imports} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type importer struct { | ||||||
|  | 	fset    *token.FileSet | ||||||
|  | 	imports map[string]*types.Package | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (imp importer) Import(importPath string) (*types.Package, error) { | ||||||
|  | 	return imp.ImportFrom(importPath, "", 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) { | ||||||
|  | 	filename, path := Find(importPath, srcDir) | ||||||
|  | 	if filename == "" { | ||||||
|  | 		if importPath == "unsafe" { | ||||||
|  | 			// Even for unsafe, call Find first in case | ||||||
|  | 			// the package was vendored. | ||||||
|  | 			return types.Unsafe, nil | ||||||
|  | 		} | ||||||
|  | 		return nil, fmt.Errorf("can't find import: %s", importPath) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if pkg, ok := imp.imports[path]; ok && pkg.Complete() { | ||||||
|  | 		return pkg, nil // cache hit | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// open file | ||||||
|  | 	f, err := os.Open(filename) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer func() { | ||||||
|  | 		f.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			// add file name to error | ||||||
|  | 			err = fmt.Errorf("reading export data: %s: %v", filename, err) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	r, err := NewReader(f) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return Read(r, imp.fset, imp.imports, path) | ||||||
|  | } | ||||||
							
								
								
									
										852
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										852
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,852 @@ | |||||||
|  | // Copyright 2016 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. | ||||||
|  | 
 | ||||||
|  | // Binary package export. | ||||||
|  | // This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go; | ||||||
|  | // see that file for specification of the format. | ||||||
|  | 
 | ||||||
|  | package gcimporter | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/constant" | ||||||
|  | 	"go/token" | ||||||
|  | 	"go/types" | ||||||
|  | 	"math" | ||||||
|  | 	"math/big" | ||||||
|  | 	"sort" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // If debugFormat is set, each integer and string value is preceded by a marker | ||||||
|  | // and position information in the encoding. This mechanism permits an importer | ||||||
|  | // to recognize immediately when it is out of sync. The importer recognizes this | ||||||
|  | // mode automatically (i.e., it can import export data produced with debugging | ||||||
|  | // support even if debugFormat is not set at the time of import). This mode will | ||||||
|  | // lead to massively larger export data (by a factor of 2 to 3) and should only | ||||||
|  | // be enabled during development and debugging. | ||||||
|  | // | ||||||
|  | // NOTE: This flag is the first flag to enable if importing dies because of | ||||||
|  | // (suspected) format errors, and whenever a change is made to the format. | ||||||
|  | const debugFormat = false // default: false | ||||||
|  | 
 | ||||||
|  | // If trace is set, debugging output is printed to std out. | ||||||
|  | const trace = false // default: false | ||||||
|  | 
 | ||||||
|  | // Current export format version. Increase with each format change. | ||||||
|  | // Note: The latest binary (non-indexed) export format is at version 6. | ||||||
|  | //       This exporter is still at level 4, but it doesn't matter since | ||||||
|  | //       the binary importer can handle older versions just fine. | ||||||
|  | // 6: package height (CL 105038) -- NOT IMPLEMENTED HERE | ||||||
|  | // 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMEMTED HERE | ||||||
|  | // 4: type name objects support type aliases, uses aliasTag | ||||||
|  | // 3: Go1.8 encoding (same as version 2, aliasTag defined but never used) | ||||||
|  | // 2: removed unused bool in ODCL export (compiler only) | ||||||
|  | // 1: header format change (more regular), export package for _ struct fields | ||||||
|  | // 0: Go1.7 encoding | ||||||
|  | const exportVersion = 4 | ||||||
|  | 
 | ||||||
|  | // trackAllTypes enables cycle tracking for all types, not just named | ||||||
|  | // types. The existing compiler invariants assume that unnamed types | ||||||
|  | // that are not completely set up are not used, or else there are spurious | ||||||
|  | // errors. | ||||||
|  | // If disabled, only named types are tracked, possibly leading to slightly | ||||||
|  | // less efficient encoding in rare cases. It also prevents the export of | ||||||
|  | // some corner-case type declarations (but those are not handled correctly | ||||||
|  | // with with the textual export format either). | ||||||
|  | // TODO(gri) enable and remove once issues caused by it are fixed | ||||||
|  | const trackAllTypes = false | ||||||
|  | 
 | ||||||
|  | type exporter struct { | ||||||
|  | 	fset *token.FileSet | ||||||
|  | 	out  bytes.Buffer | ||||||
|  | 
 | ||||||
|  | 	// object -> index maps, indexed in order of serialization | ||||||
|  | 	strIndex map[string]int | ||||||
|  | 	pkgIndex map[*types.Package]int | ||||||
|  | 	typIndex map[types.Type]int | ||||||
|  | 
 | ||||||
|  | 	// position encoding | ||||||
|  | 	posInfoFormat bool | ||||||
|  | 	prevFile      string | ||||||
|  | 	prevLine      int | ||||||
|  | 
 | ||||||
|  | 	// debugging support | ||||||
|  | 	written int // bytes written | ||||||
|  | 	indent  int // for trace | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // internalError represents an error generated inside this package. | ||||||
|  | type internalError string | ||||||
|  | 
 | ||||||
|  | func (e internalError) Error() string { return "gcimporter: " + string(e) } | ||||||
|  | 
 | ||||||
|  | func internalErrorf(format string, args ...interface{}) error { | ||||||
|  | 	return internalError(fmt.Sprintf(format, args...)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BExportData returns binary export data for pkg. | ||||||
|  | // If no file set is provided, position info will be missing. | ||||||
|  | func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { | ||||||
|  | 	defer func() { | ||||||
|  | 		if e := recover(); e != nil { | ||||||
|  | 			if ierr, ok := e.(internalError); ok { | ||||||
|  | 				err = ierr | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			// Not an internal error; panic again. | ||||||
|  | 			panic(e) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	p := exporter{ | ||||||
|  | 		fset:          fset, | ||||||
|  | 		strIndex:      map[string]int{"": 0}, // empty string is mapped to 0 | ||||||
|  | 		pkgIndex:      make(map[*types.Package]int), | ||||||
|  | 		typIndex:      make(map[types.Type]int), | ||||||
|  | 		posInfoFormat: true, // TODO(gri) might become a flag, eventually | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// write version info | ||||||
|  | 	// The version string must start with "version %d" where %d is the version | ||||||
|  | 	// number. Additional debugging information may follow after a blank; that | ||||||
|  | 	// text is ignored by the importer. | ||||||
|  | 	p.rawStringln(fmt.Sprintf("version %d", exportVersion)) | ||||||
|  | 	var debug string | ||||||
|  | 	if debugFormat { | ||||||
|  | 		debug = "debug" | ||||||
|  | 	} | ||||||
|  | 	p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly | ||||||
|  | 	p.bool(trackAllTypes) | ||||||
|  | 	p.bool(p.posInfoFormat) | ||||||
|  | 
 | ||||||
|  | 	// --- generic export data --- | ||||||
|  | 
 | ||||||
|  | 	// populate type map with predeclared "known" types | ||||||
|  | 	for index, typ := range predeclared() { | ||||||
|  | 		p.typIndex[typ] = index | ||||||
|  | 	} | ||||||
|  | 	if len(p.typIndex) != len(predeclared()) { | ||||||
|  | 		return nil, internalError("duplicate entries in type map?") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// write package data | ||||||
|  | 	p.pkg(pkg, true) | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("\n") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// write objects | ||||||
|  | 	objcount := 0 | ||||||
|  | 	scope := pkg.Scope() | ||||||
|  | 	for _, name := range scope.Names() { | ||||||
|  | 		if !ast.IsExported(name) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if trace { | ||||||
|  | 			p.tracef("\n") | ||||||
|  | 		} | ||||||
|  | 		p.obj(scope.Lookup(name)) | ||||||
|  | 		objcount++ | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// indicate end of list | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("\n") | ||||||
|  | 	} | ||||||
|  | 	p.tag(endTag) | ||||||
|  | 
 | ||||||
|  | 	// for self-verification only (redundant) | ||||||
|  | 	p.int(objcount) | ||||||
|  | 
 | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("\n") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// --- end of export data --- | ||||||
|  | 
 | ||||||
|  | 	return p.out.Bytes(), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) pkg(pkg *types.Package, emptypath bool) { | ||||||
|  | 	if pkg == nil { | ||||||
|  | 		panic(internalError("unexpected nil pkg")) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// if we saw the package before, write its index (>= 0) | ||||||
|  | 	if i, ok := p.pkgIndex[pkg]; ok { | ||||||
|  | 		p.index('P', i) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// otherwise, remember the package, write the package tag (< 0) and package data | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("P%d = { ", len(p.pkgIndex)) | ||||||
|  | 		defer p.tracef("} ") | ||||||
|  | 	} | ||||||
|  | 	p.pkgIndex[pkg] = len(p.pkgIndex) | ||||||
|  | 
 | ||||||
|  | 	p.tag(packageTag) | ||||||
|  | 	p.string(pkg.Name()) | ||||||
|  | 	if emptypath { | ||||||
|  | 		p.string("") | ||||||
|  | 	} else { | ||||||
|  | 		p.string(pkg.Path()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) obj(obj types.Object) { | ||||||
|  | 	switch obj := obj.(type) { | ||||||
|  | 	case *types.Const: | ||||||
|  | 		p.tag(constTag) | ||||||
|  | 		p.pos(obj) | ||||||
|  | 		p.qualifiedName(obj) | ||||||
|  | 		p.typ(obj.Type()) | ||||||
|  | 		p.value(obj.Val()) | ||||||
|  | 
 | ||||||
|  | 	case *types.TypeName: | ||||||
|  | 		if obj.IsAlias() { | ||||||
|  | 			p.tag(aliasTag) | ||||||
|  | 			p.pos(obj) | ||||||
|  | 			p.qualifiedName(obj) | ||||||
|  | 		} else { | ||||||
|  | 			p.tag(typeTag) | ||||||
|  | 		} | ||||||
|  | 		p.typ(obj.Type()) | ||||||
|  | 
 | ||||||
|  | 	case *types.Var: | ||||||
|  | 		p.tag(varTag) | ||||||
|  | 		p.pos(obj) | ||||||
|  | 		p.qualifiedName(obj) | ||||||
|  | 		p.typ(obj.Type()) | ||||||
|  | 
 | ||||||
|  | 	case *types.Func: | ||||||
|  | 		p.tag(funcTag) | ||||||
|  | 		p.pos(obj) | ||||||
|  | 		p.qualifiedName(obj) | ||||||
|  | 		sig := obj.Type().(*types.Signature) | ||||||
|  | 		p.paramList(sig.Params(), sig.Variadic()) | ||||||
|  | 		p.paramList(sig.Results(), false) | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		panic(internalErrorf("unexpected object %v (%T)", obj, obj)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) pos(obj types.Object) { | ||||||
|  | 	if !p.posInfoFormat { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	file, line := p.fileLine(obj) | ||||||
|  | 	if file == p.prevFile { | ||||||
|  | 		// common case: write line delta | ||||||
|  | 		// delta == 0 means different file or no line change | ||||||
|  | 		delta := line - p.prevLine | ||||||
|  | 		p.int(delta) | ||||||
|  | 		if delta == 0 { | ||||||
|  | 			p.int(-1) // -1 means no file change | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// different file | ||||||
|  | 		p.int(0) | ||||||
|  | 		// Encode filename as length of common prefix with previous | ||||||
|  | 		// filename, followed by (possibly empty) suffix. Filenames | ||||||
|  | 		// frequently share path prefixes, so this can save a lot | ||||||
|  | 		// of space and make export data size less dependent on file | ||||||
|  | 		// path length. The suffix is unlikely to be empty because | ||||||
|  | 		// file names tend to end in ".go". | ||||||
|  | 		n := commonPrefixLen(p.prevFile, file) | ||||||
|  | 		p.int(n)           // n >= 0 | ||||||
|  | 		p.string(file[n:]) // write suffix only | ||||||
|  | 		p.prevFile = file | ||||||
|  | 		p.int(line) | ||||||
|  | 	} | ||||||
|  | 	p.prevLine = line | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) fileLine(obj types.Object) (file string, line int) { | ||||||
|  | 	if p.fset != nil { | ||||||
|  | 		pos := p.fset.Position(obj.Pos()) | ||||||
|  | 		file = pos.Filename | ||||||
|  | 		line = pos.Line | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func commonPrefixLen(a, b string) int { | ||||||
|  | 	if len(a) > len(b) { | ||||||
|  | 		a, b = b, a | ||||||
|  | 	} | ||||||
|  | 	// len(a) <= len(b) | ||||||
|  | 	i := 0 | ||||||
|  | 	for i < len(a) && a[i] == b[i] { | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	return i | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) qualifiedName(obj types.Object) { | ||||||
|  | 	p.string(obj.Name()) | ||||||
|  | 	p.pkg(obj.Pkg(), false) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) typ(t types.Type) { | ||||||
|  | 	if t == nil { | ||||||
|  | 		panic(internalError("nil type")) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Possible optimization: Anonymous pointer types *T where | ||||||
|  | 	// T is a named type are common. We could canonicalize all | ||||||
|  | 	// such types *T to a single type PT = *T. This would lead | ||||||
|  | 	// to at most one *T entry in typIndex, and all future *T's | ||||||
|  | 	// would be encoded as the respective index directly. Would | ||||||
|  | 	// save 1 byte (pointerTag) per *T and reduce the typIndex | ||||||
|  | 	// size (at the cost of a canonicalization map). We can do | ||||||
|  | 	// this later, without encoding format change. | ||||||
|  | 
 | ||||||
|  | 	// if we saw the type before, write its index (>= 0) | ||||||
|  | 	if i, ok := p.typIndex[t]; ok { | ||||||
|  | 		p.index('T', i) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// otherwise, remember the type, write the type tag (< 0) and type data | ||||||
|  | 	if trackAllTypes { | ||||||
|  | 		if trace { | ||||||
|  | 			p.tracef("T%d = {>\n", len(p.typIndex)) | ||||||
|  | 			defer p.tracef("<\n} ") | ||||||
|  | 		} | ||||||
|  | 		p.typIndex[t] = len(p.typIndex) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch t := t.(type) { | ||||||
|  | 	case *types.Named: | ||||||
|  | 		if !trackAllTypes { | ||||||
|  | 			// if we don't track all types, track named types now | ||||||
|  | 			p.typIndex[t] = len(p.typIndex) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		p.tag(namedTag) | ||||||
|  | 		p.pos(t.Obj()) | ||||||
|  | 		p.qualifiedName(t.Obj()) | ||||||
|  | 		p.typ(t.Underlying()) | ||||||
|  | 		if !types.IsInterface(t) { | ||||||
|  | 			p.assocMethods(t) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case *types.Array: | ||||||
|  | 		p.tag(arrayTag) | ||||||
|  | 		p.int64(t.Len()) | ||||||
|  | 		p.typ(t.Elem()) | ||||||
|  | 
 | ||||||
|  | 	case *types.Slice: | ||||||
|  | 		p.tag(sliceTag) | ||||||
|  | 		p.typ(t.Elem()) | ||||||
|  | 
 | ||||||
|  | 	case *dddSlice: | ||||||
|  | 		p.tag(dddTag) | ||||||
|  | 		p.typ(t.elem) | ||||||
|  | 
 | ||||||
|  | 	case *types.Struct: | ||||||
|  | 		p.tag(structTag) | ||||||
|  | 		p.fieldList(t) | ||||||
|  | 
 | ||||||
|  | 	case *types.Pointer: | ||||||
|  | 		p.tag(pointerTag) | ||||||
|  | 		p.typ(t.Elem()) | ||||||
|  | 
 | ||||||
|  | 	case *types.Signature: | ||||||
|  | 		p.tag(signatureTag) | ||||||
|  | 		p.paramList(t.Params(), t.Variadic()) | ||||||
|  | 		p.paramList(t.Results(), false) | ||||||
|  | 
 | ||||||
|  | 	case *types.Interface: | ||||||
|  | 		p.tag(interfaceTag) | ||||||
|  | 		p.iface(t) | ||||||
|  | 
 | ||||||
|  | 	case *types.Map: | ||||||
|  | 		p.tag(mapTag) | ||||||
|  | 		p.typ(t.Key()) | ||||||
|  | 		p.typ(t.Elem()) | ||||||
|  | 
 | ||||||
|  | 	case *types.Chan: | ||||||
|  | 		p.tag(chanTag) | ||||||
|  | 		p.int(int(3 - t.Dir())) // hack | ||||||
|  | 		p.typ(t.Elem()) | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		panic(internalErrorf("unexpected type %T: %s", t, t)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) assocMethods(named *types.Named) { | ||||||
|  | 	// Sort methods (for determinism). | ||||||
|  | 	var methods []*types.Func | ||||||
|  | 	for i := 0; i < named.NumMethods(); i++ { | ||||||
|  | 		methods = append(methods, named.Method(i)) | ||||||
|  | 	} | ||||||
|  | 	sort.Sort(methodsByName(methods)) | ||||||
|  | 
 | ||||||
|  | 	p.int(len(methods)) | ||||||
|  | 
 | ||||||
|  | 	if trace && methods != nil { | ||||||
|  | 		p.tracef("associated methods {>\n") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, m := range methods { | ||||||
|  | 		if trace && i > 0 { | ||||||
|  | 			p.tracef("\n") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		p.pos(m) | ||||||
|  | 		name := m.Name() | ||||||
|  | 		p.string(name) | ||||||
|  | 		if !exported(name) { | ||||||
|  | 			p.pkg(m.Pkg(), false) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		sig := m.Type().(*types.Signature) | ||||||
|  | 		p.paramList(types.NewTuple(sig.Recv()), false) | ||||||
|  | 		p.paramList(sig.Params(), sig.Variadic()) | ||||||
|  | 		p.paramList(sig.Results(), false) | ||||||
|  | 		p.int(0) // dummy value for go:nointerface pragma - ignored by importer | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if trace && methods != nil { | ||||||
|  | 		p.tracef("<\n} ") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type methodsByName []*types.Func | ||||||
|  | 
 | ||||||
|  | func (x methodsByName) Len() int           { return len(x) } | ||||||
|  | func (x methodsByName) Swap(i, j int)      { x[i], x[j] = x[j], x[i] } | ||||||
|  | func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) fieldList(t *types.Struct) { | ||||||
|  | 	if trace && t.NumFields() > 0 { | ||||||
|  | 		p.tracef("fields {>\n") | ||||||
|  | 		defer p.tracef("<\n} ") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.int(t.NumFields()) | ||||||
|  | 	for i := 0; i < t.NumFields(); i++ { | ||||||
|  | 		if trace && i > 0 { | ||||||
|  | 			p.tracef("\n") | ||||||
|  | 		} | ||||||
|  | 		p.field(t.Field(i)) | ||||||
|  | 		p.string(t.Tag(i)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) field(f *types.Var) { | ||||||
|  | 	if !f.IsField() { | ||||||
|  | 		panic(internalError("field expected")) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.pos(f) | ||||||
|  | 	p.fieldName(f) | ||||||
|  | 	p.typ(f.Type()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) iface(t *types.Interface) { | ||||||
|  | 	// TODO(gri): enable importer to load embedded interfaces, | ||||||
|  | 	// then emit Embeddeds and ExplicitMethods separately here. | ||||||
|  | 	p.int(0) | ||||||
|  | 
 | ||||||
|  | 	n := t.NumMethods() | ||||||
|  | 	if trace && n > 0 { | ||||||
|  | 		p.tracef("methods {>\n") | ||||||
|  | 		defer p.tracef("<\n} ") | ||||||
|  | 	} | ||||||
|  | 	p.int(n) | ||||||
|  | 	for i := 0; i < n; i++ { | ||||||
|  | 		if trace && i > 0 { | ||||||
|  | 			p.tracef("\n") | ||||||
|  | 		} | ||||||
|  | 		p.method(t.Method(i)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) method(m *types.Func) { | ||||||
|  | 	sig := m.Type().(*types.Signature) | ||||||
|  | 	if sig.Recv() == nil { | ||||||
|  | 		panic(internalError("method expected")) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.pos(m) | ||||||
|  | 	p.string(m.Name()) | ||||||
|  | 	if m.Name() != "_" && !ast.IsExported(m.Name()) { | ||||||
|  | 		p.pkg(m.Pkg(), false) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// interface method; no need to encode receiver. | ||||||
|  | 	p.paramList(sig.Params(), sig.Variadic()) | ||||||
|  | 	p.paramList(sig.Results(), false) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) fieldName(f *types.Var) { | ||||||
|  | 	name := f.Name() | ||||||
|  | 
 | ||||||
|  | 	if f.Anonymous() { | ||||||
|  | 		// anonymous field - we distinguish between 3 cases: | ||||||
|  | 		// 1) field name matches base type name and is exported | ||||||
|  | 		// 2) field name matches base type name and is not exported | ||||||
|  | 		// 3) field name doesn't match base type name (alias name) | ||||||
|  | 		bname := basetypeName(f.Type()) | ||||||
|  | 		if name == bname { | ||||||
|  | 			if ast.IsExported(name) { | ||||||
|  | 				name = "" // 1) we don't need to know the field name or package | ||||||
|  | 			} else { | ||||||
|  | 				name = "?" // 2) use unexported name "?" to force package export | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			// 3) indicate alias and export name as is | ||||||
|  | 			// (this requires an extra "@" but this is a rare case) | ||||||
|  | 			p.string("@") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.string(name) | ||||||
|  | 	if name != "" && !ast.IsExported(name) { | ||||||
|  | 		p.pkg(f.Pkg(), false) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func basetypeName(typ types.Type) string { | ||||||
|  | 	switch typ := deref(typ).(type) { | ||||||
|  | 	case *types.Basic: | ||||||
|  | 		return typ.Name() | ||||||
|  | 	case *types.Named: | ||||||
|  | 		return typ.Obj().Name() | ||||||
|  | 	default: | ||||||
|  | 		return "" // unnamed type | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) paramList(params *types.Tuple, variadic bool) { | ||||||
|  | 	// use negative length to indicate unnamed parameters | ||||||
|  | 	// (look at the first parameter only since either all | ||||||
|  | 	// names are present or all are absent) | ||||||
|  | 	n := params.Len() | ||||||
|  | 	if n > 0 && params.At(0).Name() == "" { | ||||||
|  | 		n = -n | ||||||
|  | 	} | ||||||
|  | 	p.int(n) | ||||||
|  | 	for i := 0; i < params.Len(); i++ { | ||||||
|  | 		q := params.At(i) | ||||||
|  | 		t := q.Type() | ||||||
|  | 		if variadic && i == params.Len()-1 { | ||||||
|  | 			t = &dddSlice{t.(*types.Slice).Elem()} | ||||||
|  | 		} | ||||||
|  | 		p.typ(t) | ||||||
|  | 		if n > 0 { | ||||||
|  | 			name := q.Name() | ||||||
|  | 			p.string(name) | ||||||
|  | 			if name != "_" { | ||||||
|  | 				p.pkg(q.Pkg(), false) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		p.string("") // no compiler-specific info | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) value(x constant.Value) { | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("= ") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch x.Kind() { | ||||||
|  | 	case constant.Bool: | ||||||
|  | 		tag := falseTag | ||||||
|  | 		if constant.BoolVal(x) { | ||||||
|  | 			tag = trueTag | ||||||
|  | 		} | ||||||
|  | 		p.tag(tag) | ||||||
|  | 
 | ||||||
|  | 	case constant.Int: | ||||||
|  | 		if v, exact := constant.Int64Val(x); exact { | ||||||
|  | 			// common case: x fits into an int64 - use compact encoding | ||||||
|  | 			p.tag(int64Tag) | ||||||
|  | 			p.int64(v) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		// uncommon case: large x - use float encoding | ||||||
|  | 		// (powers of 2 will be encoded efficiently with exponent) | ||||||
|  | 		p.tag(floatTag) | ||||||
|  | 		p.float(constant.ToFloat(x)) | ||||||
|  | 
 | ||||||
|  | 	case constant.Float: | ||||||
|  | 		p.tag(floatTag) | ||||||
|  | 		p.float(x) | ||||||
|  | 
 | ||||||
|  | 	case constant.Complex: | ||||||
|  | 		p.tag(complexTag) | ||||||
|  | 		p.float(constant.Real(x)) | ||||||
|  | 		p.float(constant.Imag(x)) | ||||||
|  | 
 | ||||||
|  | 	case constant.String: | ||||||
|  | 		p.tag(stringTag) | ||||||
|  | 		p.string(constant.StringVal(x)) | ||||||
|  | 
 | ||||||
|  | 	case constant.Unknown: | ||||||
|  | 		// package contains type errors | ||||||
|  | 		p.tag(unknownTag) | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		panic(internalErrorf("unexpected value %v (%T)", x, x)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) float(x constant.Value) { | ||||||
|  | 	if x.Kind() != constant.Float { | ||||||
|  | 		panic(internalErrorf("unexpected constant %v, want float", x)) | ||||||
|  | 	} | ||||||
|  | 	// extract sign (there is no -0) | ||||||
|  | 	sign := constant.Sign(x) | ||||||
|  | 	if sign == 0 { | ||||||
|  | 		// x == 0 | ||||||
|  | 		p.int(0) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// x != 0 | ||||||
|  | 
 | ||||||
|  | 	var f big.Float | ||||||
|  | 	if v, exact := constant.Float64Val(x); exact { | ||||||
|  | 		// float64 | ||||||
|  | 		f.SetFloat64(v) | ||||||
|  | 	} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { | ||||||
|  | 		// TODO(gri): add big.Rat accessor to constant.Value. | ||||||
|  | 		r := valueToRat(num) | ||||||
|  | 		f.SetRat(r.Quo(r, valueToRat(denom))) | ||||||
|  | 	} else { | ||||||
|  | 		// Value too large to represent as a fraction => inaccessible. | ||||||
|  | 		// TODO(gri): add big.Float accessor to constant.Value. | ||||||
|  | 		f.SetFloat64(math.MaxFloat64) // FIXME | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// extract exponent such that 0.5 <= m < 1.0 | ||||||
|  | 	var m big.Float | ||||||
|  | 	exp := f.MantExp(&m) | ||||||
|  | 
 | ||||||
|  | 	// extract mantissa as *big.Int | ||||||
|  | 	// - set exponent large enough so mant satisfies mant.IsInt() | ||||||
|  | 	// - get *big.Int from mant | ||||||
|  | 	m.SetMantExp(&m, int(m.MinPrec())) | ||||||
|  | 	mant, acc := m.Int(nil) | ||||||
|  | 	if acc != big.Exact { | ||||||
|  | 		panic(internalError("internal error")) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.int(sign) | ||||||
|  | 	p.int(exp) | ||||||
|  | 	p.string(string(mant.Bytes())) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func valueToRat(x constant.Value) *big.Rat { | ||||||
|  | 	// Convert little-endian to big-endian. | ||||||
|  | 	// I can't believe this is necessary. | ||||||
|  | 	bytes := constant.Bytes(x) | ||||||
|  | 	for i := 0; i < len(bytes)/2; i++ { | ||||||
|  | 		bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] | ||||||
|  | 	} | ||||||
|  | 	return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) bool(b bool) bool { | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("[") | ||||||
|  | 		defer p.tracef("= %v] ", b) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	x := 0 | ||||||
|  | 	if b { | ||||||
|  | 		x = 1 | ||||||
|  | 	} | ||||||
|  | 	p.int(x) | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  | // Low-level encoders | ||||||
|  | 
 | ||||||
|  | func (p *exporter) index(marker byte, index int) { | ||||||
|  | 	if index < 0 { | ||||||
|  | 		panic(internalError("invalid index < 0")) | ||||||
|  | 	} | ||||||
|  | 	if debugFormat { | ||||||
|  | 		p.marker('t') | ||||||
|  | 	} | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("%c%d ", marker, index) | ||||||
|  | 	} | ||||||
|  | 	p.rawInt64(int64(index)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) tag(tag int) { | ||||||
|  | 	if tag >= 0 { | ||||||
|  | 		panic(internalError("invalid tag >= 0")) | ||||||
|  | 	} | ||||||
|  | 	if debugFormat { | ||||||
|  | 		p.marker('t') | ||||||
|  | 	} | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("%s ", tagString[-tag]) | ||||||
|  | 	} | ||||||
|  | 	p.rawInt64(int64(tag)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) int(x int) { | ||||||
|  | 	p.int64(int64(x)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) int64(x int64) { | ||||||
|  | 	if debugFormat { | ||||||
|  | 		p.marker('i') | ||||||
|  | 	} | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("%d ", x) | ||||||
|  | 	} | ||||||
|  | 	p.rawInt64(x) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *exporter) string(s string) { | ||||||
|  | 	if debugFormat { | ||||||
|  | 		p.marker('s') | ||||||
|  | 	} | ||||||
|  | 	if trace { | ||||||
|  | 		p.tracef("%q ", s) | ||||||
|  | 	} | ||||||
|  | 	// if we saw the string before, write its index (>= 0) | ||||||
|  | 	// (the empty string is mapped to 0) | ||||||
|  | 	if i, ok := p.strIndex[s]; ok { | ||||||
|  | 		p.rawInt64(int64(i)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// otherwise, remember string and write its negative length and bytes | ||||||
|  | 	p.strIndex[s] = len(p.strIndex) | ||||||
|  | 	p.rawInt64(-int64(len(s))) | ||||||
|  | 	for i := 0; i < len(s); i++ { | ||||||
|  | 		p.rawByte(s[i]) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // marker emits a marker byte and position information which makes | ||||||
|  | // it easy for a reader to detect if it is "out of sync". Used for | ||||||
|  | // debugFormat format only. | ||||||
|  | func (p *exporter) marker(m byte) { | ||||||
|  | 	p.rawByte(m) | ||||||
|  | 	// Enable this for help tracking down the location | ||||||
|  | 	// of an incorrect marker when running in debugFormat. | ||||||
|  | 	if false && trace { | ||||||
|  | 		p.tracef("#%d ", p.written) | ||||||
|  | 	} | ||||||
|  | 	p.rawInt64(int64(p.written)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // rawInt64 should only be used by low-level encoders. | ||||||
|  | func (p *exporter) rawInt64(x int64) { | ||||||
|  | 	var tmp [binary.MaxVarintLen64]byte | ||||||
|  | 	n := binary.PutVarint(tmp[:], x) | ||||||
|  | 	for i := 0; i < n; i++ { | ||||||
|  | 		p.rawByte(tmp[i]) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // rawStringln should only be used to emit the initial version string. | ||||||
|  | func (p *exporter) rawStringln(s string) { | ||||||
|  | 	for i := 0; i < len(s); i++ { | ||||||
|  | 		p.rawByte(s[i]) | ||||||
|  | 	} | ||||||
|  | 	p.rawByte('\n') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // rawByte is the bottleneck interface to write to p.out. | ||||||
|  | // rawByte escapes b as follows (any encoding does that | ||||||
|  | // hides '$'): | ||||||
|  | // | ||||||
|  | //	'$'  => '|' 'S' | ||||||
|  | //	'|'  => '|' '|' | ||||||
|  | // | ||||||
|  | // Necessary so other tools can find the end of the | ||||||
|  | // export data by searching for "$$". | ||||||
|  | // rawByte should only be used by low-level encoders. | ||||||
|  | func (p *exporter) rawByte(b byte) { | ||||||
|  | 	switch b { | ||||||
|  | 	case '$': | ||||||
|  | 		// write '$' as '|' 'S' | ||||||
|  | 		b = 'S' | ||||||
|  | 		fallthrough | ||||||
|  | 	case '|': | ||||||
|  | 		// write '|' as '|' '|' | ||||||
|  | 		p.out.WriteByte('|') | ||||||
|  | 		p.written++ | ||||||
|  | 	} | ||||||
|  | 	p.out.WriteByte(b) | ||||||
|  | 	p.written++ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // tracef is like fmt.Printf but it rewrites the format string | ||||||
|  | // to take care of indentation. | ||||||
|  | func (p *exporter) tracef(format string, args ...interface{}) { | ||||||
|  | 	if strings.ContainsAny(format, "<>\n") { | ||||||
|  | 		var buf bytes.Buffer | ||||||
|  | 		for i := 0; i < len(format); i++ { | ||||||
|  | 			// no need to deal with runes | ||||||
|  | 			ch := format[i] | ||||||
|  | 			switch ch { | ||||||
|  | 			case '>': | ||||||
|  | 				p.indent++ | ||||||
|  | 				continue | ||||||
|  | 			case '<': | ||||||
|  | 				p.indent-- | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			buf.WriteByte(ch) | ||||||
|  | 			if ch == '\n' { | ||||||
|  | 				for j := p.indent; j > 0; j-- { | ||||||
|  | 					buf.WriteString(".  ") | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		format = buf.String() | ||||||
|  | 	} | ||||||
|  | 	fmt.Printf(format, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Debugging support. | ||||||
|  | // (tagString is only used when tracing is enabled) | ||||||
|  | var tagString = [...]string{ | ||||||
|  | 	// Packages | ||||||
|  | 	-packageTag: "package", | ||||||
|  | 
 | ||||||
|  | 	// Types | ||||||
|  | 	-namedTag:     "named type", | ||||||
|  | 	-arrayTag:     "array", | ||||||
|  | 	-sliceTag:     "slice", | ||||||
|  | 	-dddTag:       "ddd", | ||||||
|  | 	-structTag:    "struct", | ||||||
|  | 	-pointerTag:   "pointer", | ||||||
|  | 	-signatureTag: "signature", | ||||||
|  | 	-interfaceTag: "interface", | ||||||
|  | 	-mapTag:       "map", | ||||||
|  | 	-chanTag:      "chan", | ||||||
|  | 
 | ||||||
|  | 	// Values | ||||||
|  | 	-falseTag:    "false", | ||||||
|  | 	-trueTag:     "true", | ||||||
|  | 	-int64Tag:    "int64", | ||||||
|  | 	-floatTag:    "float", | ||||||
|  | 	-fractionTag: "fraction", | ||||||
|  | 	-complexTag:  "complex", | ||||||
|  | 	-stringTag:   "string", | ||||||
|  | 	-unknownTag:  "unknown", | ||||||
|  | 
 | ||||||
|  | 	// Type aliases | ||||||
|  | 	-aliasTag: "alias", | ||||||
|  | } | ||||||
							
								
								
									
										1039
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1039
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										93
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/exportdata.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/exportdata.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | // This file is a copy of $GOROOT/src/go/internal/gcimporter/exportdata.go. | ||||||
|  | 
 | ||||||
|  | // This file implements FindExportData. | ||||||
|  | 
 | ||||||
|  | package gcimporter | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func readGopackHeader(r *bufio.Reader) (name string, size int, err error) { | ||||||
|  | 	// See $GOROOT/include/ar.h. | ||||||
|  | 	hdr := make([]byte, 16+12+6+6+8+10+2) | ||||||
|  | 	_, err = io.ReadFull(r, hdr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// leave for debugging | ||||||
|  | 	if false { | ||||||
|  | 		fmt.Printf("header: %s", hdr) | ||||||
|  | 	} | ||||||
|  | 	s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10])) | ||||||
|  | 	size, err = strconv.Atoi(s) | ||||||
|  | 	if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' { | ||||||
|  | 		err = fmt.Errorf("invalid archive header") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	name = strings.TrimSpace(string(hdr[:16])) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindExportData positions the reader r at the beginning of the | ||||||
|  | // export data section of an underlying GC-created object/archive | ||||||
|  | // file by reading from it. The reader must be positioned at the | ||||||
|  | // start of the file before calling this function. The hdr result | ||||||
|  | // is the string before the export data, either "$$" or "$$B". | ||||||
|  | // | ||||||
|  | func FindExportData(r *bufio.Reader) (hdr string, err error) { | ||||||
|  | 	// Read first line to make sure this is an object file. | ||||||
|  | 	line, err := r.ReadSlice('\n') | ||||||
|  | 	if err != nil { | ||||||
|  | 		err = fmt.Errorf("can't find export data (%v)", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if string(line) == "!<arch>\n" { | ||||||
|  | 		// Archive file. Scan to __.PKGDEF. | ||||||
|  | 		var name string | ||||||
|  | 		if name, _, err = readGopackHeader(r); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// First entry should be __.PKGDEF. | ||||||
|  | 		if name != "__.PKGDEF" { | ||||||
|  | 			err = fmt.Errorf("go archive is missing __.PKGDEF") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Read first line of __.PKGDEF data, so that line | ||||||
|  | 		// is once again the first line of the input. | ||||||
|  | 		if line, err = r.ReadSlice('\n'); err != nil { | ||||||
|  | 			err = fmt.Errorf("can't find export data (%v)", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Now at __.PKGDEF in archive or still at beginning of file. | ||||||
|  | 	// Either way, line should begin with "go object ". | ||||||
|  | 	if !strings.HasPrefix(string(line), "go object ") { | ||||||
|  | 		err = fmt.Errorf("not a Go object file") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Skip over object header to export data. | ||||||
|  | 	// Begins after first line starting with $$. | ||||||
|  | 	for line[0] != '$' { | ||||||
|  | 		if line, err = r.ReadSlice('\n'); err != nil { | ||||||
|  | 			err = fmt.Errorf("can't find export data (%v)", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	hdr = string(line) | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										1078
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1078
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										739
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										739
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,739 @@ | |||||||
|  | // Copyright 2019 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. | ||||||
|  | 
 | ||||||
|  | // Indexed binary package export. | ||||||
|  | // This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go; | ||||||
|  | // see that file for specification of the format. | ||||||
|  | 
 | ||||||
|  | package gcimporter | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/constant" | ||||||
|  | 	"go/token" | ||||||
|  | 	"go/types" | ||||||
|  | 	"io" | ||||||
|  | 	"math/big" | ||||||
|  | 	"reflect" | ||||||
|  | 	"sort" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Current indexed export format version. Increase with each format change. | ||||||
|  | // 0: Go1.11 encoding | ||||||
|  | const iexportVersion = 0 | ||||||
|  | 
 | ||||||
|  | // IExportData returns the binary export data for pkg. | ||||||
|  | // | ||||||
|  | // If no file set is provided, position info will be missing. | ||||||
|  | // The package path of the top-level package will not be recorded, | ||||||
|  | // so that calls to IImportData can override with a provided package path. | ||||||
|  | func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { | ||||||
|  | 	defer func() { | ||||||
|  | 		if e := recover(); e != nil { | ||||||
|  | 			if ierr, ok := e.(internalError); ok { | ||||||
|  | 				err = ierr | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			// Not an internal error; panic again. | ||||||
|  | 			panic(e) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	p := iexporter{ | ||||||
|  | 		out:         bytes.NewBuffer(nil), | ||||||
|  | 		fset:        fset, | ||||||
|  | 		allPkgs:     map[*types.Package]bool{}, | ||||||
|  | 		stringIndex: map[string]uint64{}, | ||||||
|  | 		declIndex:   map[types.Object]uint64{}, | ||||||
|  | 		typIndex:    map[types.Type]uint64{}, | ||||||
|  | 		localpkg:    pkg, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, pt := range predeclared() { | ||||||
|  | 		p.typIndex[pt] = uint64(i) | ||||||
|  | 	} | ||||||
|  | 	if len(p.typIndex) > predeclReserved { | ||||||
|  | 		panic(internalErrorf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Initialize work queue with exported declarations. | ||||||
|  | 	scope := pkg.Scope() | ||||||
|  | 	for _, name := range scope.Names() { | ||||||
|  | 		if ast.IsExported(name) { | ||||||
|  | 			p.pushDecl(scope.Lookup(name)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Loop until no more work. | ||||||
|  | 	for !p.declTodo.empty() { | ||||||
|  | 		p.doDecl(p.declTodo.popHead()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Append indices to data0 section. | ||||||
|  | 	dataLen := uint64(p.data0.Len()) | ||||||
|  | 	w := p.newWriter() | ||||||
|  | 	w.writeIndex(p.declIndex) | ||||||
|  | 	w.flush() | ||||||
|  | 
 | ||||||
|  | 	// Assemble header. | ||||||
|  | 	var hdr intWriter | ||||||
|  | 	hdr.WriteByte('i') | ||||||
|  | 	hdr.uint64(iexportVersion) | ||||||
|  | 	hdr.uint64(uint64(p.strings.Len())) | ||||||
|  | 	hdr.uint64(dataLen) | ||||||
|  | 
 | ||||||
|  | 	// Flush output. | ||||||
|  | 	io.Copy(p.out, &hdr) | ||||||
|  | 	io.Copy(p.out, &p.strings) | ||||||
|  | 	io.Copy(p.out, &p.data0) | ||||||
|  | 
 | ||||||
|  | 	return p.out.Bytes(), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // writeIndex writes out an object index. mainIndex indicates whether | ||||||
|  | // we're writing out the main index, which is also read by | ||||||
|  | // non-compiler tools and includes a complete package description | ||||||
|  | // (i.e., name and height). | ||||||
|  | func (w *exportWriter) writeIndex(index map[types.Object]uint64) { | ||||||
|  | 	// Build a map from packages to objects from that package. | ||||||
|  | 	pkgObjs := map[*types.Package][]types.Object{} | ||||||
|  | 
 | ||||||
|  | 	// For the main index, make sure to include every package that | ||||||
|  | 	// we reference, even if we're not exporting (or reexporting) | ||||||
|  | 	// any symbols from it. | ||||||
|  | 	pkgObjs[w.p.localpkg] = nil | ||||||
|  | 	for pkg := range w.p.allPkgs { | ||||||
|  | 		pkgObjs[pkg] = nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for obj := range index { | ||||||
|  | 		pkgObjs[obj.Pkg()] = append(pkgObjs[obj.Pkg()], obj) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var pkgs []*types.Package | ||||||
|  | 	for pkg, objs := range pkgObjs { | ||||||
|  | 		pkgs = append(pkgs, pkg) | ||||||
|  | 
 | ||||||
|  | 		sort.Slice(objs, func(i, j int) bool { | ||||||
|  | 			return objs[i].Name() < objs[j].Name() | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sort.Slice(pkgs, func(i, j int) bool { | ||||||
|  | 		return w.exportPath(pkgs[i]) < w.exportPath(pkgs[j]) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	w.uint64(uint64(len(pkgs))) | ||||||
|  | 	for _, pkg := range pkgs { | ||||||
|  | 		w.string(w.exportPath(pkg)) | ||||||
|  | 		w.string(pkg.Name()) | ||||||
|  | 		w.uint64(uint64(0)) // package height is not needed for go/types | ||||||
|  | 
 | ||||||
|  | 		objs := pkgObjs[pkg] | ||||||
|  | 		w.uint64(uint64(len(objs))) | ||||||
|  | 		for _, obj := range objs { | ||||||
|  | 			w.string(obj.Name()) | ||||||
|  | 			w.uint64(index[obj]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type iexporter struct { | ||||||
|  | 	fset *token.FileSet | ||||||
|  | 	out  *bytes.Buffer | ||||||
|  | 
 | ||||||
|  | 	localpkg *types.Package | ||||||
|  | 
 | ||||||
|  | 	// allPkgs tracks all packages that have been referenced by | ||||||
|  | 	// the export data, so we can ensure to include them in the | ||||||
|  | 	// main index. | ||||||
|  | 	allPkgs map[*types.Package]bool | ||||||
|  | 
 | ||||||
|  | 	declTodo objQueue | ||||||
|  | 
 | ||||||
|  | 	strings     intWriter | ||||||
|  | 	stringIndex map[string]uint64 | ||||||
|  | 
 | ||||||
|  | 	data0     intWriter | ||||||
|  | 	declIndex map[types.Object]uint64 | ||||||
|  | 	typIndex  map[types.Type]uint64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // stringOff returns the offset of s within the string section. | ||||||
|  | // If not already present, it's added to the end. | ||||||
|  | func (p *iexporter) stringOff(s string) uint64 { | ||||||
|  | 	off, ok := p.stringIndex[s] | ||||||
|  | 	if !ok { | ||||||
|  | 		off = uint64(p.strings.Len()) | ||||||
|  | 		p.stringIndex[s] = off | ||||||
|  | 
 | ||||||
|  | 		p.strings.uint64(uint64(len(s))) | ||||||
|  | 		p.strings.WriteString(s) | ||||||
|  | 	} | ||||||
|  | 	return off | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // pushDecl adds n to the declaration work queue, if not already present. | ||||||
|  | func (p *iexporter) pushDecl(obj types.Object) { | ||||||
|  | 	// Package unsafe is known to the compiler and predeclared. | ||||||
|  | 	assert(obj.Pkg() != types.Unsafe) | ||||||
|  | 
 | ||||||
|  | 	if _, ok := p.declIndex[obj]; ok { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.declIndex[obj] = ^uint64(0) // mark n present in work queue | ||||||
|  | 	p.declTodo.pushTail(obj) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // exportWriter handles writing out individual data section chunks. | ||||||
|  | type exportWriter struct { | ||||||
|  | 	p *iexporter | ||||||
|  | 
 | ||||||
|  | 	data     intWriter | ||||||
|  | 	currPkg  *types.Package | ||||||
|  | 	prevFile string | ||||||
|  | 	prevLine int64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) exportPath(pkg *types.Package) string { | ||||||
|  | 	if pkg == w.p.localpkg { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return pkg.Path() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *iexporter) doDecl(obj types.Object) { | ||||||
|  | 	w := p.newWriter() | ||||||
|  | 	w.setPkg(obj.Pkg(), false) | ||||||
|  | 
 | ||||||
|  | 	switch obj := obj.(type) { | ||||||
|  | 	case *types.Var: | ||||||
|  | 		w.tag('V') | ||||||
|  | 		w.pos(obj.Pos()) | ||||||
|  | 		w.typ(obj.Type(), obj.Pkg()) | ||||||
|  | 
 | ||||||
|  | 	case *types.Func: | ||||||
|  | 		sig, _ := obj.Type().(*types.Signature) | ||||||
|  | 		if sig.Recv() != nil { | ||||||
|  | 			panic(internalErrorf("unexpected method: %v", sig)) | ||||||
|  | 		} | ||||||
|  | 		w.tag('F') | ||||||
|  | 		w.pos(obj.Pos()) | ||||||
|  | 		w.signature(sig) | ||||||
|  | 
 | ||||||
|  | 	case *types.Const: | ||||||
|  | 		w.tag('C') | ||||||
|  | 		w.pos(obj.Pos()) | ||||||
|  | 		w.value(obj.Type(), obj.Val()) | ||||||
|  | 
 | ||||||
|  | 	case *types.TypeName: | ||||||
|  | 		if obj.IsAlias() { | ||||||
|  | 			w.tag('A') | ||||||
|  | 			w.pos(obj.Pos()) | ||||||
|  | 			w.typ(obj.Type(), obj.Pkg()) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Defined type. | ||||||
|  | 		w.tag('T') | ||||||
|  | 		w.pos(obj.Pos()) | ||||||
|  | 
 | ||||||
|  | 		underlying := obj.Type().Underlying() | ||||||
|  | 		w.typ(underlying, obj.Pkg()) | ||||||
|  | 
 | ||||||
|  | 		t := obj.Type() | ||||||
|  | 		if types.IsInterface(t) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		named, ok := t.(*types.Named) | ||||||
|  | 		if !ok { | ||||||
|  | 			panic(internalErrorf("%s is not a defined type", t)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		n := named.NumMethods() | ||||||
|  | 		w.uint64(uint64(n)) | ||||||
|  | 		for i := 0; i < n; i++ { | ||||||
|  | 			m := named.Method(i) | ||||||
|  | 			w.pos(m.Pos()) | ||||||
|  | 			w.string(m.Name()) | ||||||
|  | 			sig, _ := m.Type().(*types.Signature) | ||||||
|  | 			w.param(sig.Recv()) | ||||||
|  | 			w.signature(sig) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		panic(internalErrorf("unexpected object: %v", obj)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.declIndex[obj] = w.flush() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) tag(tag byte) { | ||||||
|  | 	w.data.WriteByte(tag) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) pos(pos token.Pos) { | ||||||
|  | 	if w.p.fset == nil { | ||||||
|  | 		w.int64(0) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p := w.p.fset.Position(pos) | ||||||
|  | 	file := p.Filename | ||||||
|  | 	line := int64(p.Line) | ||||||
|  | 
 | ||||||
|  | 	// When file is the same as the last position (common case), | ||||||
|  | 	// we can save a few bytes by delta encoding just the line | ||||||
|  | 	// number. | ||||||
|  | 	// | ||||||
|  | 	// Note: Because data objects may be read out of order (or not | ||||||
|  | 	// at all), we can only apply delta encoding within a single | ||||||
|  | 	// object. This is handled implicitly by tracking prevFile and | ||||||
|  | 	// prevLine as fields of exportWriter. | ||||||
|  | 
 | ||||||
|  | 	if file == w.prevFile { | ||||||
|  | 		delta := line - w.prevLine | ||||||
|  | 		w.int64(delta) | ||||||
|  | 		if delta == deltaNewFile { | ||||||
|  | 			w.int64(-1) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		w.int64(deltaNewFile) | ||||||
|  | 		w.int64(line) // line >= 0 | ||||||
|  | 		w.string(file) | ||||||
|  | 		w.prevFile = file | ||||||
|  | 	} | ||||||
|  | 	w.prevLine = line | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) pkg(pkg *types.Package) { | ||||||
|  | 	// Ensure any referenced packages are declared in the main index. | ||||||
|  | 	w.p.allPkgs[pkg] = true | ||||||
|  | 
 | ||||||
|  | 	w.string(w.exportPath(pkg)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) qualifiedIdent(obj types.Object) { | ||||||
|  | 	// Ensure any referenced declarations are written out too. | ||||||
|  | 	w.p.pushDecl(obj) | ||||||
|  | 
 | ||||||
|  | 	w.string(obj.Name()) | ||||||
|  | 	w.pkg(obj.Pkg()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) typ(t types.Type, pkg *types.Package) { | ||||||
|  | 	w.data.uint64(w.p.typOff(t, pkg)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *iexporter) newWriter() *exportWriter { | ||||||
|  | 	return &exportWriter{p: p} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) flush() uint64 { | ||||||
|  | 	off := uint64(w.p.data0.Len()) | ||||||
|  | 	io.Copy(&w.p.data0, &w.data) | ||||||
|  | 	return off | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *iexporter) typOff(t types.Type, pkg *types.Package) uint64 { | ||||||
|  | 	off, ok := p.typIndex[t] | ||||||
|  | 	if !ok { | ||||||
|  | 		w := p.newWriter() | ||||||
|  | 		w.doTyp(t, pkg) | ||||||
|  | 		off = predeclReserved + w.flush() | ||||||
|  | 		p.typIndex[t] = off | ||||||
|  | 	} | ||||||
|  | 	return off | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) startType(k itag) { | ||||||
|  | 	w.data.uint64(uint64(k)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { | ||||||
|  | 	switch t := t.(type) { | ||||||
|  | 	case *types.Named: | ||||||
|  | 		w.startType(definedType) | ||||||
|  | 		w.qualifiedIdent(t.Obj()) | ||||||
|  | 
 | ||||||
|  | 	case *types.Pointer: | ||||||
|  | 		w.startType(pointerType) | ||||||
|  | 		w.typ(t.Elem(), pkg) | ||||||
|  | 
 | ||||||
|  | 	case *types.Slice: | ||||||
|  | 		w.startType(sliceType) | ||||||
|  | 		w.typ(t.Elem(), pkg) | ||||||
|  | 
 | ||||||
|  | 	case *types.Array: | ||||||
|  | 		w.startType(arrayType) | ||||||
|  | 		w.uint64(uint64(t.Len())) | ||||||
|  | 		w.typ(t.Elem(), pkg) | ||||||
|  | 
 | ||||||
|  | 	case *types.Chan: | ||||||
|  | 		w.startType(chanType) | ||||||
|  | 		// 1 RecvOnly; 2 SendOnly; 3 SendRecv | ||||||
|  | 		var dir uint64 | ||||||
|  | 		switch t.Dir() { | ||||||
|  | 		case types.RecvOnly: | ||||||
|  | 			dir = 1 | ||||||
|  | 		case types.SendOnly: | ||||||
|  | 			dir = 2 | ||||||
|  | 		case types.SendRecv: | ||||||
|  | 			dir = 3 | ||||||
|  | 		} | ||||||
|  | 		w.uint64(dir) | ||||||
|  | 		w.typ(t.Elem(), pkg) | ||||||
|  | 
 | ||||||
|  | 	case *types.Map: | ||||||
|  | 		w.startType(mapType) | ||||||
|  | 		w.typ(t.Key(), pkg) | ||||||
|  | 		w.typ(t.Elem(), pkg) | ||||||
|  | 
 | ||||||
|  | 	case *types.Signature: | ||||||
|  | 		w.startType(signatureType) | ||||||
|  | 		w.setPkg(pkg, true) | ||||||
|  | 		w.signature(t) | ||||||
|  | 
 | ||||||
|  | 	case *types.Struct: | ||||||
|  | 		w.startType(structType) | ||||||
|  | 		w.setPkg(pkg, true) | ||||||
|  | 
 | ||||||
|  | 		n := t.NumFields() | ||||||
|  | 		w.uint64(uint64(n)) | ||||||
|  | 		for i := 0; i < n; i++ { | ||||||
|  | 			f := t.Field(i) | ||||||
|  | 			w.pos(f.Pos()) | ||||||
|  | 			w.string(f.Name()) | ||||||
|  | 			w.typ(f.Type(), pkg) | ||||||
|  | 			w.bool(f.Anonymous()) | ||||||
|  | 			w.string(t.Tag(i)) // note (or tag) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case *types.Interface: | ||||||
|  | 		w.startType(interfaceType) | ||||||
|  | 		w.setPkg(pkg, true) | ||||||
|  | 
 | ||||||
|  | 		n := t.NumEmbeddeds() | ||||||
|  | 		w.uint64(uint64(n)) | ||||||
|  | 		for i := 0; i < n; i++ { | ||||||
|  | 			f := t.Embedded(i) | ||||||
|  | 			w.pos(f.Obj().Pos()) | ||||||
|  | 			w.typ(f.Obj().Type(), f.Obj().Pkg()) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		n = t.NumExplicitMethods() | ||||||
|  | 		w.uint64(uint64(n)) | ||||||
|  | 		for i := 0; i < n; i++ { | ||||||
|  | 			m := t.ExplicitMethod(i) | ||||||
|  | 			w.pos(m.Pos()) | ||||||
|  | 			w.string(m.Name()) | ||||||
|  | 			sig, _ := m.Type().(*types.Signature) | ||||||
|  | 			w.signature(sig) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t))) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) setPkg(pkg *types.Package, write bool) { | ||||||
|  | 	if write { | ||||||
|  | 		w.pkg(pkg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	w.currPkg = pkg | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) signature(sig *types.Signature) { | ||||||
|  | 	w.paramList(sig.Params()) | ||||||
|  | 	w.paramList(sig.Results()) | ||||||
|  | 	if sig.Params().Len() > 0 { | ||||||
|  | 		w.bool(sig.Variadic()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) paramList(tup *types.Tuple) { | ||||||
|  | 	n := tup.Len() | ||||||
|  | 	w.uint64(uint64(n)) | ||||||
|  | 	for i := 0; i < n; i++ { | ||||||
|  | 		w.param(tup.At(i)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) param(obj types.Object) { | ||||||
|  | 	w.pos(obj.Pos()) | ||||||
|  | 	w.localIdent(obj) | ||||||
|  | 	w.typ(obj.Type(), obj.Pkg()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) value(typ types.Type, v constant.Value) { | ||||||
|  | 	w.typ(typ, nil) | ||||||
|  | 
 | ||||||
|  | 	switch v.Kind() { | ||||||
|  | 	case constant.Bool: | ||||||
|  | 		w.bool(constant.BoolVal(v)) | ||||||
|  | 	case constant.Int: | ||||||
|  | 		var i big.Int | ||||||
|  | 		if i64, exact := constant.Int64Val(v); exact { | ||||||
|  | 			i.SetInt64(i64) | ||||||
|  | 		} else if ui64, exact := constant.Uint64Val(v); exact { | ||||||
|  | 			i.SetUint64(ui64) | ||||||
|  | 		} else { | ||||||
|  | 			i.SetString(v.ExactString(), 10) | ||||||
|  | 		} | ||||||
|  | 		w.mpint(&i, typ) | ||||||
|  | 	case constant.Float: | ||||||
|  | 		f := constantToFloat(v) | ||||||
|  | 		w.mpfloat(f, typ) | ||||||
|  | 	case constant.Complex: | ||||||
|  | 		w.mpfloat(constantToFloat(constant.Real(v)), typ) | ||||||
|  | 		w.mpfloat(constantToFloat(constant.Imag(v)), typ) | ||||||
|  | 	case constant.String: | ||||||
|  | 		w.string(constant.StringVal(v)) | ||||||
|  | 	case constant.Unknown: | ||||||
|  | 		// package contains type errors | ||||||
|  | 	default: | ||||||
|  | 		panic(internalErrorf("unexpected value %v (%T)", v, v)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // constantToFloat converts a constant.Value with kind constant.Float to a | ||||||
|  | // big.Float. | ||||||
|  | func constantToFloat(x constant.Value) *big.Float { | ||||||
|  | 	assert(x.Kind() == constant.Float) | ||||||
|  | 	// Use the same floating-point precision (512) as cmd/compile | ||||||
|  | 	// (see Mpprec in cmd/compile/internal/gc/mpfloat.go). | ||||||
|  | 	const mpprec = 512 | ||||||
|  | 	var f big.Float | ||||||
|  | 	f.SetPrec(mpprec) | ||||||
|  | 	if v, exact := constant.Float64Val(x); exact { | ||||||
|  | 		// float64 | ||||||
|  | 		f.SetFloat64(v) | ||||||
|  | 	} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { | ||||||
|  | 		// TODO(gri): add big.Rat accessor to constant.Value. | ||||||
|  | 		n := valueToRat(num) | ||||||
|  | 		d := valueToRat(denom) | ||||||
|  | 		f.SetRat(n.Quo(n, d)) | ||||||
|  | 	} else { | ||||||
|  | 		// Value too large to represent as a fraction => inaccessible. | ||||||
|  | 		// TODO(gri): add big.Float accessor to constant.Value. | ||||||
|  | 		_, ok := f.SetString(x.ExactString()) | ||||||
|  | 		assert(ok) | ||||||
|  | 	} | ||||||
|  | 	return &f | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // mpint exports a multi-precision integer. | ||||||
|  | // | ||||||
|  | // For unsigned types, small values are written out as a single | ||||||
|  | // byte. Larger values are written out as a length-prefixed big-endian | ||||||
|  | // byte string, where the length prefix is encoded as its complement. | ||||||
|  | // For example, bytes 0, 1, and 2 directly represent the integer | ||||||
|  | // values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-, | ||||||
|  | // 2-, and 3-byte big-endian string follow. | ||||||
|  | // | ||||||
|  | // Encoding for signed types use the same general approach as for | ||||||
|  | // unsigned types, except small values use zig-zag encoding and the | ||||||
|  | // bottom bit of length prefix byte for large values is reserved as a | ||||||
|  | // sign bit. | ||||||
|  | // | ||||||
|  | // The exact boundary between small and large encodings varies | ||||||
|  | // according to the maximum number of bytes needed to encode a value | ||||||
|  | // of type typ. As a special case, 8-bit types are always encoded as a | ||||||
|  | // single byte. | ||||||
|  | // | ||||||
|  | // TODO(mdempsky): Is this level of complexity really worthwhile? | ||||||
|  | func (w *exportWriter) mpint(x *big.Int, typ types.Type) { | ||||||
|  | 	basic, ok := typ.Underlying().(*types.Basic) | ||||||
|  | 	if !ok { | ||||||
|  | 		panic(internalErrorf("unexpected type %v (%T)", typ.Underlying(), typ.Underlying())) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	signed, maxBytes := intSize(basic) | ||||||
|  | 
 | ||||||
|  | 	negative := x.Sign() < 0 | ||||||
|  | 	if !signed && negative { | ||||||
|  | 		panic(internalErrorf("negative unsigned integer; type %v, value %v", typ, x)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	b := x.Bytes() | ||||||
|  | 	if len(b) > 0 && b[0] == 0 { | ||||||
|  | 		panic(internalErrorf("leading zeros")) | ||||||
|  | 	} | ||||||
|  | 	if uint(len(b)) > maxBytes { | ||||||
|  | 		panic(internalErrorf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	maxSmall := 256 - maxBytes | ||||||
|  | 	if signed { | ||||||
|  | 		maxSmall = 256 - 2*maxBytes | ||||||
|  | 	} | ||||||
|  | 	if maxBytes == 1 { | ||||||
|  | 		maxSmall = 256 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check if x can use small value encoding. | ||||||
|  | 	if len(b) <= 1 { | ||||||
|  | 		var ux uint | ||||||
|  | 		if len(b) == 1 { | ||||||
|  | 			ux = uint(b[0]) | ||||||
|  | 		} | ||||||
|  | 		if signed { | ||||||
|  | 			ux <<= 1 | ||||||
|  | 			if negative { | ||||||
|  | 				ux-- | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if ux < maxSmall { | ||||||
|  | 			w.data.WriteByte(byte(ux)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	n := 256 - uint(len(b)) | ||||||
|  | 	if signed { | ||||||
|  | 		n = 256 - 2*uint(len(b)) | ||||||
|  | 		if negative { | ||||||
|  | 			n |= 1 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if n < maxSmall || n >= 256 { | ||||||
|  | 		panic(internalErrorf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	w.data.WriteByte(byte(n)) | ||||||
|  | 	w.data.Write(b) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // mpfloat exports a multi-precision floating point number. | ||||||
|  | // | ||||||
|  | // The number's value is decomposed into mantissa × 2**exponent, where | ||||||
|  | // mantissa is an integer. The value is written out as mantissa (as a | ||||||
|  | // multi-precision integer) and then the exponent, except exponent is | ||||||
|  | // omitted if mantissa is zero. | ||||||
|  | func (w *exportWriter) mpfloat(f *big.Float, typ types.Type) { | ||||||
|  | 	if f.IsInf() { | ||||||
|  | 		panic("infinite constant") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Break into f = mant × 2**exp, with 0.5 <= mant < 1. | ||||||
|  | 	var mant big.Float | ||||||
|  | 	exp := int64(f.MantExp(&mant)) | ||||||
|  | 
 | ||||||
|  | 	// Scale so that mant is an integer. | ||||||
|  | 	prec := mant.MinPrec() | ||||||
|  | 	mant.SetMantExp(&mant, int(prec)) | ||||||
|  | 	exp -= int64(prec) | ||||||
|  | 
 | ||||||
|  | 	manti, acc := mant.Int(nil) | ||||||
|  | 	if acc != big.Exact { | ||||||
|  | 		panic(internalErrorf("mantissa scaling failed for %f (%s)", f, acc)) | ||||||
|  | 	} | ||||||
|  | 	w.mpint(manti, typ) | ||||||
|  | 	if manti.Sign() != 0 { | ||||||
|  | 		w.int64(exp) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) bool(b bool) bool { | ||||||
|  | 	var x uint64 | ||||||
|  | 	if b { | ||||||
|  | 		x = 1 | ||||||
|  | 	} | ||||||
|  | 	w.uint64(x) | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) int64(x int64)   { w.data.int64(x) } | ||||||
|  | func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) } | ||||||
|  | func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) } | ||||||
|  | 
 | ||||||
|  | func (w *exportWriter) localIdent(obj types.Object) { | ||||||
|  | 	// Anonymous parameters. | ||||||
|  | 	if obj == nil { | ||||||
|  | 		w.string("") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	name := obj.Name() | ||||||
|  | 	if name == "_" { | ||||||
|  | 		w.string("_") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	w.string(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type intWriter struct { | ||||||
|  | 	bytes.Buffer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *intWriter) int64(x int64) { | ||||||
|  | 	var buf [binary.MaxVarintLen64]byte | ||||||
|  | 	n := binary.PutVarint(buf[:], x) | ||||||
|  | 	w.Write(buf[:n]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *intWriter) uint64(x uint64) { | ||||||
|  | 	var buf [binary.MaxVarintLen64]byte | ||||||
|  | 	n := binary.PutUvarint(buf[:], x) | ||||||
|  | 	w.Write(buf[:n]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func assert(cond bool) { | ||||||
|  | 	if !cond { | ||||||
|  | 		panic("internal error: assertion failed") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // The below is copied from go/src/cmd/compile/internal/gc/syntax.go. | ||||||
|  | 
 | ||||||
|  | // objQueue is a FIFO queue of types.Object. The zero value of objQueue is | ||||||
|  | // a ready-to-use empty queue. | ||||||
|  | type objQueue struct { | ||||||
|  | 	ring       []types.Object | ||||||
|  | 	head, tail int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // empty returns true if q contains no Nodes. | ||||||
|  | func (q *objQueue) empty() bool { | ||||||
|  | 	return q.head == q.tail | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // pushTail appends n to the tail of the queue. | ||||||
|  | func (q *objQueue) pushTail(obj types.Object) { | ||||||
|  | 	if len(q.ring) == 0 { | ||||||
|  | 		q.ring = make([]types.Object, 16) | ||||||
|  | 	} else if q.head+len(q.ring) == q.tail { | ||||||
|  | 		// Grow the ring. | ||||||
|  | 		nring := make([]types.Object, len(q.ring)*2) | ||||||
|  | 		// Copy the old elements. | ||||||
|  | 		part := q.ring[q.head%len(q.ring):] | ||||||
|  | 		if q.tail-q.head <= len(part) { | ||||||
|  | 			part = part[:q.tail-q.head] | ||||||
|  | 			copy(nring, part) | ||||||
|  | 		} else { | ||||||
|  | 			pos := copy(nring, part) | ||||||
|  | 			copy(nring[pos:], q.ring[:q.tail%len(q.ring)]) | ||||||
|  | 		} | ||||||
|  | 		q.ring, q.head, q.tail = nring, 0, q.tail-q.head | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	q.ring[q.tail%len(q.ring)] = obj | ||||||
|  | 	q.tail++ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // popHead pops a node from the head of the queue. It panics if q is empty. | ||||||
|  | func (q *objQueue) popHead() types.Object { | ||||||
|  | 	if q.empty() { | ||||||
|  | 		panic("dequeue empty") | ||||||
|  | 	} | ||||||
|  | 	obj := q.ring[q.head%len(q.ring)] | ||||||
|  | 	q.head++ | ||||||
|  | 	return obj | ||||||
|  | } | ||||||
							
								
								
									
										630
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										630
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,630 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | // Indexed package import. | ||||||
|  | // See cmd/compile/internal/gc/iexport.go for the export data format. | ||||||
|  | 
 | ||||||
|  | // This file is a copy of $GOROOT/src/go/internal/gcimporter/iimport.go. | ||||||
|  | 
 | ||||||
|  | package gcimporter | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/constant" | ||||||
|  | 	"go/token" | ||||||
|  | 	"go/types" | ||||||
|  | 	"io" | ||||||
|  | 	"sort" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type intReader struct { | ||||||
|  | 	*bytes.Reader | ||||||
|  | 	path string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *intReader) int64() int64 { | ||||||
|  | 	i, err := binary.ReadVarint(r.Reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		errorf("import %q: read varint error: %v", r.path, err) | ||||||
|  | 	} | ||||||
|  | 	return i | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *intReader) uint64() uint64 { | ||||||
|  | 	i, err := binary.ReadUvarint(r.Reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		errorf("import %q: read varint error: %v", r.path, err) | ||||||
|  | 	} | ||||||
|  | 	return i | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const predeclReserved = 32 | ||||||
|  | 
 | ||||||
|  | type itag uint64 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// Types | ||||||
|  | 	definedType itag = iota | ||||||
|  | 	pointerType | ||||||
|  | 	sliceType | ||||||
|  | 	arrayType | ||||||
|  | 	chanType | ||||||
|  | 	mapType | ||||||
|  | 	signatureType | ||||||
|  | 	structType | ||||||
|  | 	interfaceType | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // IImportData imports a package from the serialized package data | ||||||
|  | // and returns the number of bytes consumed and a reference to the package. | ||||||
|  | // If the export data version is not recognized or the format is otherwise | ||||||
|  | // compromised, an error is returned. | ||||||
|  | func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { | ||||||
|  | 	const currentVersion = 1 | ||||||
|  | 	version := int64(-1) | ||||||
|  | 	defer func() { | ||||||
|  | 		if e := recover(); e != nil { | ||||||
|  | 			if version > currentVersion { | ||||||
|  | 				err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) | ||||||
|  | 			} else { | ||||||
|  | 				err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	r := &intReader{bytes.NewReader(data), path} | ||||||
|  | 
 | ||||||
|  | 	version = int64(r.uint64()) | ||||||
|  | 	switch version { | ||||||
|  | 	case currentVersion, 0: | ||||||
|  | 	default: | ||||||
|  | 		errorf("unknown iexport format version %d", version) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sLen := int64(r.uint64()) | ||||||
|  | 	dLen := int64(r.uint64()) | ||||||
|  | 
 | ||||||
|  | 	whence, _ := r.Seek(0, io.SeekCurrent) | ||||||
|  | 	stringData := data[whence : whence+sLen] | ||||||
|  | 	declData := data[whence+sLen : whence+sLen+dLen] | ||||||
|  | 	r.Seek(sLen+dLen, io.SeekCurrent) | ||||||
|  | 
 | ||||||
|  | 	p := iimporter{ | ||||||
|  | 		ipath:   path, | ||||||
|  | 		version: int(version), | ||||||
|  | 
 | ||||||
|  | 		stringData:  stringData, | ||||||
|  | 		stringCache: make(map[uint64]string), | ||||||
|  | 		pkgCache:    make(map[uint64]*types.Package), | ||||||
|  | 
 | ||||||
|  | 		declData: declData, | ||||||
|  | 		pkgIndex: make(map[*types.Package]map[string]uint64), | ||||||
|  | 		typCache: make(map[uint64]types.Type), | ||||||
|  | 
 | ||||||
|  | 		fake: fakeFileSet{ | ||||||
|  | 			fset:  fset, | ||||||
|  | 			files: make(map[string]*token.File), | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, pt := range predeclared() { | ||||||
|  | 		p.typCache[uint64(i)] = pt | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pkgList := make([]*types.Package, r.uint64()) | ||||||
|  | 	for i := range pkgList { | ||||||
|  | 		pkgPathOff := r.uint64() | ||||||
|  | 		pkgPath := p.stringAt(pkgPathOff) | ||||||
|  | 		pkgName := p.stringAt(r.uint64()) | ||||||
|  | 		_ = r.uint64() // package height; unused by go/types | ||||||
|  | 
 | ||||||
|  | 		if pkgPath == "" { | ||||||
|  | 			pkgPath = path | ||||||
|  | 		} | ||||||
|  | 		pkg := imports[pkgPath] | ||||||
|  | 		if pkg == nil { | ||||||
|  | 			pkg = types.NewPackage(pkgPath, pkgName) | ||||||
|  | 			imports[pkgPath] = pkg | ||||||
|  | 		} else if pkg.Name() != pkgName { | ||||||
|  | 			errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		p.pkgCache[pkgPathOff] = pkg | ||||||
|  | 
 | ||||||
|  | 		nameIndex := make(map[string]uint64) | ||||||
|  | 		for nSyms := r.uint64(); nSyms > 0; nSyms-- { | ||||||
|  | 			name := p.stringAt(r.uint64()) | ||||||
|  | 			nameIndex[name] = r.uint64() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		p.pkgIndex[pkg] = nameIndex | ||||||
|  | 		pkgList[i] = pkg | ||||||
|  | 	} | ||||||
|  | 	if len(pkgList) == 0 { | ||||||
|  | 		errorf("no packages found for %s", path) | ||||||
|  | 		panic("unreachable") | ||||||
|  | 	} | ||||||
|  | 	p.ipkg = pkgList[0] | ||||||
|  | 	names := make([]string, 0, len(p.pkgIndex[p.ipkg])) | ||||||
|  | 	for name := range p.pkgIndex[p.ipkg] { | ||||||
|  | 		names = append(names, name) | ||||||
|  | 	} | ||||||
|  | 	sort.Strings(names) | ||||||
|  | 	for _, name := range names { | ||||||
|  | 		p.doDecl(p.ipkg, name) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, typ := range p.interfaceList { | ||||||
|  | 		typ.Complete() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// record all referenced packages as imports | ||||||
|  | 	list := append(([]*types.Package)(nil), pkgList[1:]...) | ||||||
|  | 	sort.Sort(byPath(list)) | ||||||
|  | 	p.ipkg.SetImports(list) | ||||||
|  | 
 | ||||||
|  | 	// package was imported completely and without errors | ||||||
|  | 	p.ipkg.MarkComplete() | ||||||
|  | 
 | ||||||
|  | 	consumed, _ := r.Seek(0, io.SeekCurrent) | ||||||
|  | 	return int(consumed), p.ipkg, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type iimporter struct { | ||||||
|  | 	ipath   string | ||||||
|  | 	ipkg    *types.Package | ||||||
|  | 	version int | ||||||
|  | 
 | ||||||
|  | 	stringData  []byte | ||||||
|  | 	stringCache map[uint64]string | ||||||
|  | 	pkgCache    map[uint64]*types.Package | ||||||
|  | 
 | ||||||
|  | 	declData []byte | ||||||
|  | 	pkgIndex map[*types.Package]map[string]uint64 | ||||||
|  | 	typCache map[uint64]types.Type | ||||||
|  | 
 | ||||||
|  | 	fake          fakeFileSet | ||||||
|  | 	interfaceList []*types.Interface | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *iimporter) doDecl(pkg *types.Package, name string) { | ||||||
|  | 	// See if we've already imported this declaration. | ||||||
|  | 	if obj := pkg.Scope().Lookup(name); obj != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	off, ok := p.pkgIndex[pkg][name] | ||||||
|  | 	if !ok { | ||||||
|  | 		errorf("%v.%v not in index", pkg, name) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := &importReader{p: p, currPkg: pkg} | ||||||
|  | 	r.declReader.Reset(p.declData[off:]) | ||||||
|  | 
 | ||||||
|  | 	r.obj(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *iimporter) stringAt(off uint64) string { | ||||||
|  | 	if s, ok := p.stringCache[off]; ok { | ||||||
|  | 		return s | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	slen, n := binary.Uvarint(p.stringData[off:]) | ||||||
|  | 	if n <= 0 { | ||||||
|  | 		errorf("varint failed") | ||||||
|  | 	} | ||||||
|  | 	spos := off + uint64(n) | ||||||
|  | 	s := string(p.stringData[spos : spos+slen]) | ||||||
|  | 	p.stringCache[off] = s | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *iimporter) pkgAt(off uint64) *types.Package { | ||||||
|  | 	if pkg, ok := p.pkgCache[off]; ok { | ||||||
|  | 		return pkg | ||||||
|  | 	} | ||||||
|  | 	path := p.stringAt(off) | ||||||
|  | 	if path == p.ipath { | ||||||
|  | 		return p.ipkg | ||||||
|  | 	} | ||||||
|  | 	errorf("missing package %q in %q", path, p.ipath) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *iimporter) typAt(off uint64, base *types.Named) types.Type { | ||||||
|  | 	if t, ok := p.typCache[off]; ok && (base == nil || !isInterface(t)) { | ||||||
|  | 		return t | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if off < predeclReserved { | ||||||
|  | 		errorf("predeclared type missing from cache: %v", off) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := &importReader{p: p} | ||||||
|  | 	r.declReader.Reset(p.declData[off-predeclReserved:]) | ||||||
|  | 	t := r.doType(base) | ||||||
|  | 
 | ||||||
|  | 	if base == nil || !isInterface(t) { | ||||||
|  | 		p.typCache[off] = t | ||||||
|  | 	} | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type importReader struct { | ||||||
|  | 	p          *iimporter | ||||||
|  | 	declReader bytes.Reader | ||||||
|  | 	currPkg    *types.Package | ||||||
|  | 	prevFile   string | ||||||
|  | 	prevLine   int64 | ||||||
|  | 	prevColumn int64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) obj(name string) { | ||||||
|  | 	tag := r.byte() | ||||||
|  | 	pos := r.pos() | ||||||
|  | 
 | ||||||
|  | 	switch tag { | ||||||
|  | 	case 'A': | ||||||
|  | 		typ := r.typ() | ||||||
|  | 
 | ||||||
|  | 		r.declare(types.NewTypeName(pos, r.currPkg, name, typ)) | ||||||
|  | 
 | ||||||
|  | 	case 'C': | ||||||
|  | 		typ, val := r.value() | ||||||
|  | 
 | ||||||
|  | 		r.declare(types.NewConst(pos, r.currPkg, name, typ, val)) | ||||||
|  | 
 | ||||||
|  | 	case 'F': | ||||||
|  | 		sig := r.signature(nil) | ||||||
|  | 
 | ||||||
|  | 		r.declare(types.NewFunc(pos, r.currPkg, name, sig)) | ||||||
|  | 
 | ||||||
|  | 	case 'T': | ||||||
|  | 		// Types can be recursive. We need to setup a stub | ||||||
|  | 		// declaration before recursing. | ||||||
|  | 		obj := types.NewTypeName(pos, r.currPkg, name, nil) | ||||||
|  | 		named := types.NewNamed(obj, nil, nil) | ||||||
|  | 		r.declare(obj) | ||||||
|  | 
 | ||||||
|  | 		underlying := r.p.typAt(r.uint64(), named).Underlying() | ||||||
|  | 		named.SetUnderlying(underlying) | ||||||
|  | 
 | ||||||
|  | 		if !isInterface(underlying) { | ||||||
|  | 			for n := r.uint64(); n > 0; n-- { | ||||||
|  | 				mpos := r.pos() | ||||||
|  | 				mname := r.ident() | ||||||
|  | 				recv := r.param() | ||||||
|  | 				msig := r.signature(recv) | ||||||
|  | 
 | ||||||
|  | 				named.AddMethod(types.NewFunc(mpos, r.currPkg, mname, msig)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case 'V': | ||||||
|  | 		typ := r.typ() | ||||||
|  | 
 | ||||||
|  | 		r.declare(types.NewVar(pos, r.currPkg, name, typ)) | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		errorf("unexpected tag: %v", tag) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) declare(obj types.Object) { | ||||||
|  | 	obj.Pkg().Scope().Insert(obj) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) value() (typ types.Type, val constant.Value) { | ||||||
|  | 	typ = r.typ() | ||||||
|  | 
 | ||||||
|  | 	switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType { | ||||||
|  | 	case types.IsBoolean: | ||||||
|  | 		val = constant.MakeBool(r.bool()) | ||||||
|  | 
 | ||||||
|  | 	case types.IsString: | ||||||
|  | 		val = constant.MakeString(r.string()) | ||||||
|  | 
 | ||||||
|  | 	case types.IsInteger: | ||||||
|  | 		val = r.mpint(b) | ||||||
|  | 
 | ||||||
|  | 	case types.IsFloat: | ||||||
|  | 		val = r.mpfloat(b) | ||||||
|  | 
 | ||||||
|  | 	case types.IsComplex: | ||||||
|  | 		re := r.mpfloat(b) | ||||||
|  | 		im := r.mpfloat(b) | ||||||
|  | 		val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		if b.Kind() == types.Invalid { | ||||||
|  | 			val = constant.MakeUnknown() | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		errorf("unexpected type %v", typ) // panics | ||||||
|  | 		panic("unreachable") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func intSize(b *types.Basic) (signed bool, maxBytes uint) { | ||||||
|  | 	if (b.Info() & types.IsUntyped) != 0 { | ||||||
|  | 		return true, 64 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch b.Kind() { | ||||||
|  | 	case types.Float32, types.Complex64: | ||||||
|  | 		return true, 3 | ||||||
|  | 	case types.Float64, types.Complex128: | ||||||
|  | 		return true, 7 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	signed = (b.Info() & types.IsUnsigned) == 0 | ||||||
|  | 	switch b.Kind() { | ||||||
|  | 	case types.Int8, types.Uint8: | ||||||
|  | 		maxBytes = 1 | ||||||
|  | 	case types.Int16, types.Uint16: | ||||||
|  | 		maxBytes = 2 | ||||||
|  | 	case types.Int32, types.Uint32: | ||||||
|  | 		maxBytes = 4 | ||||||
|  | 	default: | ||||||
|  | 		maxBytes = 8 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) mpint(b *types.Basic) constant.Value { | ||||||
|  | 	signed, maxBytes := intSize(b) | ||||||
|  | 
 | ||||||
|  | 	maxSmall := 256 - maxBytes | ||||||
|  | 	if signed { | ||||||
|  | 		maxSmall = 256 - 2*maxBytes | ||||||
|  | 	} | ||||||
|  | 	if maxBytes == 1 { | ||||||
|  | 		maxSmall = 256 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	n, _ := r.declReader.ReadByte() | ||||||
|  | 	if uint(n) < maxSmall { | ||||||
|  | 		v := int64(n) | ||||||
|  | 		if signed { | ||||||
|  | 			v >>= 1 | ||||||
|  | 			if n&1 != 0 { | ||||||
|  | 				v = ^v | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return constant.MakeInt64(v) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	v := -n | ||||||
|  | 	if signed { | ||||||
|  | 		v = -(n &^ 1) >> 1 | ||||||
|  | 	} | ||||||
|  | 	if v < 1 || uint(v) > maxBytes { | ||||||
|  | 		errorf("weird decoding: %v, %v => %v", n, signed, v) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	buf := make([]byte, v) | ||||||
|  | 	io.ReadFull(&r.declReader, buf) | ||||||
|  | 
 | ||||||
|  | 	// convert to little endian | ||||||
|  | 	// TODO(gri) go/constant should have a more direct conversion function | ||||||
|  | 	//           (e.g., once it supports a big.Float based implementation) | ||||||
|  | 	for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 { | ||||||
|  | 		buf[i], buf[j] = buf[j], buf[i] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	x := constant.MakeFromBytes(buf) | ||||||
|  | 	if signed && n&1 != 0 { | ||||||
|  | 		x = constant.UnaryOp(token.SUB, x, 0) | ||||||
|  | 	} | ||||||
|  | 	return x | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) mpfloat(b *types.Basic) constant.Value { | ||||||
|  | 	x := r.mpint(b) | ||||||
|  | 	if constant.Sign(x) == 0 { | ||||||
|  | 		return x | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	exp := r.int64() | ||||||
|  | 	switch { | ||||||
|  | 	case exp > 0: | ||||||
|  | 		x = constant.Shift(x, token.SHL, uint(exp)) | ||||||
|  | 	case exp < 0: | ||||||
|  | 		d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp)) | ||||||
|  | 		x = constant.BinaryOp(x, token.QUO, d) | ||||||
|  | 	} | ||||||
|  | 	return x | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) ident() string { | ||||||
|  | 	return r.string() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) qualifiedIdent() (*types.Package, string) { | ||||||
|  | 	name := r.string() | ||||||
|  | 	pkg := r.pkg() | ||||||
|  | 	return pkg, name | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) pos() token.Pos { | ||||||
|  | 	if r.p.version >= 1 { | ||||||
|  | 		r.posv1() | ||||||
|  | 	} else { | ||||||
|  | 		r.posv0() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 { | ||||||
|  | 		return token.NoPos | ||||||
|  | 	} | ||||||
|  | 	return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) posv0() { | ||||||
|  | 	delta := r.int64() | ||||||
|  | 	if delta != deltaNewFile { | ||||||
|  | 		r.prevLine += delta | ||||||
|  | 	} else if l := r.int64(); l == -1 { | ||||||
|  | 		r.prevLine += deltaNewFile | ||||||
|  | 	} else { | ||||||
|  | 		r.prevFile = r.string() | ||||||
|  | 		r.prevLine = l | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) posv1() { | ||||||
|  | 	delta := r.int64() | ||||||
|  | 	r.prevColumn += delta >> 1 | ||||||
|  | 	if delta&1 != 0 { | ||||||
|  | 		delta = r.int64() | ||||||
|  | 		r.prevLine += delta >> 1 | ||||||
|  | 		if delta&1 != 0 { | ||||||
|  | 			r.prevFile = r.string() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) typ() types.Type { | ||||||
|  | 	return r.p.typAt(r.uint64(), nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isInterface(t types.Type) bool { | ||||||
|  | 	_, ok := t.(*types.Interface) | ||||||
|  | 	return ok | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) pkg() *types.Package { return r.p.pkgAt(r.uint64()) } | ||||||
|  | func (r *importReader) string() string      { return r.p.stringAt(r.uint64()) } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) doType(base *types.Named) types.Type { | ||||||
|  | 	switch k := r.kind(); k { | ||||||
|  | 	default: | ||||||
|  | 		errorf("unexpected kind tag in %q: %v", r.p.ipath, k) | ||||||
|  | 		return nil | ||||||
|  | 
 | ||||||
|  | 	case definedType: | ||||||
|  | 		pkg, name := r.qualifiedIdent() | ||||||
|  | 		r.p.doDecl(pkg, name) | ||||||
|  | 		return pkg.Scope().Lookup(name).(*types.TypeName).Type() | ||||||
|  | 	case pointerType: | ||||||
|  | 		return types.NewPointer(r.typ()) | ||||||
|  | 	case sliceType: | ||||||
|  | 		return types.NewSlice(r.typ()) | ||||||
|  | 	case arrayType: | ||||||
|  | 		n := r.uint64() | ||||||
|  | 		return types.NewArray(r.typ(), int64(n)) | ||||||
|  | 	case chanType: | ||||||
|  | 		dir := chanDir(int(r.uint64())) | ||||||
|  | 		return types.NewChan(dir, r.typ()) | ||||||
|  | 	case mapType: | ||||||
|  | 		return types.NewMap(r.typ(), r.typ()) | ||||||
|  | 	case signatureType: | ||||||
|  | 		r.currPkg = r.pkg() | ||||||
|  | 		return r.signature(nil) | ||||||
|  | 
 | ||||||
|  | 	case structType: | ||||||
|  | 		r.currPkg = r.pkg() | ||||||
|  | 
 | ||||||
|  | 		fields := make([]*types.Var, r.uint64()) | ||||||
|  | 		tags := make([]string, len(fields)) | ||||||
|  | 		for i := range fields { | ||||||
|  | 			fpos := r.pos() | ||||||
|  | 			fname := r.ident() | ||||||
|  | 			ftyp := r.typ() | ||||||
|  | 			emb := r.bool() | ||||||
|  | 			tag := r.string() | ||||||
|  | 
 | ||||||
|  | 			fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb) | ||||||
|  | 			tags[i] = tag | ||||||
|  | 		} | ||||||
|  | 		return types.NewStruct(fields, tags) | ||||||
|  | 
 | ||||||
|  | 	case interfaceType: | ||||||
|  | 		r.currPkg = r.pkg() | ||||||
|  | 
 | ||||||
|  | 		embeddeds := make([]types.Type, r.uint64()) | ||||||
|  | 		for i := range embeddeds { | ||||||
|  | 			_ = r.pos() | ||||||
|  | 			embeddeds[i] = r.typ() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		methods := make([]*types.Func, r.uint64()) | ||||||
|  | 		for i := range methods { | ||||||
|  | 			mpos := r.pos() | ||||||
|  | 			mname := r.ident() | ||||||
|  | 
 | ||||||
|  | 			// TODO(mdempsky): Matches bimport.go, but I | ||||||
|  | 			// don't agree with this. | ||||||
|  | 			var recv *types.Var | ||||||
|  | 			if base != nil { | ||||||
|  | 				recv = types.NewVar(token.NoPos, r.currPkg, "", base) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			msig := r.signature(recv) | ||||||
|  | 			methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		typ := newInterface(methods, embeddeds) | ||||||
|  | 		r.p.interfaceList = append(r.p.interfaceList, typ) | ||||||
|  | 		return typ | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) kind() itag { | ||||||
|  | 	return itag(r.uint64()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) signature(recv *types.Var) *types.Signature { | ||||||
|  | 	params := r.paramList() | ||||||
|  | 	results := r.paramList() | ||||||
|  | 	variadic := params.Len() > 0 && r.bool() | ||||||
|  | 	return types.NewSignature(recv, params, results, variadic) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) paramList() *types.Tuple { | ||||||
|  | 	xs := make([]*types.Var, r.uint64()) | ||||||
|  | 	for i := range xs { | ||||||
|  | 		xs[i] = r.param() | ||||||
|  | 	} | ||||||
|  | 	return types.NewTuple(xs...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) param() *types.Var { | ||||||
|  | 	pos := r.pos() | ||||||
|  | 	name := r.ident() | ||||||
|  | 	typ := r.typ() | ||||||
|  | 	return types.NewParam(pos, r.currPkg, name, typ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) bool() bool { | ||||||
|  | 	return r.uint64() != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) int64() int64 { | ||||||
|  | 	n, err := binary.ReadVarint(&r.declReader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		errorf("readVarint: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) uint64() uint64 { | ||||||
|  | 	n, err := binary.ReadUvarint(&r.declReader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		errorf("readUvarint: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *importReader) byte() byte { | ||||||
|  | 	x, err := r.declReader.ReadByte() | ||||||
|  | 	if err != nil { | ||||||
|  | 		errorf("declReader.ReadByte: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return x | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | // +build !go1.11 | ||||||
|  | 
 | ||||||
|  | package gcimporter | ||||||
|  | 
 | ||||||
|  | import "go/types" | ||||||
|  | 
 | ||||||
|  | func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface { | ||||||
|  | 	named := make([]*types.Named, len(embeddeds)) | ||||||
|  | 	for i, e := range embeddeds { | ||||||
|  | 		var ok bool | ||||||
|  | 		named[i], ok = e.(*types.Named) | ||||||
|  | 		if !ok { | ||||||
|  | 			panic("embedding of non-defined interfaces in interfaces is not supported before Go 1.11") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return types.NewInterface(methods, named) | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | // +build go1.11 | ||||||
|  | 
 | ||||||
|  | package gcimporter | ||||||
|  | 
 | ||||||
|  | import "go/types" | ||||||
|  | 
 | ||||||
|  | func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface { | ||||||
|  | 	return types.NewInterfaceType(methods, embeddeds) | ||||||
|  | } | ||||||
							
								
								
									
										117
									
								
								vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | // Package packagesdriver fetches type sizes for go/packages and go/analysis. | ||||||
|  | package packagesdriver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/types" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/internal/gocommand" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var debug = false | ||||||
|  | 
 | ||||||
|  | func GetSizes(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { | ||||||
|  | 	// TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver. | ||||||
|  | 	const toolPrefix = "GOPACKAGESDRIVER=" | ||||||
|  | 	tool := "" | ||||||
|  | 	for _, env := range env { | ||||||
|  | 		if val := strings.TrimPrefix(env, toolPrefix); val != env { | ||||||
|  | 			tool = val | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if tool == "" { | ||||||
|  | 		var err error | ||||||
|  | 		tool, err = exec.LookPath("gopackagesdriver") | ||||||
|  | 		if err != nil { | ||||||
|  | 			// We did not find the driver, so use "go list". | ||||||
|  | 			tool = "off" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if tool == "off" { | ||||||
|  | 		return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := json.Marshal(struct { | ||||||
|  | 		Command    string   `json:"command"` | ||||||
|  | 		Env        []string `json:"env"` | ||||||
|  | 		BuildFlags []string `json:"build_flags"` | ||||||
|  | 	}{ | ||||||
|  | 		Command:    "sizes", | ||||||
|  | 		Env:        env, | ||||||
|  | 		BuildFlags: buildFlags, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to encode message to driver tool: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	buf := new(bytes.Buffer) | ||||||
|  | 	cmd := exec.CommandContext(ctx, tool) | ||||||
|  | 	cmd.Dir = dir | ||||||
|  | 	cmd.Env = env | ||||||
|  | 	cmd.Stdin = bytes.NewReader(req) | ||||||
|  | 	cmd.Stdout = buf | ||||||
|  | 	cmd.Stderr = new(bytes.Buffer) | ||||||
|  | 	if err := cmd.Run(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) | ||||||
|  | 	} | ||||||
|  | 	var response struct { | ||||||
|  | 		// Sizes, if not nil, is the types.Sizes to use when type checking. | ||||||
|  | 		Sizes *types.StdSizes | ||||||
|  | 	} | ||||||
|  | 	if err := json.Unmarshal(buf.Bytes(), &response); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return response.Sizes, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func GetSizesGolist(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { | ||||||
|  | 	inv := gocommand.Invocation{ | ||||||
|  | 		Verb:       "list", | ||||||
|  | 		Args:       []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"}, | ||||||
|  | 		Env:        env, | ||||||
|  | 		BuildFlags: buildFlags, | ||||||
|  | 		WorkingDir: dir, | ||||||
|  | 	} | ||||||
|  | 	stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) | ||||||
|  | 	var goarch, compiler string | ||||||
|  | 	if rawErr != nil { | ||||||
|  | 		if strings.Contains(rawErr.Error(), "cannot find main module") { | ||||||
|  | 			// User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc. | ||||||
|  | 			// TODO(matloob): Is this a problem in practice? | ||||||
|  | 			inv := gocommand.Invocation{ | ||||||
|  | 				Verb:       "env", | ||||||
|  | 				Args:       []string{"GOARCH"}, | ||||||
|  | 				Env:        env, | ||||||
|  | 				WorkingDir: dir, | ||||||
|  | 			} | ||||||
|  | 			envout, enverr := gocmdRunner.Run(ctx, inv) | ||||||
|  | 			if enverr != nil { | ||||||
|  | 				return nil, enverr | ||||||
|  | 			} | ||||||
|  | 			goarch = strings.TrimSpace(envout.String()) | ||||||
|  | 			compiler = "gc" | ||||||
|  | 		} else { | ||||||
|  | 			return nil, friendlyErr | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		fields := strings.Fields(stdout.String()) | ||||||
|  | 		if len(fields) < 2 { | ||||||
|  | 			return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\":\nstdout: <<%s>>\nstderr: <<%s>>", | ||||||
|  | 				stdout.String(), stderr.String()) | ||||||
|  | 		} | ||||||
|  | 		goarch = fields[0] | ||||||
|  | 		compiler = fields[1] | ||||||
|  | 	} | ||||||
|  | 	return types.SizesFor(compiler, goarch), nil | ||||||
|  | } | ||||||
							
								
								
									
										221
									
								
								vendor/golang.org/x/tools/go/packages/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								vendor/golang.org/x/tools/go/packages/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,221 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | Package packages loads Go packages for inspection and analysis. | ||||||
|  | 
 | ||||||
|  | The Load function takes as input a list of patterns and return a list of Package | ||||||
|  | structs describing individual packages matched by those patterns. | ||||||
|  | The LoadMode controls the amount of detail in the loaded packages. | ||||||
|  | 
 | ||||||
|  | Load passes most patterns directly to the underlying build tool, | ||||||
|  | but all patterns with the prefix "query=", where query is a | ||||||
|  | non-empty string of letters from [a-z], are reserved and may be | ||||||
|  | interpreted as query operators. | ||||||
|  | 
 | ||||||
|  | Two query operators are currently supported: "file" and "pattern". | ||||||
|  | 
 | ||||||
|  | The query "file=path/to/file.go" matches the package or packages enclosing | ||||||
|  | the Go source file path/to/file.go.  For example "file=~/go/src/fmt/print.go" | ||||||
|  | might return the packages "fmt" and "fmt [fmt.test]". | ||||||
|  | 
 | ||||||
|  | The query "pattern=string" causes "string" to be passed directly to | ||||||
|  | the underlying build tool. In most cases this is unnecessary, | ||||||
|  | but an application can use Load("pattern=" + x) as an escaping mechanism | ||||||
|  | to ensure that x is not interpreted as a query operator if it contains '='. | ||||||
|  | 
 | ||||||
|  | All other query operators are reserved for future use and currently | ||||||
|  | cause Load to report an error. | ||||||
|  | 
 | ||||||
|  | The Package struct provides basic information about the package, including | ||||||
|  | 
 | ||||||
|  |   - ID, a unique identifier for the package in the returned set; | ||||||
|  |   - GoFiles, the names of the package's Go source files; | ||||||
|  |   - Imports, a map from source import strings to the Packages they name; | ||||||
|  |   - Types, the type information for the package's exported symbols; | ||||||
|  |   - Syntax, the parsed syntax trees for the package's source code; and | ||||||
|  |   - TypeInfo, the result of a complete type-check of the package syntax trees. | ||||||
|  | 
 | ||||||
|  | (See the documentation for type Package for the complete list of fields | ||||||
|  | and more detailed descriptions.) | ||||||
|  | 
 | ||||||
|  | For example, | ||||||
|  | 
 | ||||||
|  | 	Load(nil, "bytes", "unicode...") | ||||||
|  | 
 | ||||||
|  | returns four Package structs describing the standard library packages | ||||||
|  | bytes, unicode, unicode/utf16, and unicode/utf8. Note that one pattern | ||||||
|  | can match multiple packages and that a package might be matched by | ||||||
|  | multiple patterns: in general it is not possible to determine which | ||||||
|  | packages correspond to which patterns. | ||||||
|  | 
 | ||||||
|  | Note that the list returned by Load contains only the packages matched | ||||||
|  | by the patterns. Their dependencies can be found by walking the import | ||||||
|  | graph using the Imports fields. | ||||||
|  | 
 | ||||||
|  | The Load function can be configured by passing a pointer to a Config as | ||||||
|  | the first argument. A nil Config is equivalent to the zero Config, which | ||||||
|  | causes Load to run in LoadFiles mode, collecting minimal information. | ||||||
|  | See the documentation for type Config for details. | ||||||
|  | 
 | ||||||
|  | As noted earlier, the Config.Mode controls the amount of detail | ||||||
|  | reported about the loaded packages. See the documentation for type LoadMode | ||||||
|  | for details. | ||||||
|  | 
 | ||||||
|  | Most tools should pass their command-line arguments (after any flags) | ||||||
|  | uninterpreted to the loader, so that the loader can interpret them | ||||||
|  | according to the conventions of the underlying build system. | ||||||
|  | See the Example function for typical usage. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | package packages // import "golang.org/x/tools/go/packages" | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | 
 | ||||||
|  | Motivation and design considerations | ||||||
|  | 
 | ||||||
|  | The new package's design solves problems addressed by two existing | ||||||
|  | packages: go/build, which locates and describes packages, and | ||||||
|  | golang.org/x/tools/go/loader, which loads, parses and type-checks them. | ||||||
|  | The go/build.Package structure encodes too much of the 'go build' way | ||||||
|  | of organizing projects, leaving us in need of a data type that describes a | ||||||
|  | package of Go source code independent of the underlying build system. | ||||||
|  | We wanted something that works equally well with go build and vgo, and | ||||||
|  | also other build systems such as Bazel and Blaze, making it possible to | ||||||
|  | construct analysis tools that work in all these environments. | ||||||
|  | Tools such as errcheck and staticcheck were essentially unavailable to | ||||||
|  | the Go community at Google, and some of Google's internal tools for Go | ||||||
|  | are unavailable externally. | ||||||
|  | This new package provides a uniform way to obtain package metadata by | ||||||
|  | querying each of these build systems, optionally supporting their | ||||||
|  | preferred command-line notations for packages, so that tools integrate | ||||||
|  | neatly with users' build environments. The Metadata query function | ||||||
|  | executes an external query tool appropriate to the current workspace. | ||||||
|  | 
 | ||||||
|  | Loading packages always returns the complete import graph "all the way down", | ||||||
|  | even if all you want is information about a single package, because the query | ||||||
|  | mechanisms of all the build systems we currently support ({go,vgo} list, and | ||||||
|  | blaze/bazel aspect-based query) cannot provide detailed information | ||||||
|  | about one package without visiting all its dependencies too, so there is | ||||||
|  | no additional asymptotic cost to providing transitive information. | ||||||
|  | (This property might not be true of a hypothetical 5th build system.) | ||||||
|  | 
 | ||||||
|  | In calls to TypeCheck, all initial packages, and any package that | ||||||
|  | transitively depends on one of them, must be loaded from source. | ||||||
|  | Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from | ||||||
|  | source; D may be loaded from export data, and E may not be loaded at all | ||||||
|  | (though it's possible that D's export data mentions it, so a | ||||||
|  | types.Package may be created for it and exposed.) | ||||||
|  | 
 | ||||||
|  | The old loader had a feature to suppress type-checking of function | ||||||
|  | bodies on a per-package basis, primarily intended to reduce the work of | ||||||
|  | obtaining type information for imported packages. Now that imports are | ||||||
|  | satisfied by export data, the optimization no longer seems necessary. | ||||||
|  | 
 | ||||||
|  | Despite some early attempts, the old loader did not exploit export data, | ||||||
|  | instead always using the equivalent of WholeProgram mode. This was due | ||||||
|  | to the complexity of mixing source and export data packages (now | ||||||
|  | resolved by the upward traversal mentioned above), and because export data | ||||||
|  | files were nearly always missing or stale. Now that 'go build' supports | ||||||
|  | caching, all the underlying build systems can guarantee to produce | ||||||
|  | export data in a reasonable (amortized) time. | ||||||
|  | 
 | ||||||
|  | Test "main" packages synthesized by the build system are now reported as | ||||||
|  | first-class packages, avoiding the need for clients (such as go/ssa) to | ||||||
|  | reinvent this generation logic. | ||||||
|  | 
 | ||||||
|  | One way in which go/packages is simpler than the old loader is in its | ||||||
|  | treatment of in-package tests. In-package tests are packages that | ||||||
|  | consist of all the files of the library under test, plus the test files. | ||||||
|  | The old loader constructed in-package tests by a two-phase process of | ||||||
|  | mutation called "augmentation": first it would construct and type check | ||||||
|  | all the ordinary library packages and type-check the packages that | ||||||
|  | depend on them; then it would add more (test) files to the package and | ||||||
|  | type-check again. This two-phase approach had four major problems: | ||||||
|  | 1) in processing the tests, the loader modified the library package, | ||||||
|  |    leaving no way for a client application to see both the test | ||||||
|  |    package and the library package; one would mutate into the other. | ||||||
|  | 2) because test files can declare additional methods on types defined in | ||||||
|  |    the library portion of the package, the dispatch of method calls in | ||||||
|  |    the library portion was affected by the presence of the test files. | ||||||
|  |    This should have been a clue that the packages were logically | ||||||
|  |    different. | ||||||
|  | 3) this model of "augmentation" assumed at most one in-package test | ||||||
|  |    per library package, which is true of projects using 'go build', | ||||||
|  |    but not other build systems. | ||||||
|  | 4) because of the two-phase nature of test processing, all packages that | ||||||
|  |    import the library package had to be processed before augmentation, | ||||||
|  |    forcing a "one-shot" API and preventing the client from calling Load | ||||||
|  |    in several times in sequence as is now possible in WholeProgram mode. | ||||||
|  |    (TypeCheck mode has a similar one-shot restriction for a different reason.) | ||||||
|  | 
 | ||||||
|  | Early drafts of this package supported "multi-shot" operation. | ||||||
|  | Although it allowed clients to make a sequence of calls (or concurrent | ||||||
|  | calls) to Load, building up the graph of Packages incrementally, | ||||||
|  | it was of marginal value: it complicated the API | ||||||
|  | (since it allowed some options to vary across calls but not others), | ||||||
|  | it complicated the implementation, | ||||||
|  | it cannot be made to work in Types mode, as explained above, | ||||||
|  | and it was less efficient than making one combined call (when this is possible). | ||||||
|  | Among the clients we have inspected, none made multiple calls to load | ||||||
|  | but could not be easily and satisfactorily modified to make only a single call. | ||||||
|  | However, applications changes may be required. | ||||||
|  | For example, the ssadump command loads the user-specified packages | ||||||
|  | and in addition the runtime package.  It is tempting to simply append | ||||||
|  | "runtime" to the user-provided list, but that does not work if the user | ||||||
|  | specified an ad-hoc package such as [a.go b.go]. | ||||||
|  | Instead, ssadump no longer requests the runtime package, | ||||||
|  | but seeks it among the dependencies of the user-specified packages, | ||||||
|  | and emits an error if it is not found. | ||||||
|  | 
 | ||||||
|  | Overlays: The Overlay field in the Config allows providing alternate contents | ||||||
|  | for Go source files, by providing a mapping from file path to contents. | ||||||
|  | go/packages will pull in new imports added in overlay files when go/packages | ||||||
|  | is run in LoadImports mode or greater. | ||||||
|  | Overlay support for the go list driver isn't complete yet: if the file doesn't | ||||||
|  | exist on disk, it will only be recognized in an overlay if it is a non-test file | ||||||
|  | and the package would be reported even without the overlay. | ||||||
|  | 
 | ||||||
|  | Questions & Tasks | ||||||
|  | 
 | ||||||
|  | - Add GOARCH/GOOS? | ||||||
|  |   They are not portable concepts, but could be made portable. | ||||||
|  |   Our goal has been to allow users to express themselves using the conventions | ||||||
|  |   of the underlying build system: if the build system honors GOARCH | ||||||
|  |   during a build and during a metadata query, then so should | ||||||
|  |   applications built atop that query mechanism. | ||||||
|  |   Conversely, if the target architecture of the build is determined by | ||||||
|  |   command-line flags, the application can pass the relevant | ||||||
|  |   flags through to the build system using a command such as: | ||||||
|  |     myapp -query_flag="--cpu=amd64" -query_flag="--os=darwin" | ||||||
|  |   However, this approach is low-level, unwieldy, and non-portable. | ||||||
|  |   GOOS and GOARCH seem important enough to warrant a dedicated option. | ||||||
|  | 
 | ||||||
|  | - How should we handle partial failures such as a mixture of good and | ||||||
|  |   malformed patterns, existing and non-existent packages, successful and | ||||||
|  |   failed builds, import failures, import cycles, and so on, in a call to | ||||||
|  |   Load? | ||||||
|  | 
 | ||||||
|  | - Support bazel, blaze, and go1.10 list, not just go1.11 list. | ||||||
|  | 
 | ||||||
|  | - Handle (and test) various partial success cases, e.g. | ||||||
|  |   a mixture of good packages and: | ||||||
|  |   invalid patterns | ||||||
|  |   nonexistent packages | ||||||
|  |   empty packages | ||||||
|  |   packages with malformed package or import declarations | ||||||
|  |   unreadable files | ||||||
|  |   import cycles | ||||||
|  |   other parse errors | ||||||
|  |   type errors | ||||||
|  |   Make sure we record errors at the correct place in the graph. | ||||||
|  | 
 | ||||||
|  | - Missing packages among initial arguments are not reported. | ||||||
|  |   Return bogus packages for them, like golist does. | ||||||
|  | 
 | ||||||
|  | - "undeclared name" errors (for example) are reported out of source file | ||||||
|  |   order. I suspect this is due to the breadth-first resolution now used | ||||||
|  |   by go/types. Is that a bug? Discuss with gri. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
							
								
								
									
										101
									
								
								vendor/golang.org/x/tools/go/packages/external.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								vendor/golang.org/x/tools/go/packages/external.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | // This file enables an external tool to intercept package requests. | ||||||
|  | // If the tool is present then its results are used in preference to | ||||||
|  | // the go list command. | ||||||
|  | 
 | ||||||
|  | package packages | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // The Driver Protocol | ||||||
|  | // | ||||||
|  | // The driver, given the inputs to a call to Load, returns metadata about the packages specified. | ||||||
|  | // This allows for different build systems to support go/packages by telling go/packages how the | ||||||
|  | // packages' source is organized. | ||||||
|  | // The driver is a binary, either specified by the GOPACKAGESDRIVER environment variable or in | ||||||
|  | // the path as gopackagesdriver. It's given the inputs to load in its argv. See the package | ||||||
|  | // documentation in doc.go for the full description of the patterns that need to be supported. | ||||||
|  | // A driver receives as a JSON-serialized driverRequest struct in standard input and will | ||||||
|  | // produce a JSON-serialized driverResponse (see definition in packages.go) in its standard output. | ||||||
|  | 
 | ||||||
|  | // driverRequest is used to provide the portion of Load's Config that is needed by a driver. | ||||||
|  | type driverRequest struct { | ||||||
|  | 	Mode LoadMode `json:"mode"` | ||||||
|  | 	// Env specifies the environment the underlying build system should be run in. | ||||||
|  | 	Env []string `json:"env"` | ||||||
|  | 	// BuildFlags are flags that should be passed to the underlying build system. | ||||||
|  | 	BuildFlags []string `json:"build_flags"` | ||||||
|  | 	// Tests specifies whether the patterns should also return test packages. | ||||||
|  | 	Tests bool `json:"tests"` | ||||||
|  | 	// Overlay maps file paths (relative to the driver's working directory) to the byte contents | ||||||
|  | 	// of overlay files. | ||||||
|  | 	Overlay map[string][]byte `json:"overlay"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // findExternalDriver returns the file path of a tool that supplies | ||||||
|  | // the build system package structure, or "" if not found." | ||||||
|  | // If GOPACKAGESDRIVER is set in the environment findExternalTool returns its | ||||||
|  | // value, otherwise it searches for a binary named gopackagesdriver on the PATH. | ||||||
|  | func findExternalDriver(cfg *Config) driver { | ||||||
|  | 	const toolPrefix = "GOPACKAGESDRIVER=" | ||||||
|  | 	tool := "" | ||||||
|  | 	for _, env := range cfg.Env { | ||||||
|  | 		if val := strings.TrimPrefix(env, toolPrefix); val != env { | ||||||
|  | 			tool = val | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if tool != "" && tool == "off" { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if tool == "" { | ||||||
|  | 		var err error | ||||||
|  | 		tool, err = exec.LookPath("gopackagesdriver") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return func(cfg *Config, words ...string) (*driverResponse, error) { | ||||||
|  | 		req, err := json.Marshal(driverRequest{ | ||||||
|  | 			Mode:       cfg.Mode, | ||||||
|  | 			Env:        cfg.Env, | ||||||
|  | 			BuildFlags: cfg.BuildFlags, | ||||||
|  | 			Tests:      cfg.Tests, | ||||||
|  | 			Overlay:    cfg.Overlay, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed to encode message to driver tool: %v", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		buf := new(bytes.Buffer) | ||||||
|  | 		stderr := new(bytes.Buffer) | ||||||
|  | 		cmd := exec.CommandContext(cfg.Context, tool, words...) | ||||||
|  | 		cmd.Dir = cfg.Dir | ||||||
|  | 		cmd.Env = cfg.Env | ||||||
|  | 		cmd.Stdin = bytes.NewReader(req) | ||||||
|  | 		cmd.Stdout = buf | ||||||
|  | 		cmd.Stderr = stderr | ||||||
|  | 
 | ||||||
|  | 		if err := cmd.Run(); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) | ||||||
|  | 		} | ||||||
|  | 		if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" { | ||||||
|  | 			fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var response driverResponse | ||||||
|  | 		if err := json.Unmarshal(buf.Bytes(), &response); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return &response, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										1012
									
								
								vendor/golang.org/x/tools/go/packages/golist.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1012
									
								
								vendor/golang.org/x/tools/go/packages/golist.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										568
									
								
								vendor/golang.org/x/tools/go/packages/golist_overlay.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										568
									
								
								vendor/golang.org/x/tools/go/packages/golist_overlay.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,568 @@ | |||||||
|  | package packages | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/parser" | ||||||
|  | 	"go/token" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"regexp" | ||||||
|  | 	"sort" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/internal/gocommand" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // processGolistOverlay provides rudimentary support for adding | ||||||
|  | // files that don't exist on disk to an overlay. The results can be | ||||||
|  | // sometimes incorrect. | ||||||
|  | // TODO(matloob): Handle unsupported cases, including the following: | ||||||
|  | // - determining the correct package to add given a new import path | ||||||
|  | func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) { | ||||||
|  | 	havePkgs := make(map[string]string) // importPath -> non-test package ID | ||||||
|  | 	needPkgsSet := make(map[string]bool) | ||||||
|  | 	modifiedPkgsSet := make(map[string]bool) | ||||||
|  | 
 | ||||||
|  | 	pkgOfDir := make(map[string][]*Package) | ||||||
|  | 	for _, pkg := range response.dr.Packages { | ||||||
|  | 		// This is an approximation of import path to id. This can be | ||||||
|  | 		// wrong for tests, vendored packages, and a number of other cases. | ||||||
|  | 		havePkgs[pkg.PkgPath] = pkg.ID | ||||||
|  | 		x := commonDir(pkg.GoFiles) | ||||||
|  | 		if x != "" { | ||||||
|  | 			pkgOfDir[x] = append(pkgOfDir[x], pkg) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If no new imports are added, it is safe to avoid loading any needPkgs. | ||||||
|  | 	// Otherwise, it's hard to tell which package is actually being loaded | ||||||
|  | 	// (due to vendoring) and whether any modified package will show up | ||||||
|  | 	// in the transitive set of dependencies (because new imports are added, | ||||||
|  | 	// potentially modifying the transitive set of dependencies). | ||||||
|  | 	var overlayAddsImports bool | ||||||
|  | 
 | ||||||
|  | 	// If both a package and its test package are created by the overlay, we | ||||||
|  | 	// need the real package first. Process all non-test files before test | ||||||
|  | 	// files, and make the whole process deterministic while we're at it. | ||||||
|  | 	var overlayFiles []string | ||||||
|  | 	for opath := range state.cfg.Overlay { | ||||||
|  | 		overlayFiles = append(overlayFiles, opath) | ||||||
|  | 	} | ||||||
|  | 	sort.Slice(overlayFiles, func(i, j int) bool { | ||||||
|  | 		iTest := strings.HasSuffix(overlayFiles[i], "_test.go") | ||||||
|  | 		jTest := strings.HasSuffix(overlayFiles[j], "_test.go") | ||||||
|  | 		if iTest != jTest { | ||||||
|  | 			return !iTest // non-tests are before tests. | ||||||
|  | 		} | ||||||
|  | 		return overlayFiles[i] < overlayFiles[j] | ||||||
|  | 	}) | ||||||
|  | 	for _, opath := range overlayFiles { | ||||||
|  | 		contents := state.cfg.Overlay[opath] | ||||||
|  | 		base := filepath.Base(opath) | ||||||
|  | 		dir := filepath.Dir(opath) | ||||||
|  | 		var pkg *Package           // if opath belongs to both a package and its test variant, this will be the test variant | ||||||
|  | 		var testVariantOf *Package // if opath is a test file, this is the package it is testing | ||||||
|  | 		var fileExists bool | ||||||
|  | 		isTestFile := strings.HasSuffix(opath, "_test.go") | ||||||
|  | 		pkgName, ok := extractPackageName(opath, contents) | ||||||
|  | 		if !ok { | ||||||
|  | 			// Don't bother adding a file that doesn't even have a parsable package statement | ||||||
|  | 			// to the overlay. | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		// If all the overlay files belong to a different package, change the | ||||||
|  | 		// package name to that package. | ||||||
|  | 		maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir]) | ||||||
|  | 	nextPackage: | ||||||
|  | 		for _, p := range response.dr.Packages { | ||||||
|  | 			if pkgName != p.Name && p.ID != "command-line-arguments" { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			for _, f := range p.GoFiles { | ||||||
|  | 				if !sameFile(filepath.Dir(f), dir) { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				// Make sure to capture information on the package's test variant, if needed. | ||||||
|  | 				if isTestFile && !hasTestFiles(p) { | ||||||
|  | 					// TODO(matloob): Are there packages other than the 'production' variant | ||||||
|  | 					// of a package that this can match? This shouldn't match the test main package | ||||||
|  | 					// because the file is generated in another directory. | ||||||
|  | 					testVariantOf = p | ||||||
|  | 					continue nextPackage | ||||||
|  | 				} else if !isTestFile && hasTestFiles(p) { | ||||||
|  | 					// We're examining a test variant, but the overlaid file is | ||||||
|  | 					// a non-test file. Because the overlay implementation | ||||||
|  | 					// (currently) only adds a file to one package, skip this | ||||||
|  | 					// package, so that we can add the file to the production | ||||||
|  | 					// variant of the package. (https://golang.org/issue/36857 | ||||||
|  | 					// tracks handling overlays on both the production and test | ||||||
|  | 					// variant of a package). | ||||||
|  | 					continue nextPackage | ||||||
|  | 				} | ||||||
|  | 				if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath { | ||||||
|  | 					// We have already seen the production version of the | ||||||
|  | 					// for which p is a test variant. | ||||||
|  | 					if hasTestFiles(p) { | ||||||
|  | 						testVariantOf = pkg | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				pkg = p | ||||||
|  | 				if filepath.Base(f) == base { | ||||||
|  | 					fileExists = true | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// The overlay could have included an entirely new package or an | ||||||
|  | 		// ad-hoc package. An ad-hoc package is one that we have manually | ||||||
|  | 		// constructed from inadequate `go list` results for a file= query. | ||||||
|  | 		// It will have the ID command-line-arguments. | ||||||
|  | 		if pkg == nil || pkg.ID == "command-line-arguments" { | ||||||
|  | 			// Try to find the module or gopath dir the file is contained in. | ||||||
|  | 			// Then for modules, add the module opath to the beginning. | ||||||
|  | 			pkgPath, ok, err := state.getPkgPath(dir) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, nil, err | ||||||
|  | 			} | ||||||
|  | 			if !ok { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			var forTest string // only set for x tests | ||||||
|  | 			isXTest := strings.HasSuffix(pkgName, "_test") | ||||||
|  | 			if isXTest { | ||||||
|  | 				forTest = pkgPath | ||||||
|  | 				pkgPath += "_test" | ||||||
|  | 			} | ||||||
|  | 			id := pkgPath | ||||||
|  | 			if isTestFile { | ||||||
|  | 				if isXTest { | ||||||
|  | 					id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest) | ||||||
|  | 				} else { | ||||||
|  | 					id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if pkg != nil { | ||||||
|  | 				// TODO(rstambler): We should change the package's path and ID | ||||||
|  | 				// here. The only issue is that this messes with the roots. | ||||||
|  | 			} else { | ||||||
|  | 				// Try to reclaim a package with the same ID, if it exists in the response. | ||||||
|  | 				for _, p := range response.dr.Packages { | ||||||
|  | 					if reclaimPackage(p, id, opath, contents) { | ||||||
|  | 						pkg = p | ||||||
|  | 						break | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				// Otherwise, create a new package. | ||||||
|  | 				if pkg == nil { | ||||||
|  | 					pkg = &Package{ | ||||||
|  | 						PkgPath: pkgPath, | ||||||
|  | 						ID:      id, | ||||||
|  | 						Name:    pkgName, | ||||||
|  | 						Imports: make(map[string]*Package), | ||||||
|  | 					} | ||||||
|  | 					response.addPackage(pkg) | ||||||
|  | 					havePkgs[pkg.PkgPath] = id | ||||||
|  | 					// Add the production package's sources for a test variant. | ||||||
|  | 					if isTestFile && !isXTest && testVariantOf != nil { | ||||||
|  | 						pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) | ||||||
|  | 						pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) | ||||||
|  | 						// Add the package under test and its imports to the test variant. | ||||||
|  | 						pkg.forTest = testVariantOf.PkgPath | ||||||
|  | 						for k, v := range testVariantOf.Imports { | ||||||
|  | 							pkg.Imports[k] = &Package{ID: v.ID} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					if isXTest { | ||||||
|  | 						pkg.forTest = forTest | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !fileExists { | ||||||
|  | 			pkg.GoFiles = append(pkg.GoFiles, opath) | ||||||
|  | 			// TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior | ||||||
|  | 			// if the file will be ignored due to its build tags. | ||||||
|  | 			pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath) | ||||||
|  | 			modifiedPkgsSet[pkg.ID] = true | ||||||
|  | 		} | ||||||
|  | 		imports, err := extractImports(opath, contents) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// Let the parser or type checker report errors later. | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		for _, imp := range imports { | ||||||
|  | 			// TODO(rstambler): If the package is an x test and the import has | ||||||
|  | 			// a test variant, make sure to replace it. | ||||||
|  | 			if _, found := pkg.Imports[imp]; found { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			overlayAddsImports = true | ||||||
|  | 			id, ok := havePkgs[imp] | ||||||
|  | 			if !ok { | ||||||
|  | 				var err error | ||||||
|  | 				id, err = state.resolveImport(dir, imp) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return nil, nil, err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			pkg.Imports[imp] = &Package{ID: id} | ||||||
|  | 			// Add dependencies to the non-test variant version of this package as well. | ||||||
|  | 			if testVariantOf != nil { | ||||||
|  | 				testVariantOf.Imports[imp] = &Package{ID: id} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// toPkgPath guesses the package path given the id. | ||||||
|  | 	toPkgPath := func(sourceDir, id string) (string, error) { | ||||||
|  | 		if i := strings.IndexByte(id, ' '); i >= 0 { | ||||||
|  | 			return state.resolveImport(sourceDir, id[:i]) | ||||||
|  | 		} | ||||||
|  | 		return state.resolveImport(sourceDir, id) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Now that new packages have been created, do another pass to determine | ||||||
|  | 	// the new set of missing packages. | ||||||
|  | 	for _, pkg := range response.dr.Packages { | ||||||
|  | 		for _, imp := range pkg.Imports { | ||||||
|  | 			if len(pkg.GoFiles) == 0 { | ||||||
|  | 				return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath) | ||||||
|  | 			} | ||||||
|  | 			pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, nil, err | ||||||
|  | 			} | ||||||
|  | 			if _, ok := havePkgs[pkgPath]; !ok { | ||||||
|  | 				needPkgsSet[pkgPath] = true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if overlayAddsImports { | ||||||
|  | 		needPkgs = make([]string, 0, len(needPkgsSet)) | ||||||
|  | 		for pkg := range needPkgsSet { | ||||||
|  | 			needPkgs = append(needPkgs, pkg) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	modifiedPkgs = make([]string, 0, len(modifiedPkgsSet)) | ||||||
|  | 	for pkg := range modifiedPkgsSet { | ||||||
|  | 		modifiedPkgs = append(modifiedPkgs, pkg) | ||||||
|  | 	} | ||||||
|  | 	return modifiedPkgs, needPkgs, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // resolveImport finds the ID of a package given its import path. | ||||||
|  | // In particular, it will find the right vendored copy when in GOPATH mode. | ||||||
|  | func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) { | ||||||
|  | 	env, err := state.getEnv() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	if env["GOMOD"] != "" { | ||||||
|  | 		return importPath, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	searchDir := sourceDir | ||||||
|  | 	for { | ||||||
|  | 		vendorDir := filepath.Join(searchDir, "vendor") | ||||||
|  | 		exists, ok := state.vendorDirs[vendorDir] | ||||||
|  | 		if !ok { | ||||||
|  | 			info, err := os.Stat(vendorDir) | ||||||
|  | 			exists = err == nil && info.IsDir() | ||||||
|  | 			state.vendorDirs[vendorDir] = exists | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if exists { | ||||||
|  | 			vendoredPath := filepath.Join(vendorDir, importPath) | ||||||
|  | 			if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() { | ||||||
|  | 				// We should probably check for .go files here, but shame on anyone who fools us. | ||||||
|  | 				path, ok, err := state.getPkgPath(vendoredPath) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return "", err | ||||||
|  | 				} | ||||||
|  | 				if ok { | ||||||
|  | 					return path, nil | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// We know we've hit the top of the filesystem when we Dir / and get /, | ||||||
|  | 		// or C:\ and get C:\, etc. | ||||||
|  | 		next := filepath.Dir(searchDir) | ||||||
|  | 		if next == searchDir { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		searchDir = next | ||||||
|  | 	} | ||||||
|  | 	return importPath, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func hasTestFiles(p *Package) bool { | ||||||
|  | 	for _, f := range p.GoFiles { | ||||||
|  | 		if strings.HasSuffix(f, "_test.go") { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // determineRootDirs returns a mapping from absolute directories that could | ||||||
|  | // contain code to their corresponding import path prefixes. | ||||||
|  | func (state *golistState) determineRootDirs() (map[string]string, error) { | ||||||
|  | 	env, err := state.getEnv() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if env["GOMOD"] != "" { | ||||||
|  | 		state.rootsOnce.Do(func() { | ||||||
|  | 			state.rootDirs, state.rootDirsError = state.determineRootDirsModules() | ||||||
|  | 		}) | ||||||
|  | 	} else { | ||||||
|  | 		state.rootsOnce.Do(func() { | ||||||
|  | 			state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH() | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return state.rootDirs, state.rootDirsError | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (state *golistState) determineRootDirsModules() (map[string]string, error) { | ||||||
|  | 	// List all of the modules--the first will be the directory for the main | ||||||
|  | 	// module. Any replaced modules will also need to be treated as roots. | ||||||
|  | 	// Editing files in the module cache isn't a great idea, so we don't | ||||||
|  | 	// plan to ever support that. | ||||||
|  | 	out, err := state.invokeGo("list", "-m", "-json", "all") | ||||||
|  | 	if err != nil { | ||||||
|  | 		// 'go list all' will fail if we're outside of a module and | ||||||
|  | 		// GO111MODULE=on. Try falling back without 'all'. | ||||||
|  | 		var innerErr error | ||||||
|  | 		out, innerErr = state.invokeGo("list", "-m", "-json") | ||||||
|  | 		if innerErr != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	roots := map[string]string{} | ||||||
|  | 	modules := map[string]string{} | ||||||
|  | 	var i int | ||||||
|  | 	for dec := json.NewDecoder(out); dec.More(); { | ||||||
|  | 		mod := new(gocommand.ModuleJSON) | ||||||
|  | 		if err := dec.Decode(mod); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if mod.Dir != "" && mod.Path != "" { | ||||||
|  | 			// This is a valid module; add it to the map. | ||||||
|  | 			absDir, err := filepath.Abs(mod.Dir) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			modules[absDir] = mod.Path | ||||||
|  | 			// The first result is the main module. | ||||||
|  | 			if i == 0 || mod.Replace != nil && mod.Replace.Path != "" { | ||||||
|  | 				roots[absDir] = mod.Path | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | 	return roots, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) { | ||||||
|  | 	m := map[string]string{} | ||||||
|  | 	for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) { | ||||||
|  | 		absDir, err := filepath.Abs(dir) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		m[filepath.Join(absDir, "src")] = "" | ||||||
|  | 	} | ||||||
|  | 	return m, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func extractImports(filename string, contents []byte) ([]string, error) { | ||||||
|  | 	f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset? | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var res []string | ||||||
|  | 	for _, imp := range f.Imports { | ||||||
|  | 		quotedPath := imp.Path.Value | ||||||
|  | 		path, err := strconv.Unquote(quotedPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		res = append(res, path) | ||||||
|  | 	} | ||||||
|  | 	return res, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // reclaimPackage attempts to reuse a package that failed to load in an overlay. | ||||||
|  | // | ||||||
|  | // If the package has errors and has no Name, GoFiles, or Imports, | ||||||
|  | // then it's possible that it doesn't yet exist on disk. | ||||||
|  | func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool { | ||||||
|  | 	// TODO(rstambler): Check the message of the actual error? | ||||||
|  | 	// It differs between $GOPATH and module mode. | ||||||
|  | 	if pkg.ID != id { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if len(pkg.Errors) != 1 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if pkg.Name != "" || pkg.ExportFile != "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if len(pkg.Imports) > 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	pkgName, ok := extractPackageName(filename, contents) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	pkg.Name = pkgName | ||||||
|  | 	pkg.Errors = nil | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func extractPackageName(filename string, contents []byte) (string, bool) { | ||||||
|  | 	// TODO(rstambler): Check the message of the actual error? | ||||||
|  | 	// It differs between $GOPATH and module mode. | ||||||
|  | 	f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset? | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 	return f.Name.Name, true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func commonDir(a []string) string { | ||||||
|  | 	seen := make(map[string]bool) | ||||||
|  | 	x := append([]string{}, a...) | ||||||
|  | 	for _, f := range x { | ||||||
|  | 		seen[filepath.Dir(f)] = true | ||||||
|  | 	} | ||||||
|  | 	if len(seen) > 1 { | ||||||
|  | 		log.Fatalf("commonDir saw %v for %v", seen, x) | ||||||
|  | 	} | ||||||
|  | 	for k := range seen { | ||||||
|  | 		// len(seen) == 1 | ||||||
|  | 		return k | ||||||
|  | 	} | ||||||
|  | 	return "" // no files | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // It is possible that the files in the disk directory dir have a different package | ||||||
|  | // name from newName, which is deduced from the overlays. If they all have a different | ||||||
|  | // package name, and they all have the same package name, then that name becomes | ||||||
|  | // the package name. | ||||||
|  | // It returns true if it changes the package name, false otherwise. | ||||||
|  | func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) { | ||||||
|  | 	names := make(map[string]int) | ||||||
|  | 	for _, p := range pkgsOfDir { | ||||||
|  | 		names[p.Name]++ | ||||||
|  | 	} | ||||||
|  | 	if len(names) != 1 { | ||||||
|  | 		// some files are in different packages | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var oldName string | ||||||
|  | 	for k := range names { | ||||||
|  | 		oldName = k | ||||||
|  | 	} | ||||||
|  | 	if newName == oldName { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// We might have a case where all of the package names in the directory are | ||||||
|  | 	// the same, but the overlay file is for an x test, which belongs to its | ||||||
|  | 	// own package. If the x test does not yet exist on disk, we may not yet | ||||||
|  | 	// have its package name on disk, but we should not rename the packages. | ||||||
|  | 	// | ||||||
|  | 	// We use a heuristic to determine if this file belongs to an x test: | ||||||
|  | 	// The test file should have a package name whose package name has a _test | ||||||
|  | 	// suffix or looks like "newName_test". | ||||||
|  | 	maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test") | ||||||
|  | 	if isTestFile && maybeXTest { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	for _, p := range pkgsOfDir { | ||||||
|  | 		p.Name = newName | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // This function is copy-pasted from | ||||||
|  | // https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/cmd/go/internal/search/search.go#L360. | ||||||
|  | // It should be deleted when we remove support for overlays from go/packages. | ||||||
|  | // | ||||||
|  | // NOTE: This does not handle any ./... or ./ style queries, as this function | ||||||
|  | // doesn't know the working directory. | ||||||
|  | // | ||||||
|  | // matchPattern(pattern)(name) reports whether | ||||||
|  | // name matches pattern. Pattern is a limited glob | ||||||
|  | // pattern in which '...' means 'any string' and there | ||||||
|  | // is no other special syntax. | ||||||
|  | // Unfortunately, there are two special cases. Quoting "go help packages": | ||||||
|  | // | ||||||
|  | // First, /... at the end of the pattern can match an empty string, | ||||||
|  | // so that net/... matches both net and packages in its subdirectories, like net/http. | ||||||
|  | // Second, any slash-separated pattern element containing a wildcard never | ||||||
|  | // participates in a match of the "vendor" element in the path of a vendored | ||||||
|  | // package, so that ./... does not match packages in subdirectories of | ||||||
|  | // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. | ||||||
|  | // Note, however, that a directory named vendor that itself contains code | ||||||
|  | // is not a vendored package: cmd/vendor would be a command named vendor, | ||||||
|  | // and the pattern cmd/... matches it. | ||||||
|  | func matchPattern(pattern string) func(name string) bool { | ||||||
|  | 	// Convert pattern to regular expression. | ||||||
|  | 	// The strategy for the trailing /... is to nest it in an explicit ? expression. | ||||||
|  | 	// The strategy for the vendor exclusion is to change the unmatchable | ||||||
|  | 	// vendor strings to a disallowed code point (vendorChar) and to use | ||||||
|  | 	// "(anything but that codepoint)*" as the implementation of the ... wildcard. | ||||||
|  | 	// This is a bit complicated but the obvious alternative, | ||||||
|  | 	// namely a hand-written search like in most shell glob matchers, | ||||||
|  | 	// is too easy to make accidentally exponential. | ||||||
|  | 	// Using package regexp guarantees linear-time matching. | ||||||
|  | 
 | ||||||
|  | 	const vendorChar = "\x00" | ||||||
|  | 
 | ||||||
|  | 	if strings.Contains(pattern, vendorChar) { | ||||||
|  | 		return func(name string) bool { return false } | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	re := regexp.QuoteMeta(pattern) | ||||||
|  | 	re = replaceVendor(re, vendorChar) | ||||||
|  | 	switch { | ||||||
|  | 	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): | ||||||
|  | 		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` | ||||||
|  | 	case re == vendorChar+`/\.\.\.`: | ||||||
|  | 		re = `(/vendor|/` + vendorChar + `/\.\.\.)` | ||||||
|  | 	case strings.HasSuffix(re, `/\.\.\.`): | ||||||
|  | 		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` | ||||||
|  | 	} | ||||||
|  | 	re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`) | ||||||
|  | 
 | ||||||
|  | 	reg := regexp.MustCompile(`^` + re + `$`) | ||||||
|  | 
 | ||||||
|  | 	return func(name string) bool { | ||||||
|  | 		if strings.Contains(name, vendorChar) { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		return reg.MatchString(replaceVendor(name, vendorChar)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // replaceVendor returns the result of replacing | ||||||
|  | // non-trailing vendor path elements in x with repl. | ||||||
|  | func replaceVendor(x, repl string) string { | ||||||
|  | 	if !strings.Contains(x, "vendor") { | ||||||
|  | 		return x | ||||||
|  | 	} | ||||||
|  | 	elem := strings.Split(x, "/") | ||||||
|  | 	for i := 0; i < len(elem)-1; i++ { | ||||||
|  | 		if elem[i] == "vendor" { | ||||||
|  | 			elem[i] = repl | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return strings.Join(elem, "/") | ||||||
|  | } | ||||||
							
								
								
									
										1212
									
								
								vendor/golang.org/x/tools/go/packages/packages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1212
									
								
								vendor/golang.org/x/tools/go/packages/packages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										55
									
								
								vendor/golang.org/x/tools/go/packages/visit.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vendor/golang.org/x/tools/go/packages/visit.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | package packages | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"sort" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Visit visits all the packages in the import graph whose roots are | ||||||
|  | // pkgs, calling the optional pre function the first time each package | ||||||
|  | // is encountered (preorder), and the optional post function after a | ||||||
|  | // package's dependencies have been visited (postorder). | ||||||
|  | // The boolean result of pre(pkg) determines whether | ||||||
|  | // the imports of package pkg are visited. | ||||||
|  | func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) { | ||||||
|  | 	seen := make(map[*Package]bool) | ||||||
|  | 	var visit func(*Package) | ||||||
|  | 	visit = func(pkg *Package) { | ||||||
|  | 		if !seen[pkg] { | ||||||
|  | 			seen[pkg] = true | ||||||
|  | 
 | ||||||
|  | 			if pre == nil || pre(pkg) { | ||||||
|  | 				paths := make([]string, 0, len(pkg.Imports)) | ||||||
|  | 				for path := range pkg.Imports { | ||||||
|  | 					paths = append(paths, path) | ||||||
|  | 				} | ||||||
|  | 				sort.Strings(paths) // Imports is a map, this makes visit stable | ||||||
|  | 				for _, path := range paths { | ||||||
|  | 					visit(pkg.Imports[path]) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if post != nil { | ||||||
|  | 				post(pkg) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, pkg := range pkgs { | ||||||
|  | 		visit(pkg) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PrintErrors prints to os.Stderr the accumulated errors of all | ||||||
|  | // packages in the import graph rooted at pkgs, dependencies first. | ||||||
|  | // PrintErrors returns the number of errors printed. | ||||||
|  | func PrintErrors(pkgs []*Package) int { | ||||||
|  | 	var n int | ||||||
|  | 	Visit(pkgs, nil, func(pkg *Package) { | ||||||
|  | 		for _, err := range pkg.Errors { | ||||||
|  | 			fmt.Fprintln(os.Stderr, err) | ||||||
|  | 			n++ | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 	return n | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								vendor/golang.org/x/tools/internal/event/core/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								vendor/golang.org/x/tools/internal/event/core/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | // Copyright 2019 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 core provides support for event based telemetry. | ||||||
|  | package core | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/internal/event/label" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Event holds the information about an event of note that ocurred. | ||||||
|  | type Event struct { | ||||||
|  | 	at time.Time | ||||||
|  | 
 | ||||||
|  | 	// As events are often on the stack, storing the first few labels directly | ||||||
|  | 	// in the event can avoid an allocation at all for the very common cases of | ||||||
|  | 	// simple events. | ||||||
|  | 	// The length needs to be large enough to cope with the majority of events | ||||||
|  | 	// but no so large as to cause undue stack pressure. | ||||||
|  | 	// A log message with two values will use 3 labels (one for each value and | ||||||
|  | 	// one for the message itself). | ||||||
|  | 
 | ||||||
|  | 	static  [3]label.Label // inline storage for the first few labels | ||||||
|  | 	dynamic []label.Label  // dynamically sized storage for remaining labels | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // eventLabelMap implements label.Map for a the labels of an Event. | ||||||
|  | type eventLabelMap struct { | ||||||
|  | 	event Event | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ev Event) At() time.Time { return ev.at } | ||||||
|  | 
 | ||||||
|  | func (ev Event) Format(f fmt.State, r rune) { | ||||||
|  | 	if !ev.at.IsZero() { | ||||||
|  | 		fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 ")) | ||||||
|  | 	} | ||||||
|  | 	for index := 0; ev.Valid(index); index++ { | ||||||
|  | 		if l := ev.Label(index); l.Valid() { | ||||||
|  | 			fmt.Fprintf(f, "\n\t%v", l) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ev Event) Valid(index int) bool { | ||||||
|  | 	return index >= 0 && index < len(ev.static)+len(ev.dynamic) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ev Event) Label(index int) label.Label { | ||||||
|  | 	if index < len(ev.static) { | ||||||
|  | 		return ev.static[index] | ||||||
|  | 	} | ||||||
|  | 	return ev.dynamic[index-len(ev.static)] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ev Event) Find(key label.Key) label.Label { | ||||||
|  | 	for _, l := range ev.static { | ||||||
|  | 		if l.Key() == key { | ||||||
|  | 			return l | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, l := range ev.dynamic { | ||||||
|  | 		if l.Key() == key { | ||||||
|  | 			return l | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return label.Label{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func MakeEvent(static [3]label.Label, labels []label.Label) Event { | ||||||
|  | 	return Event{ | ||||||
|  | 		static:  static, | ||||||
|  | 		dynamic: labels, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CloneEvent event returns a copy of the event with the time adjusted to at. | ||||||
|  | func CloneEvent(ev Event, at time.Time) Event { | ||||||
|  | 	ev.at = at | ||||||
|  | 	return ev | ||||||
|  | } | ||||||
							
								
								
									
										70
									
								
								vendor/golang.org/x/tools/internal/event/core/export.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								vendor/golang.org/x/tools/internal/event/core/export.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | |||||||
|  | // Copyright 2019 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 core | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  | 	"unsafe" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/internal/event/label" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Exporter is a function that handles events. | ||||||
|  | // It may return a modified context and event. | ||||||
|  | type Exporter func(context.Context, Event, label.Map) context.Context | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	exporter unsafe.Pointer | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // SetExporter sets the global exporter function that handles all events. | ||||||
|  | // The exporter is called synchronously from the event call site, so it should | ||||||
|  | // return quickly so as not to hold up user code. | ||||||
|  | func SetExporter(e Exporter) { | ||||||
|  | 	p := unsafe.Pointer(&e) | ||||||
|  | 	if e == nil { | ||||||
|  | 		// &e is always valid, and so p is always valid, but for the early abort | ||||||
|  | 		// of ProcessEvent to be efficient it needs to make the nil check on the | ||||||
|  | 		// pointer without having to dereference it, so we make the nil function | ||||||
|  | 		// also a nil pointer | ||||||
|  | 		p = nil | ||||||
|  | 	} | ||||||
|  | 	atomic.StorePointer(&exporter, p) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // deliver is called to deliver an event to the supplied exporter. | ||||||
|  | // it will fill in the time. | ||||||
|  | func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context { | ||||||
|  | 	// add the current time to the event | ||||||
|  | 	ev.at = time.Now() | ||||||
|  | 	// hand the event off to the current exporter | ||||||
|  | 	return exporter(ctx, ev, ev) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Export is called to deliver an event to the global exporter if set. | ||||||
|  | func Export(ctx context.Context, ev Event) context.Context { | ||||||
|  | 	// get the global exporter and abort early if there is not one | ||||||
|  | 	exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) | ||||||
|  | 	if exporterPtr == nil { | ||||||
|  | 		return ctx | ||||||
|  | 	} | ||||||
|  | 	return deliver(ctx, *exporterPtr, ev) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ExportPair is called to deliver a start event to the supplied exporter. | ||||||
|  | // It also returns a function that will deliver the end event to the same | ||||||
|  | // exporter. | ||||||
|  | // It will fill in the time. | ||||||
|  | func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) { | ||||||
|  | 	// get the global exporter and abort early if there is not one | ||||||
|  | 	exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) | ||||||
|  | 	if exporterPtr == nil { | ||||||
|  | 		return ctx, func() {} | ||||||
|  | 	} | ||||||
|  | 	ctx = deliver(ctx, *exporterPtr, begin) | ||||||
|  | 	return ctx, func() { deliver(ctx, *exporterPtr, end) } | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								vendor/golang.org/x/tools/internal/event/core/fast.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								vendor/golang.org/x/tools/internal/event/core/fast.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  | // Copyright 2019 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 core | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/internal/event/keys" | ||||||
|  | 	"golang.org/x/tools/internal/event/label" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Log1 takes a message and one label delivers a log event to the exporter. | ||||||
|  | // It is a customized version of Print that is faster and does no allocation. | ||||||
|  | func Log1(ctx context.Context, message string, t1 label.Label) { | ||||||
|  | 	Export(ctx, MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Msg.Of(message), | ||||||
|  | 		t1, | ||||||
|  | 	}, nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Log2 takes a message and two labels and delivers a log event to the exporter. | ||||||
|  | // It is a customized version of Print that is faster and does no allocation. | ||||||
|  | func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) { | ||||||
|  | 	Export(ctx, MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Msg.Of(message), | ||||||
|  | 		t1, | ||||||
|  | 		t2, | ||||||
|  | 	}, nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Metric1 sends a label event to the exporter with the supplied labels. | ||||||
|  | func Metric1(ctx context.Context, t1 label.Label) context.Context { | ||||||
|  | 	return Export(ctx, MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Metric.New(), | ||||||
|  | 		t1, | ||||||
|  | 	}, nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Metric2 sends a label event to the exporter with the supplied labels. | ||||||
|  | func Metric2(ctx context.Context, t1, t2 label.Label) context.Context { | ||||||
|  | 	return Export(ctx, MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Metric.New(), | ||||||
|  | 		t1, | ||||||
|  | 		t2, | ||||||
|  | 	}, nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Start1 sends a span start event with the supplied label list to the exporter. | ||||||
|  | // It also returns a function that will end the span, which should normally be | ||||||
|  | // deferred. | ||||||
|  | func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) { | ||||||
|  | 	return ExportPair(ctx, | ||||||
|  | 		MakeEvent([3]label.Label{ | ||||||
|  | 			keys.Start.Of(name), | ||||||
|  | 			t1, | ||||||
|  | 		}, nil), | ||||||
|  | 		MakeEvent([3]label.Label{ | ||||||
|  | 			keys.End.New(), | ||||||
|  | 		}, nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Start2 sends a span start event with the supplied label list to the exporter. | ||||||
|  | // It also returns a function that will end the span, which should normally be | ||||||
|  | // deferred. | ||||||
|  | func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) { | ||||||
|  | 	return ExportPair(ctx, | ||||||
|  | 		MakeEvent([3]label.Label{ | ||||||
|  | 			keys.Start.Of(name), | ||||||
|  | 			t1, | ||||||
|  | 			t2, | ||||||
|  | 		}, nil), | ||||||
|  | 		MakeEvent([3]label.Label{ | ||||||
|  | 			keys.End.New(), | ||||||
|  | 		}, nil)) | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								vendor/golang.org/x/tools/internal/event/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/golang.org/x/tools/internal/event/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | // Copyright 2019 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 event provides a set of packages that cover the main | ||||||
|  | // concepts of telemetry in an implementation agnostic way. | ||||||
|  | package event | ||||||
							
								
								
									
										127
									
								
								vendor/golang.org/x/tools/internal/event/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								vendor/golang.org/x/tools/internal/event/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | |||||||
|  | // Copyright 2019 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 event | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/internal/event/core" | ||||||
|  | 	"golang.org/x/tools/internal/event/keys" | ||||||
|  | 	"golang.org/x/tools/internal/event/label" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Exporter is a function that handles events. | ||||||
|  | // It may return a modified context and event. | ||||||
|  | type Exporter func(context.Context, core.Event, label.Map) context.Context | ||||||
|  | 
 | ||||||
|  | // SetExporter sets the global exporter function that handles all events. | ||||||
|  | // The exporter is called synchronously from the event call site, so it should | ||||||
|  | // return quickly so as not to hold up user code. | ||||||
|  | func SetExporter(e Exporter) { | ||||||
|  | 	core.SetExporter(core.Exporter(e)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Log takes a message and a label list and combines them into a single event | ||||||
|  | // before delivering them to the exporter. | ||||||
|  | func Log(ctx context.Context, message string, labels ...label.Label) { | ||||||
|  | 	core.Export(ctx, core.MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Msg.Of(message), | ||||||
|  | 	}, labels)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsLog returns true if the event was built by the Log function. | ||||||
|  | // It is intended to be used in exporters to identify the semantics of the | ||||||
|  | // event when deciding what to do with it. | ||||||
|  | func IsLog(ev core.Event) bool { | ||||||
|  | 	return ev.Label(0).Key() == keys.Msg | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Error takes a message and a label list and combines them into a single event | ||||||
|  | // before delivering them to the exporter. It captures the error in the | ||||||
|  | // delivered event. | ||||||
|  | func Error(ctx context.Context, message string, err error, labels ...label.Label) { | ||||||
|  | 	core.Export(ctx, core.MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Msg.Of(message), | ||||||
|  | 		keys.Err.Of(err), | ||||||
|  | 	}, labels)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsError returns true if the event was built by the Error function. | ||||||
|  | // It is intended to be used in exporters to identify the semantics of the | ||||||
|  | // event when deciding what to do with it. | ||||||
|  | func IsError(ev core.Event) bool { | ||||||
|  | 	return ev.Label(0).Key() == keys.Msg && | ||||||
|  | 		ev.Label(1).Key() == keys.Err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Metric sends a label event to the exporter with the supplied labels. | ||||||
|  | func Metric(ctx context.Context, labels ...label.Label) { | ||||||
|  | 	core.Export(ctx, core.MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Metric.New(), | ||||||
|  | 	}, labels)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsMetric returns true if the event was built by the Metric function. | ||||||
|  | // It is intended to be used in exporters to identify the semantics of the | ||||||
|  | // event when deciding what to do with it. | ||||||
|  | func IsMetric(ev core.Event) bool { | ||||||
|  | 	return ev.Label(0).Key() == keys.Metric | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Label sends a label event to the exporter with the supplied labels. | ||||||
|  | func Label(ctx context.Context, labels ...label.Label) context.Context { | ||||||
|  | 	return core.Export(ctx, core.MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Label.New(), | ||||||
|  | 	}, labels)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsLabel returns true if the event was built by the Label function. | ||||||
|  | // It is intended to be used in exporters to identify the semantics of the | ||||||
|  | // event when deciding what to do with it. | ||||||
|  | func IsLabel(ev core.Event) bool { | ||||||
|  | 	return ev.Label(0).Key() == keys.Label | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Start sends a span start event with the supplied label list to the exporter. | ||||||
|  | // It also returns a function that will end the span, which should normally be | ||||||
|  | // deferred. | ||||||
|  | func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) { | ||||||
|  | 	return core.ExportPair(ctx, | ||||||
|  | 		core.MakeEvent([3]label.Label{ | ||||||
|  | 			keys.Start.Of(name), | ||||||
|  | 		}, labels), | ||||||
|  | 		core.MakeEvent([3]label.Label{ | ||||||
|  | 			keys.End.New(), | ||||||
|  | 		}, nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsStart returns true if the event was built by the Start function. | ||||||
|  | // It is intended to be used in exporters to identify the semantics of the | ||||||
|  | // event when deciding what to do with it. | ||||||
|  | func IsStart(ev core.Event) bool { | ||||||
|  | 	return ev.Label(0).Key() == keys.Start | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsEnd returns true if the event was built by the End function. | ||||||
|  | // It is intended to be used in exporters to identify the semantics of the | ||||||
|  | // event when deciding what to do with it. | ||||||
|  | func IsEnd(ev core.Event) bool { | ||||||
|  | 	return ev.Label(0).Key() == keys.End | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Detach returns a context without an associated span. | ||||||
|  | // This allows the creation of spans that are not children of the current span. | ||||||
|  | func Detach(ctx context.Context) context.Context { | ||||||
|  | 	return core.Export(ctx, core.MakeEvent([3]label.Label{ | ||||||
|  | 		keys.Detach.New(), | ||||||
|  | 	}, nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsDetach returns true if the event was built by the Detach function. | ||||||
|  | // It is intended to be used in exporters to identify the semantics of the | ||||||
|  | // event when deciding what to do with it. | ||||||
|  | func IsDetach(ev core.Event) bool { | ||||||
|  | 	return ev.Label(0).Key() == keys.Detach | ||||||
|  | } | ||||||
							
								
								
									
										564
									
								
								vendor/golang.org/x/tools/internal/event/keys/keys.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										564
									
								
								vendor/golang.org/x/tools/internal/event/keys/keys.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,564 @@ | |||||||
|  | // Copyright 2019 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 keys | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"math" | ||||||
|  | 	"strconv" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/internal/event/label" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Value represents a key for untyped values. | ||||||
|  | type Value struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New creates a new Key for untyped values. | ||||||
|  | func New(name, description string) *Value { | ||||||
|  | 	return &Value{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Value) Name() string        { return k.name } | ||||||
|  | func (k *Value) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Value) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	fmt.Fprint(w, k.From(l)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Value) Get(lm label.Map) interface{} { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) } | ||||||
|  | 
 | ||||||
|  | // Tag represents a key for tagging labels that have no value. | ||||||
|  | // These are used when the existence of the label is the entire information it | ||||||
|  | // carries, such as marking events to be of a specific kind, or from a specific | ||||||
|  | // package. | ||||||
|  | type Tag struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewTag creates a new Key for tagging labels. | ||||||
|  | func NewTag(name, description string) *Tag { | ||||||
|  | 	return &Tag{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Tag) Name() string        { return k.name } | ||||||
|  | func (k *Tag) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {} | ||||||
|  | 
 | ||||||
|  | // New creates a new Label with this key. | ||||||
|  | func (k *Tag) New() label.Label { return label.OfValue(k, nil) } | ||||||
|  | 
 | ||||||
|  | // Int represents a key | ||||||
|  | type Int struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewInt creates a new Key for int values. | ||||||
|  | func NewInt(name, description string) *Int { | ||||||
|  | 	return &Int{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Int) Name() string        { return k.name } | ||||||
|  | func (k *Int) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Int) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Int) Get(lm label.Map) int { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Int) From(t label.Label) int { return int(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // Int8 represents a key | ||||||
|  | type Int8 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewInt8 creates a new Key for int8 values. | ||||||
|  | func NewInt8(name, description string) *Int8 { | ||||||
|  | 	return &Int8{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Int8) Name() string        { return k.name } | ||||||
|  | func (k *Int8) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Int8) Get(lm label.Map) int8 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // Int16 represents a key | ||||||
|  | type Int16 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewInt16 creates a new Key for int16 values. | ||||||
|  | func NewInt16(name, description string) *Int16 { | ||||||
|  | 	return &Int16{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Int16) Name() string        { return k.name } | ||||||
|  | func (k *Int16) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Int16) Get(lm label.Map) int16 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // Int32 represents a key | ||||||
|  | type Int32 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewInt32 creates a new Key for int32 values. | ||||||
|  | func NewInt32(name, description string) *Int32 { | ||||||
|  | 	return &Int32{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Int32) Name() string        { return k.name } | ||||||
|  | func (k *Int32) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Int32) Get(lm label.Map) int32 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // Int64 represents a key | ||||||
|  | type Int64 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewInt64 creates a new Key for int64 values. | ||||||
|  | func NewInt64(name, description string) *Int64 { | ||||||
|  | 	return &Int64{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Int64) Name() string        { return k.name } | ||||||
|  | func (k *Int64) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendInt(buf, k.From(l), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Int64) Get(lm label.Map) int64 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // UInt represents a key | ||||||
|  | type UInt struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewUInt creates a new Key for uint values. | ||||||
|  | func NewUInt(name, description string) *UInt { | ||||||
|  | 	return &UInt{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *UInt) Name() string        { return k.name } | ||||||
|  | func (k *UInt) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *UInt) Get(lm label.Map) uint { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // UInt8 represents a key | ||||||
|  | type UInt8 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewUInt8 creates a new Key for uint8 values. | ||||||
|  | func NewUInt8(name, description string) *UInt8 { | ||||||
|  | 	return &UInt8{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *UInt8) Name() string        { return k.name } | ||||||
|  | func (k *UInt8) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *UInt8) Get(lm label.Map) uint8 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // UInt16 represents a key | ||||||
|  | type UInt16 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewUInt16 creates a new Key for uint16 values. | ||||||
|  | func NewUInt16(name, description string) *UInt16 { | ||||||
|  | 	return &UInt16{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *UInt16) Name() string        { return k.name } | ||||||
|  | func (k *UInt16) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *UInt16) Get(lm label.Map) uint16 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // UInt32 represents a key | ||||||
|  | type UInt32 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewUInt32 creates a new Key for uint32 values. | ||||||
|  | func NewUInt32(name, description string) *UInt32 { | ||||||
|  | 	return &UInt32{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *UInt32) Name() string        { return k.name } | ||||||
|  | func (k *UInt32) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *UInt32) Get(lm label.Map) uint32 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) } | ||||||
|  | 
 | ||||||
|  | // UInt64 represents a key | ||||||
|  | type UInt64 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewUInt64 creates a new Key for uint64 values. | ||||||
|  | func NewUInt64(name, description string) *UInt64 { | ||||||
|  | 	return &UInt64{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *UInt64) Name() string        { return k.name } | ||||||
|  | func (k *UInt64) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendUint(buf, k.From(l), 10)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *UInt64) Get(lm label.Map) uint64 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() } | ||||||
|  | 
 | ||||||
|  | // Float32 represents a key | ||||||
|  | type Float32 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewFloat32 creates a new Key for float32 values. | ||||||
|  | func NewFloat32(name, description string) *Float32 { | ||||||
|  | 	return &Float32{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Float32) Name() string        { return k.name } | ||||||
|  | func (k *Float32) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Float32) Of(v float32) label.Label { | ||||||
|  | 	return label.Of64(k, uint64(math.Float32bits(v))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Float32) Get(lm label.Map) float32 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Float32) From(t label.Label) float32 { | ||||||
|  | 	return math.Float32frombits(uint32(t.Unpack64())) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Float64 represents a key | ||||||
|  | type Float64 struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewFloat64 creates a new Key for int64 values. | ||||||
|  | func NewFloat64(name, description string) *Float64 { | ||||||
|  | 	return &Float64{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Float64) Name() string        { return k.name } | ||||||
|  | func (k *Float64) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Float64) Of(v float64) label.Label { | ||||||
|  | 	return label.Of64(k, math.Float64bits(v)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Float64) Get(lm label.Map) float64 { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Float64) From(t label.Label) float64 { | ||||||
|  | 	return math.Float64frombits(t.Unpack64()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String represents a key | ||||||
|  | type String struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewString creates a new Key for int64 values. | ||||||
|  | func NewString(name, description string) *String { | ||||||
|  | 	return &String{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *String) Name() string        { return k.name } | ||||||
|  | func (k *String) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *String) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendQuote(buf, k.From(l))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *String) Of(v string) label.Label { return label.OfString(k, v) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *String) Get(lm label.Map) string { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *String) From(t label.Label) string { return t.UnpackString() } | ||||||
|  | 
 | ||||||
|  | // Boolean represents a key | ||||||
|  | type Boolean struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewBoolean creates a new Key for bool values. | ||||||
|  | func NewBoolean(name, description string) *Boolean { | ||||||
|  | 	return &Boolean{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Boolean) Name() string        { return k.name } | ||||||
|  | func (k *Boolean) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	w.Write(strconv.AppendBool(buf, k.From(l))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Boolean) Of(v bool) label.Label { | ||||||
|  | 	if v { | ||||||
|  | 		return label.Of64(k, 1) | ||||||
|  | 	} | ||||||
|  | 	return label.Of64(k, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Boolean) Get(lm label.Map) bool { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 } | ||||||
|  | 
 | ||||||
|  | // Error represents a key | ||||||
|  | type Error struct { | ||||||
|  | 	name        string | ||||||
|  | 	description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewError creates a new Key for int64 values. | ||||||
|  | func NewError(name, description string) *Error { | ||||||
|  | 	return &Error{name: name, description: description} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Error) Name() string        { return k.name } | ||||||
|  | func (k *Error) Description() string { return k.description } | ||||||
|  | 
 | ||||||
|  | func (k *Error) Format(w io.Writer, buf []byte, l label.Label) { | ||||||
|  | 	io.WriteString(w, k.From(l).Error()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Of creates a new Label with this key and the supplied value. | ||||||
|  | func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) } | ||||||
|  | 
 | ||||||
|  | // Get can be used to get a label for the key from a label.Map. | ||||||
|  | func (k *Error) Get(lm label.Map) error { | ||||||
|  | 	if t := lm.Find(k); t.Valid() { | ||||||
|  | 		return k.From(t) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From can be used to get a value from a Label. | ||||||
|  | func (k *Error) From(t label.Label) error { | ||||||
|  | 	err, _ := t.UnpackValue().(error) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								vendor/golang.org/x/tools/internal/event/keys/standard.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/golang.org/x/tools/internal/event/keys/standard.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | // Copyright 2020 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 keys | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// Msg is a key used to add message strings to label lists. | ||||||
|  | 	Msg = NewString("message", "a readable message") | ||||||
|  | 	// Label is a key used to indicate an event adds labels to the context. | ||||||
|  | 	Label = NewTag("label", "a label context marker") | ||||||
|  | 	// Start is used for things like traces that have a name. | ||||||
|  | 	Start = NewString("start", "span start") | ||||||
|  | 	// Metric is a key used to indicate an event records metrics. | ||||||
|  | 	End = NewTag("end", "a span end marker") | ||||||
|  | 	// Metric is a key used to indicate an event records metrics. | ||||||
|  | 	Detach = NewTag("detach", "a span detach marker") | ||||||
|  | 	// Err is a key used to add error values to label lists. | ||||||
|  | 	Err = NewError("error", "an error that occurred") | ||||||
|  | 	// Metric is a key used to indicate an event records metrics. | ||||||
|  | 	Metric = NewTag("metric", "a metric event marker") | ||||||
|  | ) | ||||||
							
								
								
									
										213
									
								
								vendor/golang.org/x/tools/internal/event/label/label.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								vendor/golang.org/x/tools/internal/event/label/label.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,213 @@ | |||||||
|  | // Copyright 2019 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 label | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"reflect" | ||||||
|  | 	"unsafe" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Key is used as the identity of a Label. | ||||||
|  | // Keys are intended to be compared by pointer only, the name should be unique | ||||||
|  | // for communicating with external systems, but it is not required or enforced. | ||||||
|  | type Key interface { | ||||||
|  | 	// Name returns the key name. | ||||||
|  | 	Name() string | ||||||
|  | 	// Description returns a string that can be used to describe the value. | ||||||
|  | 	Description() string | ||||||
|  | 
 | ||||||
|  | 	// Format is used in formatting to append the value of the label to the | ||||||
|  | 	// supplied buffer. | ||||||
|  | 	// The formatter may use the supplied buf as a scratch area to avoid | ||||||
|  | 	// allocations. | ||||||
|  | 	Format(w io.Writer, buf []byte, l Label) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Label holds a key and value pair. | ||||||
|  | // It is normally used when passing around lists of labels. | ||||||
|  | type Label struct { | ||||||
|  | 	key     Key | ||||||
|  | 	packed  uint64 | ||||||
|  | 	untyped interface{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Map is the interface to a collection of Labels indexed by key. | ||||||
|  | type Map interface { | ||||||
|  | 	// Find returns the label that matches the supplied key. | ||||||
|  | 	Find(key Key) Label | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // List is the interface to something that provides an iterable | ||||||
|  | // list of labels. | ||||||
|  | // Iteration should start from 0 and continue until Valid returns false. | ||||||
|  | type List interface { | ||||||
|  | 	// Valid returns true if the index is within range for the list. | ||||||
|  | 	// It does not imply the label at that index will itself be valid. | ||||||
|  | 	Valid(index int) bool | ||||||
|  | 	// Label returns the label at the given index. | ||||||
|  | 	Label(index int) Label | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // list implements LabelList for a list of Labels. | ||||||
|  | type list struct { | ||||||
|  | 	labels []Label | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // filter wraps a LabelList filtering out specific labels. | ||||||
|  | type filter struct { | ||||||
|  | 	keys       []Key | ||||||
|  | 	underlying List | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // listMap implements LabelMap for a simple list of labels. | ||||||
|  | type listMap struct { | ||||||
|  | 	labels []Label | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // mapChain implements LabelMap for a list of underlying LabelMap. | ||||||
|  | type mapChain struct { | ||||||
|  | 	maps []Map | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OfValue creates a new label from the key and value. | ||||||
|  | // This method is for implementing new key types, label creation should | ||||||
|  | // normally be done with the Of method of the key. | ||||||
|  | func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} } | ||||||
|  | 
 | ||||||
|  | // UnpackValue assumes the label was built using LabelOfValue and returns the value | ||||||
|  | // that was passed to that constructor. | ||||||
|  | // This method is for implementing new key types, for type safety normal | ||||||
|  | // access should be done with the From method of the key. | ||||||
|  | func (t Label) UnpackValue() interface{} { return t.untyped } | ||||||
|  | 
 | ||||||
|  | // Of64 creates a new label from a key and a uint64. This is often | ||||||
|  | // used for non uint64 values that can be packed into a uint64. | ||||||
|  | // This method is for implementing new key types, label creation should | ||||||
|  | // normally be done with the Of method of the key. | ||||||
|  | func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} } | ||||||
|  | 
 | ||||||
|  | // Unpack64 assumes the label was built using LabelOf64 and returns the value that | ||||||
|  | // was passed to that constructor. | ||||||
|  | // This method is for implementing new key types, for type safety normal | ||||||
|  | // access should be done with the From method of the key. | ||||||
|  | func (t Label) Unpack64() uint64 { return t.packed } | ||||||
|  | 
 | ||||||
|  | // OfString creates a new label from a key and a string. | ||||||
|  | // This method is for implementing new key types, label creation should | ||||||
|  | // normally be done with the Of method of the key. | ||||||
|  | func OfString(k Key, v string) Label { | ||||||
|  | 	hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) | ||||||
|  | 	return Label{ | ||||||
|  | 		key:     k, | ||||||
|  | 		packed:  uint64(hdr.Len), | ||||||
|  | 		untyped: unsafe.Pointer(hdr.Data), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnpackString assumes the label was built using LabelOfString and returns the | ||||||
|  | // value that was passed to that constructor. | ||||||
|  | // This method is for implementing new key types, for type safety normal | ||||||
|  | // access should be done with the From method of the key. | ||||||
|  | func (t Label) UnpackString() string { | ||||||
|  | 	var v string | ||||||
|  | 	hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) | ||||||
|  | 	hdr.Data = uintptr(t.untyped.(unsafe.Pointer)) | ||||||
|  | 	hdr.Len = int(t.packed) | ||||||
|  | 	return *(*string)(unsafe.Pointer(hdr)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Valid returns true if the Label is a valid one (it has a key). | ||||||
|  | func (t Label) Valid() bool { return t.key != nil } | ||||||
|  | 
 | ||||||
|  | // Key returns the key of this Label. | ||||||
|  | func (t Label) Key() Key { return t.key } | ||||||
|  | 
 | ||||||
|  | // Format is used for debug printing of labels. | ||||||
|  | func (t Label) Format(f fmt.State, r rune) { | ||||||
|  | 	if !t.Valid() { | ||||||
|  | 		io.WriteString(f, `nil`) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	io.WriteString(f, t.Key().Name()) | ||||||
|  | 	io.WriteString(f, "=") | ||||||
|  | 	var buf [128]byte | ||||||
|  | 	t.Key().Format(f, buf[:0], t) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *list) Valid(index int) bool { | ||||||
|  | 	return index >= 0 && index < len(l.labels) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *list) Label(index int) Label { | ||||||
|  | 	return l.labels[index] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *filter) Valid(index int) bool { | ||||||
|  | 	return f.underlying.Valid(index) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *filter) Label(index int) Label { | ||||||
|  | 	l := f.underlying.Label(index) | ||||||
|  | 	for _, f := range f.keys { | ||||||
|  | 		if l.Key() == f { | ||||||
|  | 			return Label{} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (lm listMap) Find(key Key) Label { | ||||||
|  | 	for _, l := range lm.labels { | ||||||
|  | 		if l.Key() == key { | ||||||
|  | 			return l | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return Label{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c mapChain) Find(key Key) Label { | ||||||
|  | 	for _, src := range c.maps { | ||||||
|  | 		l := src.Find(key) | ||||||
|  | 		if l.Valid() { | ||||||
|  | 			return l | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return Label{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var emptyList = &list{} | ||||||
|  | 
 | ||||||
|  | func NewList(labels ...Label) List { | ||||||
|  | 	if len(labels) == 0 { | ||||||
|  | 		return emptyList | ||||||
|  | 	} | ||||||
|  | 	return &list{labels: labels} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Filter(l List, keys ...Key) List { | ||||||
|  | 	if len(keys) == 0 { | ||||||
|  | 		return l | ||||||
|  | 	} | ||||||
|  | 	return &filter{keys: keys, underlying: l} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewMap(labels ...Label) Map { | ||||||
|  | 	return listMap{labels: labels} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func MergeMaps(srcs ...Map) Map { | ||||||
|  | 	var nonNil []Map | ||||||
|  | 	for _, src := range srcs { | ||||||
|  | 		if src != nil { | ||||||
|  | 			nonNil = append(nonNil, src) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(nonNil) == 1 { | ||||||
|  | 		return nonNil[0] | ||||||
|  | 	} | ||||||
|  | 	return mapChain{maps: nonNil} | ||||||
|  | } | ||||||
							
								
								
									
										230
									
								
								vendor/golang.org/x/tools/internal/gocommand/invoke.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								vendor/golang.org/x/tools/internal/gocommand/invoke.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,230 @@ | |||||||
|  | // Copyright 2020 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 gocommand is a helper for calling the go command. | ||||||
|  | package gocommand | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/internal/event" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // An Runner will run go command invocations and serialize | ||||||
|  | // them if it sees a concurrency error. | ||||||
|  | type Runner struct { | ||||||
|  | 	// once guards the runner initialization. | ||||||
|  | 	once sync.Once | ||||||
|  | 
 | ||||||
|  | 	// inFlight tracks available workers. | ||||||
|  | 	inFlight chan struct{} | ||||||
|  | 
 | ||||||
|  | 	// serialized guards the ability to run a go command serially, | ||||||
|  | 	// to avoid deadlocks when claiming workers. | ||||||
|  | 	serialized chan struct{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const maxInFlight = 10 | ||||||
|  | 
 | ||||||
|  | func (runner *Runner) initialize() { | ||||||
|  | 	runner.once.Do(func() { | ||||||
|  | 		runner.inFlight = make(chan struct{}, maxInFlight) | ||||||
|  | 		runner.serialized = make(chan struct{}, 1) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 1.13: go: updates to go.mod needed, but contents have changed | ||||||
|  | // 1.14: go: updating go.mod: existing contents have changed since last read | ||||||
|  | var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) | ||||||
|  | 
 | ||||||
|  | // Run is a convenience wrapper around RunRaw. | ||||||
|  | // It returns only stdout and a "friendly" error. | ||||||
|  | func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { | ||||||
|  | 	stdout, _, friendly, _ := runner.RunRaw(ctx, inv) | ||||||
|  | 	return stdout, friendly | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RunPiped runs the invocation serially, always waiting for any concurrent | ||||||
|  | // invocations to complete first. | ||||||
|  | func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error { | ||||||
|  | 	_, err := runner.runPiped(ctx, inv, stdout, stderr) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RunRaw runs the invocation, serializing requests only if they fight over | ||||||
|  | // go.mod changes. | ||||||
|  | func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { | ||||||
|  | 	// Make sure the runner is always initialized. | ||||||
|  | 	runner.initialize() | ||||||
|  | 
 | ||||||
|  | 	// First, try to run the go command concurrently. | ||||||
|  | 	stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv) | ||||||
|  | 
 | ||||||
|  | 	// If we encounter a load concurrency error, we need to retry serially. | ||||||
|  | 	if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) { | ||||||
|  | 		return stdout, stderr, friendlyErr, err | ||||||
|  | 	} | ||||||
|  | 	event.Error(ctx, "Load concurrency error, will retry serially", err) | ||||||
|  | 
 | ||||||
|  | 	// Run serially by calling runPiped. | ||||||
|  | 	stdout.Reset() | ||||||
|  | 	stderr.Reset() | ||||||
|  | 	friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr) | ||||||
|  | 	return stdout, stderr, friendlyErr, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { | ||||||
|  | 	// Wait for 1 worker to become available. | ||||||
|  | 	select { | ||||||
|  | 	case <-ctx.Done(): | ||||||
|  | 		return nil, nil, nil, ctx.Err() | ||||||
|  | 	case runner.inFlight <- struct{}{}: | ||||||
|  | 		defer func() { <-runner.inFlight }() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} | ||||||
|  | 	friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr) | ||||||
|  | 	return stdout, stderr, friendlyErr, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) { | ||||||
|  | 	// Make sure the runner is always initialized. | ||||||
|  | 	runner.initialize() | ||||||
|  | 
 | ||||||
|  | 	// Acquire the serialization lock. This avoids deadlocks between two | ||||||
|  | 	// runPiped commands. | ||||||
|  | 	select { | ||||||
|  | 	case <-ctx.Done(): | ||||||
|  | 		return nil, ctx.Err() | ||||||
|  | 	case runner.serialized <- struct{}{}: | ||||||
|  | 		defer func() { <-runner.serialized }() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Wait for all in-progress go commands to return before proceeding, | ||||||
|  | 	// to avoid load concurrency errors. | ||||||
|  | 	for i := 0; i < maxInFlight; i++ { | ||||||
|  | 		select { | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			return nil, ctx.Err() | ||||||
|  | 		case runner.inFlight <- struct{}{}: | ||||||
|  | 			// Make sure we always "return" any workers we took. | ||||||
|  | 			defer func() { <-runner.inFlight }() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return inv.runWithFriendlyError(ctx, stdout, stderr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // An Invocation represents a call to the go command. | ||||||
|  | type Invocation struct { | ||||||
|  | 	Verb       string | ||||||
|  | 	Args       []string | ||||||
|  | 	BuildFlags []string | ||||||
|  | 	Env        []string | ||||||
|  | 	WorkingDir string | ||||||
|  | 	Logf       func(format string, args ...interface{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) { | ||||||
|  | 	rawError = i.run(ctx, stdout, stderr) | ||||||
|  | 	if rawError != nil { | ||||||
|  | 		friendlyError = rawError | ||||||
|  | 		// Check for 'go' executable not being found. | ||||||
|  | 		if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { | ||||||
|  | 			friendlyError = fmt.Errorf("go command required, not found: %v", ee) | ||||||
|  | 		} | ||||||
|  | 		if ctx.Err() != nil { | ||||||
|  | 			friendlyError = ctx.Err() | ||||||
|  | 		} | ||||||
|  | 		friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { | ||||||
|  | 	log := i.Logf | ||||||
|  | 	if log == nil { | ||||||
|  | 		log = func(string, ...interface{}) {} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	goArgs := []string{i.Verb} | ||||||
|  | 	switch i.Verb { | ||||||
|  | 	case "mod": | ||||||
|  | 		// mod needs the sub-verb before build flags. | ||||||
|  | 		goArgs = append(goArgs, i.Args[0]) | ||||||
|  | 		goArgs = append(goArgs, i.BuildFlags...) | ||||||
|  | 		goArgs = append(goArgs, i.Args[1:]...) | ||||||
|  | 	case "env": | ||||||
|  | 		// env doesn't take build flags. | ||||||
|  | 		goArgs = append(goArgs, i.Args...) | ||||||
|  | 	default: | ||||||
|  | 		goArgs = append(goArgs, i.BuildFlags...) | ||||||
|  | 		goArgs = append(goArgs, i.Args...) | ||||||
|  | 	} | ||||||
|  | 	cmd := exec.Command("go", goArgs...) | ||||||
|  | 	cmd.Stdout = stdout | ||||||
|  | 	cmd.Stderr = stderr | ||||||
|  | 	// On darwin the cwd gets resolved to the real path, which breaks anything that | ||||||
|  | 	// expects the working directory to keep the original path, including the | ||||||
|  | 	// go command when dealing with modules. | ||||||
|  | 	// The Go stdlib has a special feature where if the cwd and the PWD are the | ||||||
|  | 	// same node then it trusts the PWD, so by setting it in the env for the child | ||||||
|  | 	// process we fix up all the paths returned by the go command. | ||||||
|  | 	cmd.Env = append(os.Environ(), i.Env...) | ||||||
|  | 	if i.WorkingDir != "" { | ||||||
|  | 		cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) | ||||||
|  | 		cmd.Dir = i.WorkingDir | ||||||
|  | 	} | ||||||
|  | 	defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) | ||||||
|  | 
 | ||||||
|  | 	return runCmdContext(ctx, cmd) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // runCmdContext is like exec.CommandContext except it sends os.Interrupt | ||||||
|  | // before os.Kill. | ||||||
|  | func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { | ||||||
|  | 	if err := cmd.Start(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	resChan := make(chan error, 1) | ||||||
|  | 	go func() { | ||||||
|  | 		resChan <- cmd.Wait() | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	select { | ||||||
|  | 	case err := <-resChan: | ||||||
|  | 		return err | ||||||
|  | 	case <-ctx.Done(): | ||||||
|  | 	} | ||||||
|  | 	// Cancelled. Interrupt and see if it ends voluntarily. | ||||||
|  | 	cmd.Process.Signal(os.Interrupt) | ||||||
|  | 	select { | ||||||
|  | 	case err := <-resChan: | ||||||
|  | 		return err | ||||||
|  | 	case <-time.After(time.Second): | ||||||
|  | 	} | ||||||
|  | 	// Didn't shut down in response to interrupt. Kill it hard. | ||||||
|  | 	cmd.Process.Kill() | ||||||
|  | 	return <-resChan | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func cmdDebugStr(cmd *exec.Cmd) string { | ||||||
|  | 	env := make(map[string]string) | ||||||
|  | 	for _, kv := range cmd.Env { | ||||||
|  | 		split := strings.Split(kv, "=") | ||||||
|  | 		k, v := split[0], split[1] | ||||||
|  | 		env[k] = v | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args) | ||||||
|  | } | ||||||
							
								
								
									
										102
									
								
								vendor/golang.org/x/tools/internal/gocommand/vendor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								vendor/golang.org/x/tools/internal/gocommand/vendor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | |||||||
|  | // Copyright 2020 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 gocommand | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/mod/semver" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ModuleJSON holds information about a module. | ||||||
|  | type ModuleJSON struct { | ||||||
|  | 	Path      string      // module path | ||||||
|  | 	Replace   *ModuleJSON // replaced by this module | ||||||
|  | 	Main      bool        // is this the main module? | ||||||
|  | 	Indirect  bool        // is this module only an indirect dependency of main module? | ||||||
|  | 	Dir       string      // directory holding files for this module, if any | ||||||
|  | 	GoMod     string      // path to go.mod file for this module, if any | ||||||
|  | 	GoVersion string      // go version used in module | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) | ||||||
|  | 
 | ||||||
|  | // VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands | ||||||
|  | // with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields, | ||||||
|  | // of which only Verb and Args are modified to run the appropriate Go command. | ||||||
|  | // Inspired by setDefaultBuildMod in modload/init.go | ||||||
|  | func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) { | ||||||
|  | 	mainMod, go114, err := getMainModuleAnd114(ctx, inv, r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We check the GOFLAGS to see if there is anything overridden or not. | ||||||
|  | 	inv.Verb = "env" | ||||||
|  | 	inv.Args = []string{"GOFLAGS"} | ||||||
|  | 	stdout, err := r.Run(ctx, inv) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  | 	goflags := string(bytes.TrimSpace(stdout.Bytes())) | ||||||
|  | 	matches := modFlagRegexp.FindStringSubmatch(goflags) | ||||||
|  | 	var modFlag string | ||||||
|  | 	if len(matches) != 0 { | ||||||
|  | 		modFlag = matches[1] | ||||||
|  | 	} | ||||||
|  | 	if modFlag != "" { | ||||||
|  | 		// Don't override an explicit '-mod=' argument. | ||||||
|  | 		return mainMod, modFlag == "vendor", nil | ||||||
|  | 	} | ||||||
|  | 	if mainMod == nil || !go114 { | ||||||
|  | 		return mainMod, false, nil | ||||||
|  | 	} | ||||||
|  | 	// Check 1.14's automatic vendor mode. | ||||||
|  | 	if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() { | ||||||
|  | 		if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 { | ||||||
|  | 			// The Go version is at least 1.14, and a vendor directory exists. | ||||||
|  | 			// Set -mod=vendor by default. | ||||||
|  | 			return mainMod, true, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return mainMod, false, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // getMainModuleAnd114 gets the main module's information and whether the | ||||||
|  | // go command in use is 1.14+. This is the information needed to figure out | ||||||
|  | // if vendoring should be enabled. | ||||||
|  | func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) { | ||||||
|  | 	const format = `{{.Path}} | ||||||
|  | {{.Dir}} | ||||||
|  | {{.GoMod}} | ||||||
|  | {{.GoVersion}} | ||||||
|  | {{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}} | ||||||
|  | ` | ||||||
|  | 	inv.Verb = "list" | ||||||
|  | 	inv.Args = []string{"-m", "-f", format} | ||||||
|  | 	stdout, err := r.Run(ctx, inv) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	lines := strings.Split(stdout.String(), "\n") | ||||||
|  | 	if len(lines) < 5 { | ||||||
|  | 		return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String()) | ||||||
|  | 	} | ||||||
|  | 	mod := &ModuleJSON{ | ||||||
|  | 		Path:      lines[0], | ||||||
|  | 		Dir:       lines[1], | ||||||
|  | 		GoMod:     lines[2], | ||||||
|  | 		GoVersion: lines[3], | ||||||
|  | 		Main:      true, | ||||||
|  | 	} | ||||||
|  | 	return mod, lines[4] == "go1.14", nil | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								vendor/golang.org/x/tools/internal/packagesinternal/packages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/golang.org/x/tools/internal/packagesinternal/packages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | // Package packagesinternal exposes internal-only fields from go/packages. | ||||||
|  | package packagesinternal | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"golang.org/x/tools/internal/gocommand" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var GetForTest = func(p interface{}) string { return "" } | ||||||
|  | 
 | ||||||
|  | var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil } | ||||||
|  | 
 | ||||||
|  | var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {} | ||||||
|  | 
 | ||||||
|  | var TypecheckCgo int | ||||||
							
								
								
									
										28
									
								
								vendor/golang.org/x/tools/internal/typesinternal/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								vendor/golang.org/x/tools/internal/typesinternal/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | |||||||
|  | // Copyright 2020 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 typesinternal | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go/types" | ||||||
|  | 	"reflect" | ||||||
|  | 	"unsafe" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func SetUsesCgo(conf *types.Config) bool { | ||||||
|  | 	v := reflect.ValueOf(conf).Elem() | ||||||
|  | 
 | ||||||
|  | 	f := v.FieldByName("go115UsesCgo") | ||||||
|  | 	if !f.IsValid() { | ||||||
|  | 		f = v.FieldByName("UsesCgo") | ||||||
|  | 		if !f.IsValid() { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	addr := unsafe.Pointer(f.UnsafeAddr()) | ||||||
|  | 	*(*bool)(addr) = true | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								vendor/golang.org/x/xerrors/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/golang.org/x/xerrors/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | Copyright (c) 2019 The Go 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 Google Inc. nor the names of its | ||||||
|  | contributors may be used to endorse or promote products derived from | ||||||
|  | this software without specific prior written permission. | ||||||
|  | 
 | ||||||
|  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||||
|  | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||||
|  | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||||
|  | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||||
|  | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||
|  | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||||
|  | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
							
								
								
									
										22
									
								
								vendor/golang.org/x/xerrors/PATENTS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/golang.org/x/xerrors/PATENTS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | Additional IP Rights Grant (Patents) | ||||||
|  | 
 | ||||||
|  | "This implementation" means the copyrightable works distributed by | ||||||
|  | Google as part of the Go project. | ||||||
|  | 
 | ||||||
|  | Google hereby grants to You a perpetual, worldwide, non-exclusive, | ||||||
|  | no-charge, royalty-free, irrevocable (except as stated in this section) | ||||||
|  | patent license to make, have made, use, offer to sell, sell, import, | ||||||
|  | transfer and otherwise run, modify and propagate the contents of this | ||||||
|  | implementation of Go, where such license applies only to those patent | ||||||
|  | claims, both currently owned or controlled by Google and acquired in | ||||||
|  | the future, licensable by Google that are necessarily infringed by this | ||||||
|  | implementation of Go.  This grant does not include claims that would be | ||||||
|  | infringed only as a consequence of further modification of this | ||||||
|  | implementation.  If you or your agent or exclusive licensee institute or | ||||||
|  | order or agree to the institution of patent litigation against any | ||||||
|  | entity (including a cross-claim or counterclaim in a lawsuit) alleging | ||||||
|  | that this implementation of Go or any code incorporated within this | ||||||
|  | implementation of Go constitutes direct or contributory patent | ||||||
|  | infringement, or inducement of patent infringement, then any patent | ||||||
|  | rights granted to you under this License for this implementation of Go | ||||||
|  | shall terminate as of the date such litigation is filed. | ||||||
							
								
								
									
										2
									
								
								vendor/golang.org/x/xerrors/README
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								vendor/golang.org/x/xerrors/README
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | This repository holds the transition packages for the new Go 1.13 error values. | ||||||
|  | See golang.org/design/29934-error-values. | ||||||
							
								
								
									
										193
									
								
								vendor/golang.org/x/xerrors/adaptor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								vendor/golang.org/x/xerrors/adaptor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | package xerrors | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // FormatError calls the FormatError method of f with an errors.Printer | ||||||
|  | // configured according to s and verb, and writes the result to s. | ||||||
|  | func FormatError(f Formatter, s fmt.State, verb rune) { | ||||||
|  | 	// Assuming this function is only called from the Format method, and given | ||||||
|  | 	// that FormatError takes precedence over Format, it cannot be called from | ||||||
|  | 	// any package that supports errors.Formatter. It is therefore safe to | ||||||
|  | 	// disregard that State may be a specific printer implementation and use one | ||||||
|  | 	// of our choice instead. | ||||||
|  | 
 | ||||||
|  | 	// limitations: does not support printing error as Go struct. | ||||||
|  | 
 | ||||||
|  | 	var ( | ||||||
|  | 		sep    = " " // separator before next error | ||||||
|  | 		p      = &state{State: s} | ||||||
|  | 		direct = true | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	var err error = f | ||||||
|  | 
 | ||||||
|  | 	switch verb { | ||||||
|  | 	// Note that this switch must match the preference order | ||||||
|  | 	// for ordinary string printing (%#v before %+v, and so on). | ||||||
|  | 
 | ||||||
|  | 	case 'v': | ||||||
|  | 		if s.Flag('#') { | ||||||
|  | 			if stringer, ok := err.(fmt.GoStringer); ok { | ||||||
|  | 				io.WriteString(&p.buf, stringer.GoString()) | ||||||
|  | 				goto exit | ||||||
|  | 			} | ||||||
|  | 			// proceed as if it were %v | ||||||
|  | 		} else if s.Flag('+') { | ||||||
|  | 			p.printDetail = true | ||||||
|  | 			sep = "\n  - " | ||||||
|  | 		} | ||||||
|  | 	case 's': | ||||||
|  | 	case 'q', 'x', 'X': | ||||||
|  | 		// Use an intermediate buffer in the rare cases that precision, | ||||||
|  | 		// truncation, or one of the alternative verbs (q, x, and X) are | ||||||
|  | 		// specified. | ||||||
|  | 		direct = false | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		p.buf.WriteString("%!") | ||||||
|  | 		p.buf.WriteRune(verb) | ||||||
|  | 		p.buf.WriteByte('(') | ||||||
|  | 		switch { | ||||||
|  | 		case err != nil: | ||||||
|  | 			p.buf.WriteString(reflect.TypeOf(f).String()) | ||||||
|  | 		default: | ||||||
|  | 			p.buf.WriteString("<nil>") | ||||||
|  | 		} | ||||||
|  | 		p.buf.WriteByte(')') | ||||||
|  | 		io.Copy(s, &p.buf) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | loop: | ||||||
|  | 	for { | ||||||
|  | 		switch v := err.(type) { | ||||||
|  | 		case Formatter: | ||||||
|  | 			err = v.FormatError((*printer)(p)) | ||||||
|  | 		case fmt.Formatter: | ||||||
|  | 			v.Format(p, 'v') | ||||||
|  | 			break loop | ||||||
|  | 		default: | ||||||
|  | 			io.WriteString(&p.buf, v.Error()) | ||||||
|  | 			break loop | ||||||
|  | 		} | ||||||
|  | 		if err == nil { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if p.needColon || !p.printDetail { | ||||||
|  | 			p.buf.WriteByte(':') | ||||||
|  | 			p.needColon = false | ||||||
|  | 		} | ||||||
|  | 		p.buf.WriteString(sep) | ||||||
|  | 		p.inDetail = false | ||||||
|  | 		p.needNewline = false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | exit: | ||||||
|  | 	width, okW := s.Width() | ||||||
|  | 	prec, okP := s.Precision() | ||||||
|  | 
 | ||||||
|  | 	if !direct || (okW && width > 0) || okP { | ||||||
|  | 		// Construct format string from State s. | ||||||
|  | 		format := []byte{'%'} | ||||||
|  | 		if s.Flag('-') { | ||||||
|  | 			format = append(format, '-') | ||||||
|  | 		} | ||||||
|  | 		if s.Flag('+') { | ||||||
|  | 			format = append(format, '+') | ||||||
|  | 		} | ||||||
|  | 		if s.Flag(' ') { | ||||||
|  | 			format = append(format, ' ') | ||||||
|  | 		} | ||||||
|  | 		if okW { | ||||||
|  | 			format = strconv.AppendInt(format, int64(width), 10) | ||||||
|  | 		} | ||||||
|  | 		if okP { | ||||||
|  | 			format = append(format, '.') | ||||||
|  | 			format = strconv.AppendInt(format, int64(prec), 10) | ||||||
|  | 		} | ||||||
|  | 		format = append(format, string(verb)...) | ||||||
|  | 		fmt.Fprintf(s, string(format), p.buf.String()) | ||||||
|  | 	} else { | ||||||
|  | 		io.Copy(s, &p.buf) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var detailSep = []byte("\n    ") | ||||||
|  | 
 | ||||||
|  | // state tracks error printing state. It implements fmt.State. | ||||||
|  | type state struct { | ||||||
|  | 	fmt.State | ||||||
|  | 	buf bytes.Buffer | ||||||
|  | 
 | ||||||
|  | 	printDetail bool | ||||||
|  | 	inDetail    bool | ||||||
|  | 	needColon   bool | ||||||
|  | 	needNewline bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *state) Write(b []byte) (n int, err error) { | ||||||
|  | 	if s.printDetail { | ||||||
|  | 		if len(b) == 0 { | ||||||
|  | 			return 0, nil | ||||||
|  | 		} | ||||||
|  | 		if s.inDetail && s.needColon { | ||||||
|  | 			s.needNewline = true | ||||||
|  | 			if b[0] == '\n' { | ||||||
|  | 				b = b[1:] | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		k := 0 | ||||||
|  | 		for i, c := range b { | ||||||
|  | 			if s.needNewline { | ||||||
|  | 				if s.inDetail && s.needColon { | ||||||
|  | 					s.buf.WriteByte(':') | ||||||
|  | 					s.needColon = false | ||||||
|  | 				} | ||||||
|  | 				s.buf.Write(detailSep) | ||||||
|  | 				s.needNewline = false | ||||||
|  | 			} | ||||||
|  | 			if c == '\n' { | ||||||
|  | 				s.buf.Write(b[k:i]) | ||||||
|  | 				k = i + 1 | ||||||
|  | 				s.needNewline = true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		s.buf.Write(b[k:]) | ||||||
|  | 		if !s.inDetail { | ||||||
|  | 			s.needColon = true | ||||||
|  | 		} | ||||||
|  | 	} else if !s.inDetail { | ||||||
|  | 		s.buf.Write(b) | ||||||
|  | 	} | ||||||
|  | 	return len(b), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // printer wraps a state to implement an xerrors.Printer. | ||||||
|  | type printer state | ||||||
|  | 
 | ||||||
|  | func (s *printer) Print(args ...interface{}) { | ||||||
|  | 	if !s.inDetail || s.printDetail { | ||||||
|  | 		fmt.Fprint((*state)(s), args...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *printer) Printf(format string, args ...interface{}) { | ||||||
|  | 	if !s.inDetail || s.printDetail { | ||||||
|  | 		fmt.Fprintf((*state)(s), format, args...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *printer) Detail() bool { | ||||||
|  | 	s.inDetail = true | ||||||
|  | 	return s.printDetail | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								vendor/golang.org/x/xerrors/codereview.cfg
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/golang.org/x/xerrors/codereview.cfg
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | issuerepo: golang/go | ||||||
							
								
								
									
										22
									
								
								vendor/golang.org/x/xerrors/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/golang.org/x/xerrors/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | // Copyright 2019 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 xerrors implements functions to manipulate errors. | ||||||
|  | // | ||||||
|  | // This package is based on the Go 2 proposal for error values: | ||||||
|  | //   https://golang.org/design/29934-error-values | ||||||
|  | // | ||||||
|  | // These functions were incorporated into the standard library's errors package | ||||||
|  | // in Go 1.13: | ||||||
|  | // - Is | ||||||
|  | // - As | ||||||
|  | // - Unwrap | ||||||
|  | // | ||||||
|  | // Also, Errorf's %w verb was incorporated into fmt.Errorf. | ||||||
|  | // | ||||||
|  | // Use this package to get equivalent behavior in all supported Go versions. | ||||||
|  | // | ||||||
|  | // No other features of this package were included in Go 1.13, and at present | ||||||
|  | // there are no plans to include any of them. | ||||||
|  | package xerrors // import "golang.org/x/xerrors" | ||||||
							
								
								
									
										33
									
								
								vendor/golang.org/x/xerrors/errors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/golang.org/x/xerrors/errors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | // 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 xerrors | ||||||
|  | 
 | ||||||
|  | import "fmt" | ||||||
|  | 
 | ||||||
|  | // errorString is a trivial implementation of error. | ||||||
|  | type errorString struct { | ||||||
|  | 	s     string | ||||||
|  | 	frame Frame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New returns an error that formats as the given text. | ||||||
|  | // | ||||||
|  | // The returned error contains a Frame set to the caller's location and | ||||||
|  | // implements Formatter to show this information when printed with details. | ||||||
|  | func New(text string) error { | ||||||
|  | 	return &errorString{text, Caller(1)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *errorString) Error() string { | ||||||
|  | 	return e.s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) } | ||||||
|  | 
 | ||||||
|  | func (e *errorString) FormatError(p Printer) (next error) { | ||||||
|  | 	p.Print(e.s) | ||||||
|  | 	e.frame.Format(p) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										187
									
								
								vendor/golang.org/x/xerrors/fmt.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								vendor/golang.org/x/xerrors/fmt.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | package xerrors | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/xerrors/internal" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const percentBangString = "%!" | ||||||
|  | 
 | ||||||
|  | // Errorf formats according to a format specifier and returns the string as a | ||||||
|  | // value that satisfies error. | ||||||
|  | // | ||||||
|  | // The returned error includes the file and line number of the caller when | ||||||
|  | // formatted with additional detail enabled. If the last argument is an error | ||||||
|  | // the returned error's Format method will return it if the format string ends | ||||||
|  | // with ": %s", ": %v", or ": %w". If the last argument is an error and the | ||||||
|  | // format string ends with ": %w", the returned error implements an Unwrap | ||||||
|  | // method returning it. | ||||||
|  | // | ||||||
|  | // If the format specifier includes a %w verb with an error operand in a | ||||||
|  | // position other than at the end, the returned error will still implement an | ||||||
|  | // Unwrap method returning the operand, but the error's Format method will not | ||||||
|  | // return the wrapped error. | ||||||
|  | // | ||||||
|  | // It is invalid to include more than one %w verb or to supply it with an | ||||||
|  | // operand that does not implement the error interface. The %w verb is otherwise | ||||||
|  | // a synonym for %v. | ||||||
|  | func Errorf(format string, a ...interface{}) error { | ||||||
|  | 	format = formatPlusW(format) | ||||||
|  | 	// Support a ": %[wsv]" suffix, which works well with xerrors.Formatter. | ||||||
|  | 	wrap := strings.HasSuffix(format, ": %w") | ||||||
|  | 	idx, format2, ok := parsePercentW(format) | ||||||
|  | 	percentWElsewhere := !wrap && idx >= 0 | ||||||
|  | 	if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) { | ||||||
|  | 		err := errorAt(a, len(a)-1) | ||||||
|  | 		if err == nil { | ||||||
|  | 			return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} | ||||||
|  | 		} | ||||||
|  | 		// TODO: this is not entirely correct. The error value could be | ||||||
|  | 		// printed elsewhere in format if it mixes numbered with unnumbered | ||||||
|  | 		// substitutions. With relatively small changes to doPrintf we can | ||||||
|  | 		// have it optionally ignore extra arguments and pass the argument | ||||||
|  | 		// list in its entirety. | ||||||
|  | 		msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) | ||||||
|  | 		frame := Frame{} | ||||||
|  | 		if internal.EnableTrace { | ||||||
|  | 			frame = Caller(1) | ||||||
|  | 		} | ||||||
|  | 		if wrap { | ||||||
|  | 			return &wrapError{msg, err, frame} | ||||||
|  | 		} | ||||||
|  | 		return &noWrapError{msg, err, frame} | ||||||
|  | 	} | ||||||
|  | 	// Support %w anywhere. | ||||||
|  | 	// TODO: don't repeat the wrapped error's message when %w occurs in the middle. | ||||||
|  | 	msg := fmt.Sprintf(format2, a...) | ||||||
|  | 	if idx < 0 { | ||||||
|  | 		return &noWrapError{msg, nil, Caller(1)} | ||||||
|  | 	} | ||||||
|  | 	err := errorAt(a, idx) | ||||||
|  | 	if !ok || err == nil { | ||||||
|  | 		// Too many %ws or argument of %w is not an error. Approximate the Go | ||||||
|  | 		// 1.13 fmt.Errorf message. | ||||||
|  | 		return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)} | ||||||
|  | 	} | ||||||
|  | 	frame := Frame{} | ||||||
|  | 	if internal.EnableTrace { | ||||||
|  | 		frame = Caller(1) | ||||||
|  | 	} | ||||||
|  | 	return &wrapError{msg, err, frame} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func errorAt(args []interface{}, i int) error { | ||||||
|  | 	if i < 0 || i >= len(args) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	err, ok := args[i].(error) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // formatPlusW is used to avoid the vet check that will barf at %w. | ||||||
|  | func formatPlusW(s string) string { | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Return the index of the only %w in format, or -1 if none. | ||||||
|  | // Also return a rewritten format string with %w replaced by %v, and | ||||||
|  | // false if there is more than one %w. | ||||||
|  | // TODO: handle "%[N]w". | ||||||
|  | func parsePercentW(format string) (idx int, newFormat string, ok bool) { | ||||||
|  | 	// Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go. | ||||||
|  | 	idx = -1 | ||||||
|  | 	ok = true | ||||||
|  | 	n := 0 | ||||||
|  | 	sz := 0 | ||||||
|  | 	var isW bool | ||||||
|  | 	for i := 0; i < len(format); i += sz { | ||||||
|  | 		if format[i] != '%' { | ||||||
|  | 			sz = 1 | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		// "%%" is not a format directive. | ||||||
|  | 		if i+1 < len(format) && format[i+1] == '%' { | ||||||
|  | 			sz = 2 | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		sz, isW = parsePrintfVerb(format[i:]) | ||||||
|  | 		if isW { | ||||||
|  | 			if idx >= 0 { | ||||||
|  | 				ok = false | ||||||
|  | 			} else { | ||||||
|  | 				idx = n | ||||||
|  | 			} | ||||||
|  | 			// "Replace" the last character, the 'w', with a 'v'. | ||||||
|  | 			p := i + sz - 1 | ||||||
|  | 			format = format[:p] + "v" + format[p+1:] | ||||||
|  | 		} | ||||||
|  | 		n++ | ||||||
|  | 	} | ||||||
|  | 	return idx, format, ok | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Parse the printf verb starting with a % at s[0]. | ||||||
|  | // Return how many bytes it occupies and whether the verb is 'w'. | ||||||
|  | func parsePrintfVerb(s string) (int, bool) { | ||||||
|  | 	// Assume only that the directive is a sequence of non-letters followed by a single letter. | ||||||
|  | 	sz := 0 | ||||||
|  | 	var r rune | ||||||
|  | 	for i := 1; i < len(s); i += sz { | ||||||
|  | 		r, sz = utf8.DecodeRuneInString(s[i:]) | ||||||
|  | 		if unicode.IsLetter(r) { | ||||||
|  | 			return i + sz, r == 'w' | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return len(s), false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type noWrapError struct { | ||||||
|  | 	msg   string | ||||||
|  | 	err   error | ||||||
|  | 	frame Frame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *noWrapError) Error() string { | ||||||
|  | 	return fmt.Sprint(e) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } | ||||||
|  | 
 | ||||||
|  | func (e *noWrapError) FormatError(p Printer) (next error) { | ||||||
|  | 	p.Print(e.msg) | ||||||
|  | 	e.frame.Format(p) | ||||||
|  | 	return e.err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type wrapError struct { | ||||||
|  | 	msg   string | ||||||
|  | 	err   error | ||||||
|  | 	frame Frame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *wrapError) Error() string { | ||||||
|  | 	return fmt.Sprint(e) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } | ||||||
|  | 
 | ||||||
|  | func (e *wrapError) FormatError(p Printer) (next error) { | ||||||
|  | 	p.Print(e.msg) | ||||||
|  | 	e.frame.Format(p) | ||||||
|  | 	return e.err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *wrapError) Unwrap() error { | ||||||
|  | 	return e.err | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								vendor/golang.org/x/xerrors/format.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								vendor/golang.org/x/xerrors/format.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | package xerrors | ||||||
|  | 
 | ||||||
|  | // A Formatter formats error messages. | ||||||
|  | type Formatter interface { | ||||||
|  | 	error | ||||||
|  | 
 | ||||||
|  | 	// FormatError prints the receiver's first error and returns the next error in | ||||||
|  | 	// the error chain, if any. | ||||||
|  | 	FormatError(p Printer) (next error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A Printer formats error messages. | ||||||
|  | // | ||||||
|  | // The most common implementation of Printer is the one provided by package fmt | ||||||
|  | // during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message | ||||||
|  | // typically provide their own implementations. | ||||||
|  | type Printer interface { | ||||||
|  | 	// Print appends args to the message output. | ||||||
|  | 	Print(args ...interface{}) | ||||||
|  | 
 | ||||||
|  | 	// Printf writes a formatted string. | ||||||
|  | 	Printf(format string, args ...interface{}) | ||||||
|  | 
 | ||||||
|  | 	// Detail reports whether error detail is requested. | ||||||
|  | 	// After the first call to Detail, all text written to the Printer | ||||||
|  | 	// is formatted as additional detail, or ignored when | ||||||
|  | 	// detail has not been requested. | ||||||
|  | 	// If Detail returns false, the caller can avoid printing the detail at all. | ||||||
|  | 	Detail() bool | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user