chore: migrate to gitea
This commit is contained in:
469
vendor/cloud.google.com/go/auth/CHANGES.md
generated
vendored
Normal file
469
vendor/cloud.google.com/go/auth/CHANGES.md
generated
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
# Changes
|
||||
|
||||
## [0.18.1](https://github.com/googleapis/google-cloud-go/releases/tag/auth%2Fv0.18.1) (2026-01-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add InternalOptions.TelemetryAttributes for internal client use (#13641) ([3876978](https://github.com/googleapis/google-cloud-go/commit/38769789755ed47d85e85dcd56596109de65f780))
|
||||
* remove singleton and restore normal usage of otelgrpc.clientHandler (#13522) ([673d4b0](https://github.com/googleapis/google-cloud-go/commit/673d4b05617f833aa433f7f6a350b5cb888ea20d))
|
||||
|
||||
## [0.18.0](https://github.com/googleapis/google-cloud-go/releases/tag/auth%2Fv0.18.0) (2025-12-15)
|
||||
|
||||
### Features
|
||||
|
||||
* Support scopes field from impersonated credential json (#13308) ([e3f62e1](https://github.com/googleapis/google-cloud-go/commit/e3f62e102840127a0058f5cced4c9738f2bf45f2))
|
||||
* add support for parsing EC private key (#13317) ([ea6bc62](https://github.com/googleapis/google-cloud-go/commit/ea6bc62ffe2cc0a6d607d698a181b37fa46c340d))
|
||||
* deprecate unsafe credentials JSON loading options (#13397) ([0dd2a3b](https://github.com/googleapis/google-cloud-go/commit/0dd2a3bdece9a85ee7216a737559fa9f5a869545))
|
||||
|
||||
## [0.17.0](https://github.com/googleapis/google-cloud-go/releases/tag/auth%2Fv0.17.0) (2025-10-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Add trust boundary support for service accounts and impersonation (HTTP/gRPC) (#11870) ([5c2b665](https://github.com/googleapis/google-cloud-go/commit/5c2b665f392e6dd90192f107188720aa1357e7da))
|
||||
* add trust boundary support for external accounts (#12864) ([a67a146](https://github.com/googleapis/google-cloud-go/commit/a67a146a6a88a6f1ba10c409dfce8015ecd60a64))
|
||||
|
||||
## [0.16.5](https://github.com/googleapis/google-cloud-go/compare/auth/v0.16.4...auth/v0.16.5) (2025-08-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Improve error message for unknown credentials type ([#12673](https://github.com/googleapis/google-cloud-go/issues/12673)) ([558b164](https://github.com/googleapis/google-cloud-go/commit/558b16429f621276694405fa5f2091199f2d4c4d))
|
||||
* **auth:** Set Content-Type in userTokenProvider.exchangeToken ([#12634](https://github.com/googleapis/google-cloud-go/issues/12634)) ([1197ebc](https://github.com/googleapis/google-cloud-go/commit/1197ebcbca491f8c610da732c7361c90bc6f46d0))
|
||||
|
||||
## [0.16.4](https://github.com/googleapis/google-cloud-go/compare/auth/v0.16.3...auth/v0.16.4) (2025-08-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Add UseDefaultClient: true to metadata.Options ([#12666](https://github.com/googleapis/google-cloud-go/issues/12666)) ([1482191](https://github.com/googleapis/google-cloud-go/commit/1482191e88236693efef68769752638281566766)), refs [#11078](https://github.com/googleapis/google-cloud-go/issues/11078) [#12657](https://github.com/googleapis/google-cloud-go/issues/12657)
|
||||
|
||||
## [0.16.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.16.2...auth/v0.16.3) (2025-07-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Fix race condition in cachedTokenProvider.tokenAsync ([#12586](https://github.com/googleapis/google-cloud-go/issues/12586)) ([73867cc](https://github.com/googleapis/google-cloud-go/commit/73867ccc1e9808d65361bcfc0776bd95fe34dbb3))
|
||||
|
||||
## [0.16.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.16.1...auth/v0.16.2) (2025-06-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Add back DirectPath misconfiguration logging ([#11162](https://github.com/googleapis/google-cloud-go/issues/11162)) ([8d52da5](https://github.com/googleapis/google-cloud-go/commit/8d52da58da5a0ed77a0f6307d1b561bc045406a1))
|
||||
* **auth:** Remove s2a fallback option ([#12354](https://github.com/googleapis/google-cloud-go/issues/12354)) ([d5acc59](https://github.com/googleapis/google-cloud-go/commit/d5acc599cd775ddc404349e75906fa02e8ff133e))
|
||||
|
||||
## [0.16.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.16.0...auth/v0.16.1) (2025-04-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Clone detectopts before assigning TokenBindingType ([#11881](https://github.com/googleapis/google-cloud-go/issues/11881)) ([2167b02](https://github.com/googleapis/google-cloud-go/commit/2167b020fdc43b517c2b6ecca264a10e357ea035))
|
||||
|
||||
## [0.16.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.15.0...auth/v0.16.0) (2025-04-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/credentials:** Return X.509 certificate chain as subject token ([#11948](https://github.com/googleapis/google-cloud-go/issues/11948)) ([d445a3f](https://github.com/googleapis/google-cloud-go/commit/d445a3f66272ffd5c39c4939af9bebad4582631c)), refs [#11757](https://github.com/googleapis/google-cloud-go/issues/11757)
|
||||
* **auth:** Configure DirectPath bound credentials from AllowedHardBoundTokens ([#11665](https://github.com/googleapis/google-cloud-go/issues/11665)) ([0fc40bc](https://github.com/googleapis/google-cloud-go/commit/0fc40bcf4e4673704df0973e9fa65957395d7bb4))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Allow non-default SA credentials for DP ([#11828](https://github.com/googleapis/google-cloud-go/issues/11828)) ([3a996b4](https://github.com/googleapis/google-cloud-go/commit/3a996b4129e6d0a34dfda6671f535d5aefb26a82))
|
||||
* **auth:** Restore calling DialContext ([#11930](https://github.com/googleapis/google-cloud-go/issues/11930)) ([9ec9a29](https://github.com/googleapis/google-cloud-go/commit/9ec9a29494e93197edbaf45aba28984801e9770a)), refs [#11118](https://github.com/googleapis/google-cloud-go/issues/11118)
|
||||
|
||||
## [0.15.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.14.1...auth/v0.15.0) (2025-02-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add hard-bound token request to compute token provider. ([#11588](https://github.com/googleapis/google-cloud-go/issues/11588)) ([0e608bb](https://github.com/googleapis/google-cloud-go/commit/0e608bb5ac3d694c8ad36ca4340071d3a2c78699))
|
||||
|
||||
## [0.14.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.14.0...auth/v0.14.1) (2025-01-24)
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* **auth:** Add warning about externally-provided credentials ([#11462](https://github.com/googleapis/google-cloud-go/issues/11462)) ([49fb6ff](https://github.com/googleapis/google-cloud-go/commit/49fb6ff4d754895f82c9c4d502fc7547d3b5a941))
|
||||
|
||||
## [0.14.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.13.0...auth/v0.14.0) (2025-01-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add universe domain support to idtoken ([#11059](https://github.com/googleapis/google-cloud-go/issues/11059)) ([72add7e](https://github.com/googleapis/google-cloud-go/commit/72add7e9f8f455af695e8ef79212a4bd3122fb3a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to v0.33.0 ([e9b0b69](https://github.com/googleapis/google-cloud-go/commit/e9b0b69644ea5b276cacff0a707e8a5e87efafc9))
|
||||
* **auth:** Fix copy of delegates in impersonate.NewIDTokenCredentials ([#11386](https://github.com/googleapis/google-cloud-go/issues/11386)) ([ff7ef8e](https://github.com/googleapis/google-cloud-go/commit/ff7ef8e7ade7171bce3e4f30ff10a2e9f6c27ca0)), refs [#11379](https://github.com/googleapis/google-cloud-go/issues/11379)
|
||||
* **auth:** Update golang.org/x/net to v0.33.0 ([e9b0b69](https://github.com/googleapis/google-cloud-go/commit/e9b0b69644ea5b276cacff0a707e8a5e87efafc9))
|
||||
|
||||
## [0.13.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.12.1...auth/v0.13.0) (2024-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add logging support ([#11079](https://github.com/googleapis/google-cloud-go/issues/11079)) ([c80e31d](https://github.com/googleapis/google-cloud-go/commit/c80e31df5ecb33a810be3dfb9d9e27ac531aa91d))
|
||||
* **auth:** Pass logger from auth layer to metadata package ([#11288](https://github.com/googleapis/google-cloud-go/issues/11288)) ([b552efd](https://github.com/googleapis/google-cloud-go/commit/b552efd6ab34e5dfded18438e0fbfd925805614f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Check compute cred type before non-default flag for DP ([#11255](https://github.com/googleapis/google-cloud-go/issues/11255)) ([4347ca1](https://github.com/googleapis/google-cloud-go/commit/4347ca141892be8ae813399b4b437662a103bc90))
|
||||
|
||||
## [0.12.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.12.0...auth/v0.12.1) (2024-12-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Correct typo in link ([#11160](https://github.com/googleapis/google-cloud-go/issues/11160)) ([af6fb46](https://github.com/googleapis/google-cloud-go/commit/af6fb46d7cd694ddbe8c9d63bc4cdcd62b9fb2c1))
|
||||
|
||||
## [0.12.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.11.0...auth/v0.12.0) (2024-12-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add support for providing custom certificate URL ([#11006](https://github.com/googleapis/google-cloud-go/issues/11006)) ([ebf3657](https://github.com/googleapis/google-cloud-go/commit/ebf36579724afb375d3974cf1da38f703e3b7dbc)), refs [#11005](https://github.com/googleapis/google-cloud-go/issues/11005)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Ensure endpoints are present in Validator ([#11209](https://github.com/googleapis/google-cloud-go/issues/11209)) ([106cd53](https://github.com/googleapis/google-cloud-go/commit/106cd53309facaef1b8ea78376179f523f6912b9)), refs [#11006](https://github.com/googleapis/google-cloud-go/issues/11006) [#11190](https://github.com/googleapis/google-cloud-go/issues/11190) [#11189](https://github.com/googleapis/google-cloud-go/issues/11189) [#11188](https://github.com/googleapis/google-cloud-go/issues/11188)
|
||||
|
||||
## [0.11.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.2...auth/v0.11.0) (2024-11-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add universe domain support to mTLS ([#11159](https://github.com/googleapis/google-cloud-go/issues/11159)) ([117748b](https://github.com/googleapis/google-cloud-go/commit/117748ba1cfd4ae62a6a4feb7e30951cb2bc9344))
|
||||
|
||||
## [0.10.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.1...auth/v0.10.2) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore use of grpc.Dial ([#11118](https://github.com/googleapis/google-cloud-go/issues/11118)) ([2456b94](https://github.com/googleapis/google-cloud-go/commit/2456b943b7b8aaabd4d8bfb7572c0f477ae0db45)), refs [#7556](https://github.com/googleapis/google-cloud-go/issues/7556)
|
||||
|
||||
## [0.10.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.0...auth/v0.10.1) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore Application Default Credentials support to idtoken ([#11083](https://github.com/googleapis/google-cloud-go/issues/11083)) ([8771f2e](https://github.com/googleapis/google-cloud-go/commit/8771f2ea9807ab822083808e0678392edff3b4f2))
|
||||
* **auth:** Skip impersonate universe domain check if empty ([#11086](https://github.com/googleapis/google-cloud-go/issues/11086)) ([87159c1](https://github.com/googleapis/google-cloud-go/commit/87159c1059d4a18d1367ce62746a838a94964ab6))
|
||||
|
||||
## [0.10.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.9...auth/v0.10.0) (2024-10-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add universe domain support to credentials/impersonate ([#10953](https://github.com/googleapis/google-cloud-go/issues/10953)) ([e06cb64](https://github.com/googleapis/google-cloud-go/commit/e06cb6499f7eda3aef08ab18ff197016f667684b))
|
||||
|
||||
## [0.9.9](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.8...auth/v0.9.9) (2024-10-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Fallback cert lookups for missing files ([#11013](https://github.com/googleapis/google-cloud-go/issues/11013)) ([bd76695](https://github.com/googleapis/google-cloud-go/commit/bd766957ec238b7c40ddbabb369e612dc9b07313)), refs [#10844](https://github.com/googleapis/google-cloud-go/issues/10844)
|
||||
* **auth:** Replace MDS endpoint universe_domain with universe-domain ([#11000](https://github.com/googleapis/google-cloud-go/issues/11000)) ([6a1586f](https://github.com/googleapis/google-cloud-go/commit/6a1586f2ce9974684affaea84e7b629313b4d114))
|
||||
|
||||
## [0.9.8](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.7...auth/v0.9.8) (2024-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore OpenTelemetry handling in transports ([#10968](https://github.com/googleapis/google-cloud-go/issues/10968)) ([08c6d04](https://github.com/googleapis/google-cloud-go/commit/08c6d04901c1a20e219b2d86df41dbaa6d7d7b55)), refs [#10962](https://github.com/googleapis/google-cloud-go/issues/10962)
|
||||
* **auth:** Try talk to plaintext S2A if credentials can not be found for mTLS-S2A ([#10941](https://github.com/googleapis/google-cloud-go/issues/10941)) ([0f0bf2d](https://github.com/googleapis/google-cloud-go/commit/0f0bf2d18c97dd8b65bcf0099f0802b5631c6287))
|
||||
|
||||
## [0.9.7](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.6...auth/v0.9.7) (2024-10-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore support for non-default service accounts for DirectPath ([#10937](https://github.com/googleapis/google-cloud-go/issues/10937)) ([a38650e](https://github.com/googleapis/google-cloud-go/commit/a38650edbf420223077498cafa537aec74b37aad)), refs [#10907](https://github.com/googleapis/google-cloud-go/issues/10907)
|
||||
|
||||
## [0.9.6](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.5...auth/v0.9.6) (2024-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Make aws credentials provider retrieve fresh credentials ([#10920](https://github.com/googleapis/google-cloud-go/issues/10920)) ([250fbf8](https://github.com/googleapis/google-cloud-go/commit/250fbf87d858d865e399a241b7e537c4ff0c3dd8))
|
||||
|
||||
## [0.9.5](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.4...auth/v0.9.5) (2024-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore support for GOOGLE_CLOUD_UNIVERSE_DOMAIN env ([#10915](https://github.com/googleapis/google-cloud-go/issues/10915)) ([94caaaa](https://github.com/googleapis/google-cloud-go/commit/94caaaa061362d0e00ef6214afcc8a0a3e7ebfb2))
|
||||
* **auth:** Skip directpath credentials overwrite when it's not on GCE ([#10833](https://github.com/googleapis/google-cloud-go/issues/10833)) ([7e5e8d1](https://github.com/googleapis/google-cloud-go/commit/7e5e8d10b761b0a6e43e19a028528db361bc07b1))
|
||||
* **auth:** Use new context for non-blocking token refresh ([#10919](https://github.com/googleapis/google-cloud-go/issues/10919)) ([cf7102d](https://github.com/googleapis/google-cloud-go/commit/cf7102d33a21be1e5a9d47a49456b3a57c43b350))
|
||||
|
||||
## [0.9.4](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.3...auth/v0.9.4) (2024-09-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Enable self-signed JWT for non-GDU universe domain ([#10831](https://github.com/googleapis/google-cloud-go/issues/10831)) ([f9869f7](https://github.com/googleapis/google-cloud-go/commit/f9869f7903cfd34d1b97c25d0dc5669d2c5138e6))
|
||||
|
||||
## [0.9.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.2...auth/v0.9.3) (2024-09-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Choose quota project envvar over file when both present ([#10807](https://github.com/googleapis/google-cloud-go/issues/10807)) ([2d8dd77](https://github.com/googleapis/google-cloud-go/commit/2d8dd7700eff92d4b95027be55e26e1e7aa79181)), refs [#10804](https://github.com/googleapis/google-cloud-go/issues/10804)
|
||||
|
||||
## [0.9.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.1...auth/v0.9.2) (2024-08-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Handle non-Transport DefaultTransport ([#10733](https://github.com/googleapis/google-cloud-go/issues/10733)) ([98d91dc](https://github.com/googleapis/google-cloud-go/commit/98d91dc8316b247498fab41ab35e57a0446fe556)), refs [#10742](https://github.com/googleapis/google-cloud-go/issues/10742)
|
||||
* **auth:** Make sure quota option takes precedence over env/file ([#10797](https://github.com/googleapis/google-cloud-go/issues/10797)) ([f1b050d](https://github.com/googleapis/google-cloud-go/commit/f1b050d56d804b245cab048c2980d32b0eaceb4e)), refs [#10795](https://github.com/googleapis/google-cloud-go/issues/10795)
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* **auth:** Fix Go doc comment link ([#10751](https://github.com/googleapis/google-cloud-go/issues/10751)) ([015acfa](https://github.com/googleapis/google-cloud-go/commit/015acfab4d172650928bb1119bc2cd6307b9a437))
|
||||
|
||||
## [0.9.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.0...auth/v0.9.1) (2024-08-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Setting expireEarly to default when the value is 0 ([#10732](https://github.com/googleapis/google-cloud-go/issues/10732)) ([5e67869](https://github.com/googleapis/google-cloud-go/commit/5e67869a31e9e8ecb4eeebd2cfa11a761c3b1948))
|
||||
|
||||
## [0.9.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.8.1...auth/v0.9.0) (2024-08-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Auth library can talk to S2A over mTLS ([#10634](https://github.com/googleapis/google-cloud-go/issues/10634)) ([5250a13](https://github.com/googleapis/google-cloud-go/commit/5250a13ec95b8d4eefbe0158f82857ff2189cb45))
|
||||
|
||||
## [0.8.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.8.0...auth/v0.8.1) (2024-08-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Make default client creation more lenient ([#10669](https://github.com/googleapis/google-cloud-go/issues/10669)) ([1afb9ee](https://github.com/googleapis/google-cloud-go/commit/1afb9ee1ee9de9810722800018133304a0ca34d1)), refs [#10638](https://github.com/googleapis/google-cloud-go/issues/10638)
|
||||
|
||||
## [0.8.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.3...auth/v0.8.0) (2024-08-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Adds support for X509 workload identity federation ([#10373](https://github.com/googleapis/google-cloud-go/issues/10373)) ([5d07505](https://github.com/googleapis/google-cloud-go/commit/5d075056cbe27bb1da4072a26070c41f8999eb9b))
|
||||
|
||||
## [0.7.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.2...auth/v0.7.3) (2024-08-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
|
||||
* **auth:** Disable automatic universe domain check for MDS ([#10620](https://github.com/googleapis/google-cloud-go/issues/10620)) ([7cea5ed](https://github.com/googleapis/google-cloud-go/commit/7cea5edd5a0c1e6bca558696f5607879141910e8))
|
||||
* **auth:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
|
||||
|
||||
## [0.7.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.1...auth/v0.7.2) (2024-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Use default client for universe metadata lookup ([#10551](https://github.com/googleapis/google-cloud-go/issues/10551)) ([d9046fd](https://github.com/googleapis/google-cloud-go/commit/d9046fdd1435d1ce48f374806c1def4cb5ac6cd3)), refs [#10544](https://github.com/googleapis/google-cloud-go/issues/10544)
|
||||
|
||||
## [0.7.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.0...auth/v0.7.1) (2024-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Bump google.golang.org/grpc@v1.64.1 ([8ecc4e9](https://github.com/googleapis/google-cloud-go/commit/8ecc4e9622e5bbe9b90384d5848ab816027226c5))
|
||||
|
||||
## [0.7.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.6.1...auth/v0.7.0) (2024-07-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add workload X509 cert provider as a default cert provider ([#10479](https://github.com/googleapis/google-cloud-go/issues/10479)) ([c51ee6c](https://github.com/googleapis/google-cloud-go/commit/c51ee6cf65ce05b4d501083e49d468c75ac1ea63))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Bump google.golang.org/api@v0.187.0 ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
|
||||
* **auth:** Bump google.golang.org/api@v0.187.0 ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
|
||||
* **auth:** Check len of slices, not non-nil ([#10483](https://github.com/googleapis/google-cloud-go/issues/10483)) ([0a966a1](https://github.com/googleapis/google-cloud-go/commit/0a966a183e5f0e811977216d736d875b7233e942))
|
||||
|
||||
## [0.6.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.6.0...auth/v0.6.1) (2024-07-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Support gRPC API keys ([#10460](https://github.com/googleapis/google-cloud-go/issues/10460)) ([daa6646](https://github.com/googleapis/google-cloud-go/commit/daa6646d2af5d7fb5b30489f4934c7db89868c7c))
|
||||
* **auth:** Update http and grpc transports to support token exchange over mTLS ([#10397](https://github.com/googleapis/google-cloud-go/issues/10397)) ([c6dfdcf](https://github.com/googleapis/google-cloud-go/commit/c6dfdcf893c3f971eba15026c12db0a960ae81f2))
|
||||
|
||||
## [0.6.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.2...auth/v0.6.0) (2024-06-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add non-blocking token refresh for compute MDS ([#10263](https://github.com/googleapis/google-cloud-go/issues/10263)) ([9ac350d](https://github.com/googleapis/google-cloud-go/commit/9ac350da11a49b8e2174d3fc5b1a5070fec78b4e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Return error if envvar detected file returns an error ([#10431](https://github.com/googleapis/google-cloud-go/issues/10431)) ([e52b9a7](https://github.com/googleapis/google-cloud-go/commit/e52b9a7c45468827f5d220ab00965191faeb9d05))
|
||||
|
||||
## [0.5.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.1...auth/v0.5.2) (2024-06-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Fetch initial token when CachedTokenProviderOptions.DisableAutoRefresh is true ([#10415](https://github.com/googleapis/google-cloud-go/issues/10415)) ([3266763](https://github.com/googleapis/google-cloud-go/commit/32667635ca2efad05cd8c087c004ca07d7406913)), refs [#10414](https://github.com/googleapis/google-cloud-go/issues/10414)
|
||||
|
||||
## [0.5.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.0...auth/v0.5.1) (2024-05-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Pass through client to 2LO and 3LO flows ([#10290](https://github.com/googleapis/google-cloud-go/issues/10290)) ([685784e](https://github.com/googleapis/google-cloud-go/commit/685784ea84358c15e9214bdecb307d37aa3b6d2f))
|
||||
|
||||
## [0.5.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.2...auth/v0.5.0) (2024-05-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Adds X509 workload certificate provider ([#10233](https://github.com/googleapis/google-cloud-go/issues/10233)) ([17a9db7](https://github.com/googleapis/google-cloud-go/commit/17a9db73af35e3d1a7a25ac4fd1377a103de6150))
|
||||
|
||||
## [0.4.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.1...auth/v0.4.2) (2024-05-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Enable client certificates by default only for GDU ([#10151](https://github.com/googleapis/google-cloud-go/issues/10151)) ([7c52978](https://github.com/googleapis/google-cloud-go/commit/7c529786275a39b7e00525f7d5e7be0d963e9e15))
|
||||
* **auth:** Handle non-Transport DefaultTransport ([#10162](https://github.com/googleapis/google-cloud-go/issues/10162)) ([fa3bfdb](https://github.com/googleapis/google-cloud-go/commit/fa3bfdb23aaa45b34394a8b61e753b3587506782)), refs [#10159](https://github.com/googleapis/google-cloud-go/issues/10159)
|
||||
* **auth:** Have refresh time match docs ([#10147](https://github.com/googleapis/google-cloud-go/issues/10147)) ([bcb5568](https://github.com/googleapis/google-cloud-go/commit/bcb5568c07a54dd3d2e869d15f502b0741a609e8))
|
||||
* **auth:** Update compute token fetching error with named prefix ([#10180](https://github.com/googleapis/google-cloud-go/issues/10180)) ([4573504](https://github.com/googleapis/google-cloud-go/commit/4573504828d2928bebedc875d87650ba227829ea))
|
||||
|
||||
## [0.4.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.0...auth/v0.4.1) (2024-05-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Don't try to detect default creds it opt configured ([#10143](https://github.com/googleapis/google-cloud-go/issues/10143)) ([804632e](https://github.com/googleapis/google-cloud-go/commit/804632e7c5b0b85ff522f7951114485e256eb5bc))
|
||||
|
||||
## [0.4.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.3.0...auth/v0.4.0) (2024-05-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Enable client certificates by default ([#10102](https://github.com/googleapis/google-cloud-go/issues/10102)) ([9013e52](https://github.com/googleapis/google-cloud-go/commit/9013e5200a6ec0f178ed91acb255481ffb073a2c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Get s2a logic up to date ([#10093](https://github.com/googleapis/google-cloud-go/issues/10093)) ([4fe9ae4](https://github.com/googleapis/google-cloud-go/commit/4fe9ae4b7101af2a5221d6d6b2e77b479305bb06))
|
||||
|
||||
## [0.3.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.2...auth/v0.3.0) (2024-04-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/httptransport:** Add ability to customize transport ([#10023](https://github.com/googleapis/google-cloud-go/issues/10023)) ([72c7f6b](https://github.com/googleapis/google-cloud-go/commit/72c7f6bbec3136cc7a62788fc7186bc33ef6c3b3)), refs [#9812](https://github.com/googleapis/google-cloud-go/issues/9812) [#9814](https://github.com/googleapis/google-cloud-go/issues/9814)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/credentials:** Error on bad file name if explicitly set ([#10018](https://github.com/googleapis/google-cloud-go/issues/10018)) ([55beaa9](https://github.com/googleapis/google-cloud-go/commit/55beaa993aaf052d8be39766afc6777c3c2a0bdd)), refs [#9809](https://github.com/googleapis/google-cloud-go/issues/9809)
|
||||
|
||||
## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.1...auth/v0.2.2) (2024-04-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Add internal opt to skip validation on transports ([#9999](https://github.com/googleapis/google-cloud-go/issues/9999)) ([9e20ef8](https://github.com/googleapis/google-cloud-go/commit/9e20ef89f6287d6bd03b8697d5898dc43b4a77cf)), refs [#9823](https://github.com/googleapis/google-cloud-go/issues/9823)
|
||||
* **auth:** Set secure flag for gRPC conn pools ([#10002](https://github.com/googleapis/google-cloud-go/issues/10002)) ([14e3956](https://github.com/googleapis/google-cloud-go/commit/14e3956dfd736399731b5ee8d9b178ae085cf7ba)), refs [#9833](https://github.com/googleapis/google-cloud-go/issues/9833)
|
||||
|
||||
## [0.2.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.0...auth/v0.2.1) (2024-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Default gRPC token type to Bearer if not set ([#9800](https://github.com/googleapis/google-cloud-go/issues/9800)) ([5284066](https://github.com/googleapis/google-cloud-go/commit/5284066670b6fe65d79089cfe0199c9660f87fc7))
|
||||
|
||||
## [0.2.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.1.1...auth/v0.2.0) (2024-04-15)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
In the below mentioned commits there were a few large breaking changes since the
|
||||
last release of the module.
|
||||
|
||||
1. The `Credentials` type has been moved to the root of the module as it is
|
||||
becoming the core abstraction for the whole module.
|
||||
2. Because of the above mentioned change many functions that previously
|
||||
returned a `TokenProvider` now return `Credentials`. Similarly, these
|
||||
functions have been renamed to be more specific.
|
||||
3. Most places that used to take an optional `TokenProvider` now accept
|
||||
`Credentials`. You can make a `Credentials` from a `TokenProvider` using the
|
||||
constructor found in the `auth` package.
|
||||
4. The `detect` package has been renamed to `credentials`. With this change some
|
||||
function signatures were also updated for better readability.
|
||||
5. Derivative auth flows like `impersonate` and `downscope` have been moved to
|
||||
be under the new `credentials` package.
|
||||
|
||||
Although these changes are disruptive we think that they are for the best of the
|
||||
long-term health of the module. We do not expect any more large breaking changes
|
||||
like these in future revisions, even before 1.0.0. This version will be the
|
||||
first version of the auth library that our client libraries start to use and
|
||||
depend on.
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/credentials/externalaccount:** Add default TokenURL ([#9700](https://github.com/googleapis/google-cloud-go/issues/9700)) ([81830e6](https://github.com/googleapis/google-cloud-go/commit/81830e6848ceefd055aa4d08f933d1154455a0f6))
|
||||
* **auth:** Add downscope.Options.UniverseDomain ([#9634](https://github.com/googleapis/google-cloud-go/issues/9634)) ([52cf7d7](https://github.com/googleapis/google-cloud-go/commit/52cf7d780853594291c4e34302d618299d1f5a1d))
|
||||
* **auth:** Add universe domain to grpctransport and httptransport ([#9663](https://github.com/googleapis/google-cloud-go/issues/9663)) ([67d353b](https://github.com/googleapis/google-cloud-go/commit/67d353beefe3b607c08c891876fbd95ab89e5fe3)), refs [#9670](https://github.com/googleapis/google-cloud-go/issues/9670)
|
||||
* **auth:** Add UniverseDomain to DetectOptions ([#9536](https://github.com/googleapis/google-cloud-go/issues/9536)) ([3618d3f](https://github.com/googleapis/google-cloud-go/commit/3618d3f7061615c0e189f376c75abc201203b501))
|
||||
* **auth:** Make package externalaccount public ([#9633](https://github.com/googleapis/google-cloud-go/issues/9633)) ([a0978d8](https://github.com/googleapis/google-cloud-go/commit/a0978d8e96968399940ebd7d092539772bf9caac))
|
||||
* **auth:** Move credentials to base auth package ([#9590](https://github.com/googleapis/google-cloud-go/issues/9590)) ([1a04baf](https://github.com/googleapis/google-cloud-go/commit/1a04bafa83c27342b9308d785645e1e5423ea10d))
|
||||
* **auth:** Refactor public sigs to use Credentials ([#9603](https://github.com/googleapis/google-cloud-go/issues/9603)) ([69cb240](https://github.com/googleapis/google-cloud-go/commit/69cb240c530b1f7173a9af2555c19e9a1beb56c5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
|
||||
* **auth:** Fix uint32 conversion ([9221c7f](https://github.com/googleapis/google-cloud-go/commit/9221c7fa12cef9d5fb7ddc92f41f1d6204971c7b))
|
||||
* **auth:** Port sts expires fix ([#9618](https://github.com/googleapis/google-cloud-go/issues/9618)) ([7bec97b](https://github.com/googleapis/google-cloud-go/commit/7bec97b2f51ed3ac4f9b88bf100d301da3f5d1bd))
|
||||
* **auth:** Read universe_domain from all credentials files ([#9632](https://github.com/googleapis/google-cloud-go/issues/9632)) ([16efbb5](https://github.com/googleapis/google-cloud-go/commit/16efbb52e39ea4a319e5ee1e95c0e0305b6d9824))
|
||||
* **auth:** Remove content-type header from idms get requests ([#9508](https://github.com/googleapis/google-cloud-go/issues/9508)) ([8589f41](https://github.com/googleapis/google-cloud-go/commit/8589f41599d265d7c3d46a3d86c9fab2329cbdd9))
|
||||
* **auth:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
|
||||
|
||||
## [0.1.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.1.0...auth/v0.1.1) (2024-03-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/impersonate:** Properly send default detect params ([#9529](https://github.com/googleapis/google-cloud-go/issues/9529)) ([5b6b8be](https://github.com/googleapis/google-cloud-go/commit/5b6b8bef577f82707e51f5cc5d258d5bdf90218f)), refs [#9136](https://github.com/googleapis/google-cloud-go/issues/9136)
|
||||
* **auth:** Update grpc-go to v1.56.3 ([343cea8](https://github.com/googleapis/google-cloud-go/commit/343cea8c43b1e31ae21ad50ad31d3b0b60143f8c))
|
||||
* **auth:** Update grpc-go to v1.59.0 ([81a97b0](https://github.com/googleapis/google-cloud-go/commit/81a97b06cb28b25432e4ece595c55a9857e960b7))
|
||||
|
||||
## 0.1.0 (2023-10-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add base auth package ([#8465](https://github.com/googleapis/google-cloud-go/issues/8465)) ([6a45f26](https://github.com/googleapis/google-cloud-go/commit/6a45f26b809b64edae21f312c18d4205f96b180e))
|
||||
* **auth:** Add cert support to httptransport ([#8569](https://github.com/googleapis/google-cloud-go/issues/8569)) ([37e3435](https://github.com/googleapis/google-cloud-go/commit/37e3435f8e98595eafab481bdfcb31a4c56fa993))
|
||||
* **auth:** Add Credentials.UniverseDomain() ([#8654](https://github.com/googleapis/google-cloud-go/issues/8654)) ([af0aa1e](https://github.com/googleapis/google-cloud-go/commit/af0aa1ed8015bc8fe0dd87a7549ae029107cbdb8))
|
||||
* **auth:** Add detect package ([#8491](https://github.com/googleapis/google-cloud-go/issues/8491)) ([d977419](https://github.com/googleapis/google-cloud-go/commit/d977419a3269f6acc193df77a2136a6eb4b4add7))
|
||||
* **auth:** Add downscope package ([#8532](https://github.com/googleapis/google-cloud-go/issues/8532)) ([dda9bff](https://github.com/googleapis/google-cloud-go/commit/dda9bff8ec70e6d104901b4105d13dcaa4e2404c))
|
||||
* **auth:** Add grpctransport package ([#8625](https://github.com/googleapis/google-cloud-go/issues/8625)) ([69a8347](https://github.com/googleapis/google-cloud-go/commit/69a83470bdcc7ed10c6c36d1abc3b7cfdb8a0ee5))
|
||||
* **auth:** Add httptransport package ([#8567](https://github.com/googleapis/google-cloud-go/issues/8567)) ([6898597](https://github.com/googleapis/google-cloud-go/commit/6898597d2ea95d630fcd00fd15c58c75ea843bff))
|
||||
* **auth:** Add idtoken package ([#8580](https://github.com/googleapis/google-cloud-go/issues/8580)) ([a79e693](https://github.com/googleapis/google-cloud-go/commit/a79e693e97e4e3e1c6742099af3dbc58866d88fe))
|
||||
* **auth:** Add impersonate package ([#8578](https://github.com/googleapis/google-cloud-go/issues/8578)) ([e29ba0c](https://github.com/googleapis/google-cloud-go/commit/e29ba0cb7bd3888ab9e808087027dc5a32474c04))
|
||||
* **auth:** Add support for external accounts in detect ([#8508](https://github.com/googleapis/google-cloud-go/issues/8508)) ([62210d5](https://github.com/googleapis/google-cloud-go/commit/62210d5d3e56e8e9f35db8e6ac0defec19582507))
|
||||
* **auth:** Port external account changes ([#8697](https://github.com/googleapis/google-cloud-go/issues/8697)) ([5823db5](https://github.com/googleapis/google-cloud-go/commit/5823db5d633069999b58b9131a7f9cd77e82c899))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
|
||||
* **auth:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
|
||||
202
vendor/cloud.google.com/go/auth/LICENSE
generated
vendored
Normal file
202
vendor/cloud.google.com/go/auth/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
40
vendor/cloud.google.com/go/auth/README.md
generated
vendored
Normal file
40
vendor/cloud.google.com/go/auth/README.md
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# Google Auth Library for Go
|
||||
|
||||
[](https://pkg.go.dev/cloud.google.com/go/auth)
|
||||
|
||||
## Install
|
||||
|
||||
``` bash
|
||||
go get cloud.google.com/go/auth@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The most common way this library is used is transitively, by default, from any
|
||||
of our Go client libraries.
|
||||
|
||||
### Notable use-cases
|
||||
|
||||
- To create a credential directly please see examples in the
|
||||
[credentials](https://pkg.go.dev/cloud.google.com/go/auth/credentials)
|
||||
package.
|
||||
- To create a authenticated HTTP client please see examples in the
|
||||
[httptransport](https://pkg.go.dev/cloud.google.com/go/auth/httptransport)
|
||||
package.
|
||||
- To create a authenticated gRPC connection please see examples in the
|
||||
[grpctransport](https://pkg.go.dev/cloud.google.com/go/auth/grpctransport)
|
||||
package.
|
||||
- To create an ID token please see examples in the
|
||||
[idtoken](https://pkg.go.dev/cloud.google.com/go/auth/credentials/idtoken)
|
||||
package.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please, see the
|
||||
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md)
|
||||
document for details.
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md#contributor-code-of-conduct)
|
||||
for more information.
|
||||
618
vendor/cloud.google.com/go/auth/auth.go
generated
vendored
Normal file
618
vendor/cloud.google.com/go/auth/auth.go
generated
vendored
Normal file
@@ -0,0 +1,618 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package auth provides utilities for managing Google Cloud credentials,
|
||||
// including functionality for creating, caching, and refreshing OAuth2 tokens.
|
||||
// It offers customizable options for different OAuth2 flows, such as 2-legged
|
||||
// (2LO) and 3-legged (3LO) OAuth, along with support for PKCE and automatic
|
||||
// token management.
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/jwt"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// Parameter keys for AuthCodeURL method to support PKCE.
|
||||
codeChallengeKey = "code_challenge"
|
||||
codeChallengeMethodKey = "code_challenge_method"
|
||||
|
||||
// Parameter key for Exchange method to support PKCE.
|
||||
codeVerifierKey = "code_verifier"
|
||||
|
||||
// 3 minutes and 45 seconds before expiration. The shortest MDS cache is 4 minutes,
|
||||
// so we give it 15 seconds to refresh it's cache before attempting to refresh a token.
|
||||
defaultExpiryDelta = 225 * time.Second
|
||||
|
||||
universeDomainDefault = "googleapis.com"
|
||||
)
|
||||
|
||||
// tokenState represents different states for a [Token].
|
||||
type tokenState int
|
||||
|
||||
const (
|
||||
// fresh indicates that the [Token] is valid. It is not expired or close to
|
||||
// expired, or the token has no expiry.
|
||||
fresh tokenState = iota
|
||||
// stale indicates that the [Token] is close to expired, and should be
|
||||
// refreshed. The token can be used normally.
|
||||
stale
|
||||
// invalid indicates that the [Token] is expired or invalid. The token
|
||||
// cannot be used for a normal operation.
|
||||
invalid
|
||||
)
|
||||
|
||||
var (
|
||||
defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
defaultHeader = &jwt.Header{Algorithm: jwt.HeaderAlgRSA256, Type: jwt.HeaderType}
|
||||
|
||||
// for testing
|
||||
timeNow = time.Now
|
||||
)
|
||||
|
||||
// TokenProvider specifies an interface for anything that can return a token.
|
||||
type TokenProvider interface {
|
||||
// Token returns a Token or an error.
|
||||
// The Token returned must be safe to use
|
||||
// concurrently.
|
||||
// The returned Token must not be modified.
|
||||
// The context provided must be sent along to any requests that are made in
|
||||
// the implementing code.
|
||||
Token(context.Context) (*Token, error)
|
||||
}
|
||||
|
||||
// Token holds the credential token used to authorized requests. All fields are
|
||||
// considered read-only.
|
||||
type Token struct {
|
||||
// Value is the token used to authorize requests. It is usually an access
|
||||
// token but may be other types of tokens such as ID tokens in some flows.
|
||||
Value string
|
||||
// Type is the type of token Value is. If uninitialized, it should be
|
||||
// assumed to be a "Bearer" token.
|
||||
Type string
|
||||
// Expiry is the time the token is set to expire.
|
||||
Expiry time.Time
|
||||
// Metadata may include, but is not limited to, the body of the token
|
||||
// response returned by the server.
|
||||
Metadata map[string]interface{} // TODO(codyoss): maybe make a method to flatten metadata to avoid []string for url.Values
|
||||
}
|
||||
|
||||
// IsValid reports that a [Token] is non-nil, has a [Token.Value], and has not
|
||||
// expired. A token is considered expired if [Token.Expiry] has passed or will
|
||||
// pass in the next 225 seconds.
|
||||
func (t *Token) IsValid() bool {
|
||||
return t.isValidWithEarlyExpiry(defaultExpiryDelta)
|
||||
}
|
||||
|
||||
// MetadataString is a convenience method for accessing string values in the
|
||||
// token's metadata. Returns an empty string if the metadata is nil or the value
|
||||
// for the given key cannot be cast to a string.
|
||||
func (t *Token) MetadataString(k string) string {
|
||||
if t.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
s, ok := t.Metadata[k].(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *Token) isValidWithEarlyExpiry(earlyExpiry time.Duration) bool {
|
||||
if t.isEmpty() {
|
||||
return false
|
||||
}
|
||||
if t.Expiry.IsZero() {
|
||||
return true
|
||||
}
|
||||
return !t.Expiry.Round(0).Add(-earlyExpiry).Before(timeNow())
|
||||
}
|
||||
|
||||
func (t *Token) isEmpty() bool {
|
||||
return t == nil || t.Value == ""
|
||||
}
|
||||
|
||||
// Credentials holds Google credentials, including
|
||||
// [Application Default Credentials].
|
||||
//
|
||||
// [Application Default Credentials]: https://developers.google.com/accounts/docs/application-default-credentials
|
||||
type Credentials struct {
|
||||
json []byte
|
||||
projectID CredentialsPropertyProvider
|
||||
quotaProjectID CredentialsPropertyProvider
|
||||
// universeDomain is the default service domain for a given Cloud universe.
|
||||
universeDomain CredentialsPropertyProvider
|
||||
|
||||
TokenProvider
|
||||
}
|
||||
|
||||
// JSON returns the bytes associated with the the file used to source
|
||||
// credentials if one was used.
|
||||
func (c *Credentials) JSON() []byte {
|
||||
return c.json
|
||||
}
|
||||
|
||||
// ProjectID returns the associated project ID from the underlying file or
|
||||
// environment.
|
||||
func (c *Credentials) ProjectID(ctx context.Context) (string, error) {
|
||||
if c.projectID == nil {
|
||||
return internal.GetProjectID(c.json, ""), nil
|
||||
}
|
||||
v, err := c.projectID.GetProperty(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return internal.GetProjectID(c.json, v), nil
|
||||
}
|
||||
|
||||
// QuotaProjectID returns the associated quota project ID from the underlying
|
||||
// file or environment.
|
||||
func (c *Credentials) QuotaProjectID(ctx context.Context) (string, error) {
|
||||
if c.quotaProjectID == nil {
|
||||
return internal.GetQuotaProject(c.json, ""), nil
|
||||
}
|
||||
v, err := c.quotaProjectID.GetProperty(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return internal.GetQuotaProject(c.json, v), nil
|
||||
}
|
||||
|
||||
// UniverseDomain returns the default service domain for a given Cloud universe.
|
||||
// The default value is "googleapis.com".
|
||||
func (c *Credentials) UniverseDomain(ctx context.Context) (string, error) {
|
||||
if c.universeDomain == nil {
|
||||
return universeDomainDefault, nil
|
||||
}
|
||||
v, err := c.universeDomain.GetProperty(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if v == "" {
|
||||
return universeDomainDefault, nil
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// CredentialsPropertyProvider provides an implementation to fetch a property
|
||||
// value for [Credentials].
|
||||
type CredentialsPropertyProvider interface {
|
||||
GetProperty(context.Context) (string, error)
|
||||
}
|
||||
|
||||
// CredentialsPropertyFunc is a type adapter to allow the use of ordinary
|
||||
// functions as a [CredentialsPropertyProvider].
|
||||
type CredentialsPropertyFunc func(context.Context) (string, error)
|
||||
|
||||
// GetProperty loads the properly value provided the given context.
|
||||
func (p CredentialsPropertyFunc) GetProperty(ctx context.Context) (string, error) {
|
||||
return p(ctx)
|
||||
}
|
||||
|
||||
// CredentialsOptions are used to configure [Credentials].
|
||||
type CredentialsOptions struct {
|
||||
// TokenProvider is a means of sourcing a token for the credentials. Required.
|
||||
TokenProvider TokenProvider
|
||||
// JSON is the raw contents of the credentials file if sourced from a file.
|
||||
JSON []byte
|
||||
// ProjectIDProvider resolves the project ID associated with the
|
||||
// credentials.
|
||||
ProjectIDProvider CredentialsPropertyProvider
|
||||
// QuotaProjectIDProvider resolves the quota project ID associated with the
|
||||
// credentials.
|
||||
QuotaProjectIDProvider CredentialsPropertyProvider
|
||||
// UniverseDomainProvider resolves the universe domain with the credentials.
|
||||
UniverseDomainProvider CredentialsPropertyProvider
|
||||
}
|
||||
|
||||
// NewCredentials returns new [Credentials] from the provided options.
|
||||
func NewCredentials(opts *CredentialsOptions) *Credentials {
|
||||
creds := &Credentials{
|
||||
TokenProvider: opts.TokenProvider,
|
||||
json: opts.JSON,
|
||||
projectID: opts.ProjectIDProvider,
|
||||
quotaProjectID: opts.QuotaProjectIDProvider,
|
||||
universeDomain: opts.UniverseDomainProvider,
|
||||
}
|
||||
|
||||
return creds
|
||||
}
|
||||
|
||||
// CachedTokenProviderOptions provides options for configuring a cached
|
||||
// [TokenProvider].
|
||||
type CachedTokenProviderOptions struct {
|
||||
// DisableAutoRefresh makes the TokenProvider always return the same token,
|
||||
// even if it is expired. The default is false. Optional.
|
||||
DisableAutoRefresh bool
|
||||
// ExpireEarly configures the amount of time before a token expires, that it
|
||||
// should be refreshed. If unset, the default value is 3 minutes and 45
|
||||
// seconds. Optional.
|
||||
ExpireEarly time.Duration
|
||||
// DisableAsyncRefresh configures a synchronous workflow that refreshes
|
||||
// tokens in a blocking manner. The default is false. Optional.
|
||||
DisableAsyncRefresh bool
|
||||
}
|
||||
|
||||
func (ctpo *CachedTokenProviderOptions) autoRefresh() bool {
|
||||
if ctpo == nil {
|
||||
return true
|
||||
}
|
||||
return !ctpo.DisableAutoRefresh
|
||||
}
|
||||
|
||||
func (ctpo *CachedTokenProviderOptions) expireEarly() time.Duration {
|
||||
if ctpo == nil || ctpo.ExpireEarly == 0 {
|
||||
return defaultExpiryDelta
|
||||
}
|
||||
return ctpo.ExpireEarly
|
||||
}
|
||||
|
||||
func (ctpo *CachedTokenProviderOptions) blockingRefresh() bool {
|
||||
if ctpo == nil {
|
||||
return false
|
||||
}
|
||||
return ctpo.DisableAsyncRefresh
|
||||
}
|
||||
|
||||
// NewCachedTokenProvider wraps a [TokenProvider] to cache the tokens returned
|
||||
// by the underlying provider. By default it will refresh tokens asynchronously
|
||||
// a few minutes before they expire.
|
||||
func NewCachedTokenProvider(tp TokenProvider, opts *CachedTokenProviderOptions) TokenProvider {
|
||||
if ctp, ok := tp.(*cachedTokenProvider); ok {
|
||||
return ctp
|
||||
}
|
||||
return &cachedTokenProvider{
|
||||
tp: tp,
|
||||
autoRefresh: opts.autoRefresh(),
|
||||
expireEarly: opts.expireEarly(),
|
||||
blockingRefresh: opts.blockingRefresh(),
|
||||
}
|
||||
}
|
||||
|
||||
type cachedTokenProvider struct {
|
||||
tp TokenProvider
|
||||
autoRefresh bool
|
||||
expireEarly time.Duration
|
||||
blockingRefresh bool
|
||||
|
||||
mu sync.Mutex
|
||||
cachedToken *Token
|
||||
// isRefreshRunning ensures that the non-blocking refresh will only be
|
||||
// attempted once, even if multiple callers enter the Token method.
|
||||
isRefreshRunning bool
|
||||
// isRefreshErr ensures that the non-blocking refresh will only be attempted
|
||||
// once per refresh window if an error is encountered.
|
||||
isRefreshErr bool
|
||||
}
|
||||
|
||||
func (c *cachedTokenProvider) Token(ctx context.Context) (*Token, error) {
|
||||
if c.blockingRefresh {
|
||||
return c.tokenBlocking(ctx)
|
||||
}
|
||||
return c.tokenNonBlocking(ctx)
|
||||
}
|
||||
|
||||
func (c *cachedTokenProvider) tokenNonBlocking(ctx context.Context) (*Token, error) {
|
||||
switch c.tokenState() {
|
||||
case fresh:
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.cachedToken, nil
|
||||
case stale:
|
||||
// Call tokenAsync with a new Context because the user-provided context
|
||||
// may have a short timeout incompatible with async token refresh.
|
||||
c.tokenAsync(context.Background())
|
||||
// Return the stale token immediately to not block customer requests to Cloud services.
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.cachedToken, nil
|
||||
default: // invalid
|
||||
return c.tokenBlocking(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// tokenState reports the token's validity.
|
||||
func (c *cachedTokenProvider) tokenState() tokenState {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
t := c.cachedToken
|
||||
now := timeNow()
|
||||
if t == nil || t.Value == "" {
|
||||
return invalid
|
||||
} else if t.Expiry.IsZero() {
|
||||
return fresh
|
||||
} else if now.After(t.Expiry.Round(0)) {
|
||||
return invalid
|
||||
} else if now.After(t.Expiry.Round(0).Add(-c.expireEarly)) {
|
||||
return stale
|
||||
}
|
||||
return fresh
|
||||
}
|
||||
|
||||
// tokenAsync uses a bool to ensure that only one non-blocking token refresh
|
||||
// happens at a time, even if multiple callers have entered this function
|
||||
// concurrently. This avoids creating an arbitrary number of concurrent
|
||||
// goroutines. Retries should be attempted and managed within the Token method.
|
||||
// If the refresh attempt fails, no further attempts are made until the refresh
|
||||
// window expires and the token enters the invalid state, at which point the
|
||||
// blocking call to Token should likely return the same error on the main goroutine.
|
||||
func (c *cachedTokenProvider) tokenAsync(ctx context.Context) {
|
||||
fn := func() {
|
||||
t, err := c.tp.Token(ctx)
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.isRefreshRunning = false
|
||||
if err != nil {
|
||||
// Discard errors from the non-blocking refresh, but prevent further
|
||||
// attempts.
|
||||
c.isRefreshErr = true
|
||||
return
|
||||
}
|
||||
c.cachedToken = t
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if !c.isRefreshRunning && !c.isRefreshErr {
|
||||
c.isRefreshRunning = true
|
||||
go fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cachedTokenProvider) tokenBlocking(ctx context.Context) (*Token, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.isRefreshErr = false
|
||||
if c.cachedToken.IsValid() || (!c.autoRefresh && !c.cachedToken.isEmpty()) {
|
||||
return c.cachedToken, nil
|
||||
}
|
||||
t, err := c.tp.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.cachedToken = t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Error is a error associated with retrieving a [Token]. It can hold useful
|
||||
// additional details for debugging.
|
||||
type Error struct {
|
||||
// Response is the HTTP response associated with error. The body will always
|
||||
// be already closed and consumed.
|
||||
Response *http.Response
|
||||
// Body is the HTTP response body.
|
||||
Body []byte
|
||||
// Err is the underlying wrapped error.
|
||||
Err error
|
||||
|
||||
// code returned in the token response
|
||||
code string
|
||||
// description returned in the token response
|
||||
description string
|
||||
// uri returned in the token response
|
||||
uri string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
if e.code != "" {
|
||||
s := fmt.Sprintf("auth: %q", e.code)
|
||||
if e.description != "" {
|
||||
s += fmt.Sprintf(" %q", e.description)
|
||||
}
|
||||
if e.uri != "" {
|
||||
s += fmt.Sprintf(" %q", e.uri)
|
||||
}
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("auth: cannot fetch token: %v\nResponse: %s", e.Response.StatusCode, e.Body)
|
||||
}
|
||||
|
||||
// Temporary returns true if the error is considered temporary and may be able
|
||||
// to be retried.
|
||||
func (e *Error) Temporary() bool {
|
||||
if e.Response == nil {
|
||||
return false
|
||||
}
|
||||
sc := e.Response.StatusCode
|
||||
return sc == http.StatusInternalServerError || sc == http.StatusServiceUnavailable || sc == http.StatusRequestTimeout || sc == http.StatusTooManyRequests
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// Style describes how the token endpoint wants to receive the ClientID and
|
||||
// ClientSecret.
|
||||
type Style int
|
||||
|
||||
const (
|
||||
// StyleUnknown means the value has not been initiated. Sending this in
|
||||
// a request will cause the token exchange to fail.
|
||||
StyleUnknown Style = iota
|
||||
// StyleInParams sends client info in the body of a POST request.
|
||||
StyleInParams
|
||||
// StyleInHeader sends client info using Basic Authorization header.
|
||||
StyleInHeader
|
||||
)
|
||||
|
||||
// Options2LO is the configuration settings for doing a 2-legged JWT OAuth2 flow.
|
||||
type Options2LO struct {
|
||||
// Email is the OAuth2 client ID. This value is set as the "iss" in the
|
||||
// JWT.
|
||||
Email string
|
||||
// PrivateKey contains the contents of an RSA private key or the
|
||||
// contents of a PEM file that contains a private key. It is used to sign
|
||||
// the JWT created.
|
||||
PrivateKey []byte
|
||||
// TokenURL is th URL the JWT is sent to. Required.
|
||||
TokenURL string
|
||||
// PrivateKeyID is the ID of the key used to sign the JWT. It is used as the
|
||||
// "kid" in the JWT header. Optional.
|
||||
PrivateKeyID string
|
||||
// Subject is the used for to impersonate a user. It is used as the "sub" in
|
||||
// the JWT.m Optional.
|
||||
Subject string
|
||||
// Scopes specifies requested permissions for the token. Optional.
|
||||
Scopes []string
|
||||
// Expires specifies the lifetime of the token. Optional.
|
||||
Expires time.Duration
|
||||
// Audience specifies the "aud" in the JWT. Optional.
|
||||
Audience string
|
||||
// PrivateClaims allows specifying any custom claims for the JWT. Optional.
|
||||
PrivateClaims map[string]interface{}
|
||||
// UniverseDomain is the default service domain for a given Cloud universe.
|
||||
UniverseDomain string
|
||||
|
||||
// Client is the client to be used to make the underlying token requests.
|
||||
// Optional.
|
||||
Client *http.Client
|
||||
// UseIDToken requests that the token returned be an ID token if one is
|
||||
// returned from the server. Optional.
|
||||
UseIDToken bool
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (o *Options2LO) client() *http.Client {
|
||||
if o.Client != nil {
|
||||
return o.Client
|
||||
}
|
||||
return internal.DefaultClient()
|
||||
}
|
||||
|
||||
func (o *Options2LO) validate() error {
|
||||
if o == nil {
|
||||
return errors.New("auth: options must be provided")
|
||||
}
|
||||
if o.Email == "" {
|
||||
return errors.New("auth: email must be provided")
|
||||
}
|
||||
if len(o.PrivateKey) == 0 {
|
||||
return errors.New("auth: private key must be provided")
|
||||
}
|
||||
if o.TokenURL == "" {
|
||||
return errors.New("auth: token URL must be provided")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// New2LOTokenProvider returns a [TokenProvider] from the provided options.
|
||||
func New2LOTokenProvider(opts *Options2LO) (TokenProvider, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tokenProvider2LO{opts: opts, Client: opts.client(), logger: internallog.New(opts.Logger)}, nil
|
||||
}
|
||||
|
||||
type tokenProvider2LO struct {
|
||||
opts *Options2LO
|
||||
Client *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (tp tokenProvider2LO) Token(ctx context.Context) (*Token, error) {
|
||||
pk, err := internal.ParseKey(tp.opts.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claimSet := &jwt.Claims{
|
||||
Iss: tp.opts.Email,
|
||||
Scope: strings.Join(tp.opts.Scopes, " "),
|
||||
Aud: tp.opts.TokenURL,
|
||||
AdditionalClaims: tp.opts.PrivateClaims,
|
||||
Sub: tp.opts.Subject,
|
||||
}
|
||||
if t := tp.opts.Expires; t > 0 {
|
||||
claimSet.Exp = time.Now().Add(t).Unix()
|
||||
}
|
||||
if aud := tp.opts.Audience; aud != "" {
|
||||
claimSet.Aud = aud
|
||||
}
|
||||
h := *defaultHeader
|
||||
h.KeyID = tp.opts.PrivateKeyID
|
||||
payload, err := jwt.EncodeJWS(&h, claimSet, pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Set("grant_type", defaultGrantType)
|
||||
v.Set("assertion", payload)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", tp.opts.TokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
tp.logger.DebugContext(ctx, "2LO token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
|
||||
resp, body, err := internal.DoRequest(tp.Client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
|
||||
}
|
||||
tp.logger.DebugContext(ctx, "2LO token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
|
||||
return nil, &Error{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
// tokenRes is the JSON response body.
|
||||
var tokenRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
IDToken string `json:"id_token"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||
return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
|
||||
}
|
||||
token := &Token{
|
||||
Value: tokenRes.AccessToken,
|
||||
Type: tokenRes.TokenType,
|
||||
}
|
||||
token.Metadata = make(map[string]interface{})
|
||||
json.Unmarshal(body, &token.Metadata) // no error checks for optional fields
|
||||
|
||||
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
||||
}
|
||||
if v := tokenRes.IDToken; v != "" {
|
||||
// decode returned id token to get expiry
|
||||
claimSet, err := jwt.DecodeJWS(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth: error decoding JWT token: %w", err)
|
||||
}
|
||||
token.Expiry = time.Unix(claimSet.Exp, 0)
|
||||
}
|
||||
if tp.opts.UseIDToken {
|
||||
if tokenRes.IDToken == "" {
|
||||
return nil, fmt.Errorf("auth: response doesn't have JWT token")
|
||||
}
|
||||
token.Value = tokenRes.IDToken
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
102
vendor/cloud.google.com/go/auth/credentials/compute.go
generated
vendored
Normal file
102
vendor/cloud.google.com/go/auth/credentials/compute.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
computeTokenMetadata = map[string]interface{}{
|
||||
"auth.google.tokenSource": "compute-metadata",
|
||||
"auth.google.serviceAccount": "default",
|
||||
}
|
||||
computeTokenURI = "instance/service-accounts/default/token"
|
||||
)
|
||||
|
||||
// computeTokenProvider creates a [cloud.google.com/go/auth.TokenProvider] that
|
||||
// uses the metadata service to retrieve tokens.
|
||||
func computeTokenProvider(opts *DetectOptions, client *metadata.Client) auth.TokenProvider {
|
||||
return auth.NewCachedTokenProvider(&computeProvider{
|
||||
scopes: opts.Scopes,
|
||||
client: client,
|
||||
tokenBindingType: opts.TokenBindingType,
|
||||
}, &auth.CachedTokenProviderOptions{
|
||||
ExpireEarly: opts.EarlyTokenRefresh,
|
||||
DisableAsyncRefresh: opts.DisableAsyncRefresh,
|
||||
})
|
||||
}
|
||||
|
||||
// computeProvider fetches tokens from the google cloud metadata service.
|
||||
type computeProvider struct {
|
||||
scopes []string
|
||||
client *metadata.Client
|
||||
tokenBindingType TokenBindingType
|
||||
}
|
||||
|
||||
type metadataTokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresInSec int `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
func (cs *computeProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
tokenURI, err := url.Parse(computeTokenURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasScopes := len(cs.scopes) > 0
|
||||
if hasScopes || cs.tokenBindingType != NoBinding {
|
||||
v := url.Values{}
|
||||
if hasScopes {
|
||||
v.Set("scopes", strings.Join(cs.scopes, ","))
|
||||
}
|
||||
switch cs.tokenBindingType {
|
||||
case MTLSHardBinding:
|
||||
v.Set("transport", "mtls")
|
||||
v.Set("binding-enforcement", "on")
|
||||
case ALTSHardBinding:
|
||||
v.Set("transport", "alts")
|
||||
}
|
||||
tokenURI.RawQuery = v.Encode()
|
||||
}
|
||||
tokenJSON, err := cs.client.GetWithContext(ctx, tokenURI.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
|
||||
}
|
||||
var res metadataTokenResp
|
||||
if err := json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res); err != nil {
|
||||
return nil, fmt.Errorf("credentials: invalid token JSON from metadata: %w", err)
|
||||
}
|
||||
if res.ExpiresInSec == 0 || res.AccessToken == "" {
|
||||
return nil, errors.New("credentials: incomplete token received from metadata")
|
||||
}
|
||||
token := &auth.Token{
|
||||
Value: res.AccessToken,
|
||||
Type: res.TokenType,
|
||||
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
|
||||
Metadata: computeTokenMetadata,
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
471
vendor/cloud.google.com/go/auth/credentials/detect.go
generated
vendored
Normal file
471
vendor/cloud.google.com/go/auth/credentials/detect.go
generated
vendored
Normal file
@@ -0,0 +1,471 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/auth/internal/trustboundary"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// jwtTokenURL is Google's OAuth 2.0 token URL to use with the JWT(2LO) flow.
|
||||
jwtTokenURL = "https://oauth2.googleapis.com/token"
|
||||
|
||||
// Google's OAuth 2.0 default endpoints.
|
||||
googleAuthURL = "https://accounts.google.com/o/oauth2/auth"
|
||||
googleTokenURL = "https://oauth2.googleapis.com/token"
|
||||
|
||||
// GoogleMTLSTokenURL is Google's default OAuth2.0 mTLS endpoint.
|
||||
GoogleMTLSTokenURL = "https://oauth2.mtls.googleapis.com/token"
|
||||
|
||||
// Help on default credentials
|
||||
adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
|
||||
)
|
||||
|
||||
var (
|
||||
// for testing
|
||||
allowOnGCECheck = true
|
||||
)
|
||||
|
||||
// CredType specifies the type of JSON credentials being provided
|
||||
// to a loading function such as [NewCredentialsFromFile] or
|
||||
// [NewCredentialsFromJSON].
|
||||
type CredType string
|
||||
|
||||
const (
|
||||
// ServiceAccount represents a service account file type.
|
||||
ServiceAccount CredType = "service_account"
|
||||
// AuthorizedUser represents a user credentials file type.
|
||||
AuthorizedUser CredType = "authorized_user"
|
||||
// ExternalAccount represents an external account file type.
|
||||
//
|
||||
// IMPORTANT:
|
||||
// This credential type does not validate the credential configuration. A security
|
||||
// risk occurs when a credential configuration configured with malicious urls
|
||||
// is used.
|
||||
// You should validate credential configurations provided by untrusted sources.
|
||||
// See [Security requirements when using credential configurations from an external
|
||||
// source] https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
// for more details.
|
||||
ExternalAccount CredType = "external_account"
|
||||
// ImpersonatedServiceAccount represents an impersonated service account file type.
|
||||
//
|
||||
// IMPORTANT:
|
||||
// This credential type does not validate the credential configuration. A security
|
||||
// risk occurs when a credential configuration configured with malicious urls
|
||||
// is used.
|
||||
// You should validate credential configurations provided by untrusted sources.
|
||||
// See [Security requirements when using credential configurations from an external
|
||||
// source] https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
// for more details.
|
||||
ImpersonatedServiceAccount CredType = "impersonated_service_account"
|
||||
// GDCHServiceAccount represents a GDCH service account credentials.
|
||||
GDCHServiceAccount CredType = "gdch_service_account"
|
||||
// ExternalAccountAuthorizedUser represents an external account authorized user credentials.
|
||||
ExternalAccountAuthorizedUser CredType = "external_account_authorized_user"
|
||||
)
|
||||
|
||||
// TokenBindingType specifies the type of binding used when requesting a token
|
||||
// whether to request a hard-bound token using mTLS or an instance identity
|
||||
// bound token using ALTS.
|
||||
type TokenBindingType int
|
||||
|
||||
const (
|
||||
// NoBinding specifies that requested tokens are not required to have a
|
||||
// binding. This is the default option.
|
||||
NoBinding TokenBindingType = iota
|
||||
// MTLSHardBinding specifies that a hard-bound token should be requested
|
||||
// using an mTLS with S2A channel.
|
||||
MTLSHardBinding
|
||||
// ALTSHardBinding specifies that an instance identity bound token should
|
||||
// be requested using an ALTS channel.
|
||||
ALTSHardBinding
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running in Google Cloud.
|
||||
func OnGCE() bool {
|
||||
// TODO(codyoss): once all libs use this auth lib move metadata check here
|
||||
return allowOnGCECheck && metadata.OnGCE()
|
||||
}
|
||||
|
||||
// DetectDefault searches for "Application Default Credentials" and returns
|
||||
// a credential based on the [DetectOptions] provided.
|
||||
//
|
||||
// It looks for credentials in the following places, preferring the first
|
||||
// location found:
|
||||
//
|
||||
// - A JSON file whose path is specified by the GOOGLE_APPLICATION_CREDENTIALS
|
||||
// environment variable. For workload identity federation, refer to
|
||||
// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation
|
||||
// on how to generate the JSON configuration file for on-prem/non-Google
|
||||
// cloud platforms.
|
||||
// - A JSON file in a location known to the gcloud command-line tool. On
|
||||
// Windows, this is %APPDATA%/gcloud/application_default_credentials.json. On
|
||||
// other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||
// - On Google Compute Engine, Google App Engine standard second generation
|
||||
// runtimes, and Google App Engine flexible environment, it fetches
|
||||
// credentials from the metadata server.
|
||||
//
|
||||
// Important: If you accept a credential configuration (credential
|
||||
// JSON/File/Stream) from an external source for authentication to Google
|
||||
// Cloud Platform, you must validate it before providing it to any Google
|
||||
// API or library. Providing an unvalidated credential configuration to
|
||||
// Google APIs can compromise the security of your systems and data. For
|
||||
// more information, refer to [Validate credential configurations from
|
||||
// external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
||||
func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(opts.CredentialsJSON) > 0 {
|
||||
return readCredentialsFileJSON(opts.CredentialsJSON, opts)
|
||||
}
|
||||
if opts.CredentialsFile != "" {
|
||||
return readCredentialsFile(opts.CredentialsFile, opts)
|
||||
}
|
||||
if filename := os.Getenv(credsfile.GoogleAppCredsEnvVar); filename != "" {
|
||||
creds, err := readCredentialsFile(filename, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
fileName := credsfile.GetWellKnownFileName()
|
||||
if b, err := os.ReadFile(fileName); err == nil {
|
||||
return readCredentialsFileJSON(b, opts)
|
||||
}
|
||||
|
||||
if OnGCE() {
|
||||
metadataClient := metadata.NewWithOptions(&metadata.Options{
|
||||
Logger: opts.logger(),
|
||||
UseDefaultClient: true,
|
||||
})
|
||||
gceUniverseDomainProvider := &internal.ComputeUniverseDomainProvider{
|
||||
MetadataClient: metadataClient,
|
||||
}
|
||||
|
||||
tp := computeTokenProvider(opts, metadataClient)
|
||||
if trustBoundaryEnabled {
|
||||
gceConfigProvider := trustboundary.NewGCEConfigProvider(gceUniverseDomainProvider)
|
||||
var err error
|
||||
tp, err = trustboundary.NewProvider(opts.client(), gceConfigProvider, opts.logger(), tp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to initialize GCE trust boundary provider: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
return auth.NewCredentials(&auth.CredentialsOptions{
|
||||
TokenProvider: tp,
|
||||
ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
|
||||
return metadataClient.ProjectIDWithContext(ctx)
|
||||
}),
|
||||
UniverseDomainProvider: gceUniverseDomainProvider,
|
||||
}), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("credentials: could not find default credentials. See %v for more information", adcSetupURL)
|
||||
}
|
||||
|
||||
// DetectOptions provides configuration for [DetectDefault].
|
||||
type DetectOptions struct {
|
||||
// Scopes that credentials tokens should have. Example:
|
||||
// https://www.googleapis.com/auth/cloud-platform. Required if Audience is
|
||||
// not provided.
|
||||
Scopes []string
|
||||
// TokenBindingType specifies the type of binding used when requesting a
|
||||
// token whether to request a hard-bound token using mTLS or an instance
|
||||
// identity bound token using ALTS. Optional.
|
||||
TokenBindingType TokenBindingType
|
||||
// Audience that credentials tokens should have. Only applicable for 2LO
|
||||
// flows with service accounts. If specified, scopes should not be provided.
|
||||
Audience string
|
||||
// Subject is the user email used for [domain wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
|
||||
// Optional.
|
||||
Subject string
|
||||
// EarlyTokenRefresh configures how early before a token expires that it
|
||||
// should be refreshed. Once the token’s time until expiration has entered
|
||||
// this refresh window the token is considered valid but stale. If unset,
|
||||
// the default value is 3 minutes and 45 seconds. Optional.
|
||||
EarlyTokenRefresh time.Duration
|
||||
// DisableAsyncRefresh configures a synchronous workflow that refreshes
|
||||
// stale tokens while blocking. The default is false. Optional.
|
||||
DisableAsyncRefresh bool
|
||||
// AuthHandlerOptions configures an authorization handler and other options
|
||||
// for 3LO flows. It is required, and only used, for client credential
|
||||
// flows.
|
||||
AuthHandlerOptions *auth.AuthorizationHandlerOptions
|
||||
// TokenURL allows to set the token endpoint for user credential flows. If
|
||||
// unset the default value is: https://oauth2.googleapis.com/token.
|
||||
// Optional.
|
||||
TokenURL string
|
||||
// STSAudience is the audience sent to when retrieving an STS token.
|
||||
// Currently this only used for GDCH auth flow, for which it is required.
|
||||
STSAudience string
|
||||
// CredentialsFile overrides detection logic and sources a credential file
|
||||
// from the provided filepath. If provided, CredentialsJSON must not be.
|
||||
// Optional.
|
||||
//
|
||||
// Deprecated: This field is deprecated because of a potential security risk.
|
||||
// It does not validate the credential configuration. The security risk occurs
|
||||
// when a credential configuration is accepted from a source that is not
|
||||
// under your control and used without validation on your side.
|
||||
//
|
||||
// If you know that you will be loading credential configurations of a
|
||||
// specific type, it is recommended to use a credential-type-specific
|
||||
// NewCredentialsFromFile method. This will ensure that an unexpected
|
||||
// credential type with potential for malicious intent is not loaded
|
||||
// unintentionally. You might still have to do validation for certain
|
||||
// credential types. Please follow the recommendation for that method. For
|
||||
// example, if you want to load only service accounts, you can use
|
||||
//
|
||||
// creds, err := credentials.NewCredentialsFromFile(ctx, credentials.ServiceAccount, filename, opts)
|
||||
//
|
||||
// If you are loading your credential configuration from an untrusted source
|
||||
// and have not mitigated the risks (e.g. by validating the configuration
|
||||
// yourself), make these changes as soon as possible to prevent security
|
||||
// risks to your environment.
|
||||
//
|
||||
// Regardless of the method used, it is always your responsibility to
|
||||
// validate configurations received from external sources.
|
||||
//
|
||||
// For more details see:
|
||||
// https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
CredentialsFile string
|
||||
// CredentialsJSON overrides detection logic and uses the JSON bytes as the
|
||||
// source for the credential. If provided, CredentialsFile must not be.
|
||||
// Optional.
|
||||
//
|
||||
// Deprecated: This field is deprecated because of a potential security risk.
|
||||
// It does not validate the credential configuration. The security risk occurs
|
||||
// when a credential configuration is accepted from a source that is not
|
||||
// under your control and used without validation on your side.
|
||||
//
|
||||
// If you know that you will be loading credential configurations of a
|
||||
// specific type, it is recommended to use a credential-type-specific
|
||||
// NewCredentialsFromJSON method. This will ensure that an unexpected
|
||||
// credential type with potential for malicious intent is not loaded
|
||||
// unintentionally. You might still have to do validation for certain
|
||||
// credential types. Please follow the recommendation for that method. For
|
||||
// example, if you want to load only service accounts, you can use
|
||||
//
|
||||
// creds, err := credentials.NewCredentialsFromJSON(ctx, credentials.ServiceAccount, json, opts)
|
||||
//
|
||||
// If you are loading your credential configuration from an untrusted source
|
||||
// and have not mitigated the risks (e.g. by validating the configuration
|
||||
// yourself), make these changes as soon as possible to prevent security
|
||||
// risks to your environment.
|
||||
//
|
||||
// Regardless of the method used, it is always your responsibility to
|
||||
// validate configurations received from external sources.
|
||||
//
|
||||
// For more details see:
|
||||
// https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
CredentialsJSON []byte
|
||||
// UseSelfSignedJWT directs service account based credentials to create a
|
||||
// self-signed JWT with the private key found in the file, skipping any
|
||||
// network requests that would normally be made. Optional.
|
||||
UseSelfSignedJWT bool
|
||||
// Client configures the underlying client used to make network requests
|
||||
// when fetching tokens. Optional.
|
||||
Client *http.Client
|
||||
// UniverseDomain is the default service domain for a given Cloud universe.
|
||||
// The default value is "googleapis.com". This option is ignored for
|
||||
// authentication flows that do not support universe domain. Optional.
|
||||
UniverseDomain string
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewCredentialsFromFile creates a [cloud.google.com/go/auth.Credentials] from
|
||||
// the provided file. The credType argument specifies the expected credential
|
||||
// type. If the file content does not match the expected type, an error is
|
||||
// returned.
|
||||
//
|
||||
// Important: If you accept a credential configuration (credential
|
||||
// JSON/File/Stream) from an external source for authentication to Google
|
||||
// Cloud Platform, you must validate it before providing it to any Google
|
||||
// API or library. Providing an unvalidated credential configuration to
|
||||
// Google APIs can compromise the security of your systems and data. For
|
||||
// more information, refer to [Validate credential configurations from
|
||||
// external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
||||
func NewCredentialsFromFile(credType CredType, filename string, opts *DetectOptions) (*auth.Credentials, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCredentialsFromJSON(credType, b, opts)
|
||||
}
|
||||
|
||||
// NewCredentialsFromJSON creates a [cloud.google.com/go/auth.Credentials] from
|
||||
// the provided JSON bytes. The credType argument specifies the expected
|
||||
// credential type. If the JSON does not match the expected type, an error is
|
||||
// returned.
|
||||
//
|
||||
// Important: If you accept a credential configuration (credential
|
||||
// JSON/File/Stream) from an external source for authentication to Google
|
||||
// Cloud Platform, you must validate it before providing it to any Google
|
||||
// API or library. Providing an unvalidated credential configuration to
|
||||
// Google APIs can compromise the security of your systems and data. For
|
||||
// more information, refer to [Validate credential configurations from
|
||||
// external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
||||
func NewCredentialsFromJSON(credType CredType, b []byte, opts *DetectOptions) (*auth.Credentials, error) {
|
||||
if err := checkCredentialType(b, credType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We can't use readCredentialsFileJSON because it does auto-detection
|
||||
// for client_credentials.json which we don't support here (no type field).
|
||||
// Instead, we call fileCredentials just as readCredentialsFileJSON does
|
||||
// when it doesn't detect client_credentials.json.
|
||||
return fileCredentials(b, opts)
|
||||
}
|
||||
|
||||
func checkCredentialType(b []byte, expected CredType) error {
|
||||
|
||||
fileType, err := credsfile.ParseFileType(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if CredType(fileType) != expected {
|
||||
return fmt.Errorf("credentials: expected type %q, found %q", expected, fileType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DetectOptions) validate() error {
|
||||
if o == nil {
|
||||
return errors.New("credentials: options must be provided")
|
||||
}
|
||||
if len(o.Scopes) > 0 && o.Audience != "" {
|
||||
return errors.New("credentials: both scopes and audience were provided")
|
||||
}
|
||||
if len(o.CredentialsJSON) > 0 && o.CredentialsFile != "" {
|
||||
return errors.New("credentials: both credentials file and JSON were provided")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DetectOptions) tokenURL() string {
|
||||
if o.TokenURL != "" {
|
||||
return o.TokenURL
|
||||
}
|
||||
return googleTokenURL
|
||||
}
|
||||
|
||||
func (o *DetectOptions) scopes() []string {
|
||||
scopes := make([]string, len(o.Scopes))
|
||||
copy(scopes, o.Scopes)
|
||||
return scopes
|
||||
}
|
||||
|
||||
func (o *DetectOptions) client() *http.Client {
|
||||
if o.Client != nil {
|
||||
return o.Client
|
||||
}
|
||||
return internal.DefaultClient()
|
||||
}
|
||||
|
||||
func (o *DetectOptions) logger() *slog.Logger {
|
||||
return internallog.New(o.Logger)
|
||||
}
|
||||
|
||||
func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return readCredentialsFileJSON(b, opts)
|
||||
}
|
||||
|
||||
func readCredentialsFileJSON(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
|
||||
// attempt to parse jsonData as a Google Developers Console client_credentials.json.
|
||||
config := clientCredConfigFromJSON(b, opts)
|
||||
if config != nil {
|
||||
if config.AuthHandlerOpts == nil {
|
||||
return nil, errors.New("credentials: auth handler must be specified for this credential filetype")
|
||||
}
|
||||
tp, err := auth.New3LOTokenProvider(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auth.NewCredentials(&auth.CredentialsOptions{
|
||||
TokenProvider: tp,
|
||||
JSON: b,
|
||||
}), nil
|
||||
}
|
||||
return fileCredentials(b, opts)
|
||||
}
|
||||
|
||||
func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
|
||||
var creds credsfile.ClientCredentialsFile
|
||||
var c *credsfile.Config3LO
|
||||
if err := json.Unmarshal(b, &creds); err != nil {
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case creds.Web != nil:
|
||||
c = creds.Web
|
||||
case creds.Installed != nil:
|
||||
c = creds.Installed
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if len(c.RedirectURIs) < 1 {
|
||||
return nil
|
||||
}
|
||||
var handleOpts *auth.AuthorizationHandlerOptions
|
||||
if opts.AuthHandlerOptions != nil {
|
||||
handleOpts = &auth.AuthorizationHandlerOptions{
|
||||
Handler: opts.AuthHandlerOptions.Handler,
|
||||
State: opts.AuthHandlerOptions.State,
|
||||
PKCEOpts: opts.AuthHandlerOptions.PKCEOpts,
|
||||
}
|
||||
}
|
||||
return &auth.Options3LO{
|
||||
ClientID: c.ClientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
RedirectURL: c.RedirectURIs[0],
|
||||
Scopes: opts.scopes(),
|
||||
AuthURL: c.AuthURI,
|
||||
TokenURL: c.TokenURI,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
EarlyTokenExpiry: opts.EarlyTokenRefresh,
|
||||
AuthHandlerOpts: handleOpts,
|
||||
// TODO(codyoss): refactor this out. We need to add in auto-detection
|
||||
// for this use case.
|
||||
AuthStyle: auth.StyleInParams,
|
||||
}
|
||||
}
|
||||
45
vendor/cloud.google.com/go/auth/credentials/doc.go
generated
vendored
Normal file
45
vendor/cloud.google.com/go/auth/credentials/doc.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package credentials provides support for making OAuth2 authorized and
|
||||
// authenticated HTTP requests to Google APIs. It supports the Web server flow,
|
||||
// client-side credentials, service accounts, Google Compute Engine service
|
||||
// accounts, Google App Engine service accounts and workload identity federation
|
||||
// from non-Google cloud platforms.
|
||||
//
|
||||
// A brief overview of the package follows. For more information, please read
|
||||
// https://developers.google.com/accounts/docs/OAuth2
|
||||
// and
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials.
|
||||
// For more information on using workload identity federation, refer to
|
||||
// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation.
|
||||
//
|
||||
// # Credentials
|
||||
//
|
||||
// The [cloud.google.com/go/auth.Credentials] type represents Google
|
||||
// credentials, including Application Default Credentials.
|
||||
//
|
||||
// Use [DetectDefault] to obtain Application Default Credentials.
|
||||
//
|
||||
// Application Default Credentials support workload identity federation to
|
||||
// access Google Cloud resources from non-Google Cloud platforms including Amazon
|
||||
// Web Services (AWS), Microsoft Azure or any identity provider that supports
|
||||
// OpenID Connect (OIDC). Workload identity federation is recommended for
|
||||
// non-Google Cloud environments as it avoids the need to download, manage, and
|
||||
// store service account private keys locally.
|
||||
//
|
||||
// # Workforce Identity Federation
|
||||
//
|
||||
// For more information on this feature see [cloud.google.com/go/auth/credentials/externalaccount].
|
||||
package credentials
|
||||
329
vendor/cloud.google.com/go/auth/credentials/filetypes.go
generated
vendored
Normal file
329
vendor/cloud.google.com/go/auth/credentials/filetypes.go
generated
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/credentials/internal/externalaccount"
|
||||
"cloud.google.com/go/auth/credentials/internal/externalaccountuser"
|
||||
"cloud.google.com/go/auth/credentials/internal/gdch"
|
||||
"cloud.google.com/go/auth/credentials/internal/impersonate"
|
||||
internalauth "cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/auth/internal/trustboundary"
|
||||
)
|
||||
|
||||
const cloudPlatformScope = "https://www.googleapis.com/auth/cloud-platform"
|
||||
|
||||
func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
|
||||
fileType, err := credsfile.ParseFileType(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fileType == "" {
|
||||
return nil, errors.New("credentials: unsupported unidentified file type")
|
||||
}
|
||||
|
||||
var projectID, universeDomain string
|
||||
var tp auth.TokenProvider
|
||||
switch CredType(fileType) {
|
||||
case ServiceAccount:
|
||||
f, err := credsfile.ParseServiceAccount(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleServiceAccount(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectID = f.ProjectID
|
||||
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
case AuthorizedUser:
|
||||
f, err := credsfile.ParseUserCredentials(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleUserCredential(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
universeDomain = f.UniverseDomain
|
||||
case ExternalAccount:
|
||||
f, err := credsfile.ParseExternalAccount(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleExternalAccount(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
case ExternalAccountAuthorizedUser:
|
||||
f, err := credsfile.ParseExternalAccountAuthorizedUser(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleExternalAccountAuthorizedUser(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
universeDomain = f.UniverseDomain
|
||||
case ImpersonatedServiceAccount:
|
||||
f, err := credsfile.ParseImpersonatedServiceAccount(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleImpersonatedServiceAccount(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
case GDCHServiceAccount:
|
||||
f, err := credsfile.ParseGDCHServiceAccount(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleGDCHServiceAccount(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectID = f.Project
|
||||
universeDomain = f.UniverseDomain
|
||||
default:
|
||||
return nil, fmt.Errorf("credentials: unsupported filetype %q", fileType)
|
||||
}
|
||||
return auth.NewCredentials(&auth.CredentialsOptions{
|
||||
TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{
|
||||
ExpireEarly: opts.EarlyTokenRefresh,
|
||||
}),
|
||||
JSON: b,
|
||||
ProjectIDProvider: internalauth.StaticCredentialsProperty(projectID),
|
||||
// TODO(codyoss): only set quota project here if there was a user override
|
||||
UniverseDomainProvider: internalauth.StaticCredentialsProperty(universeDomain),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// resolveUniverseDomain returns optsUniverseDomain if non-empty, in order to
|
||||
// support configuring universe-specific credentials in code. Auth flows
|
||||
// unsupported for universe domain should not use this func, but should instead
|
||||
// simply set the file universe domain on the credentials.
|
||||
func resolveUniverseDomain(optsUniverseDomain, fileUniverseDomain string) string {
|
||||
if optsUniverseDomain != "" {
|
||||
return optsUniverseDomain
|
||||
}
|
||||
return fileUniverseDomain
|
||||
}
|
||||
|
||||
func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
if opts.UseSelfSignedJWT {
|
||||
return configureSelfSignedJWT(f, opts)
|
||||
} else if ud != "" && ud != internalauth.DefaultUniverseDomain {
|
||||
// For non-GDU universe domains, token exchange is impossible and services
|
||||
// must support self-signed JWTs.
|
||||
opts.UseSelfSignedJWT = true
|
||||
return configureSelfSignedJWT(f, opts)
|
||||
}
|
||||
opts2LO := &auth.Options2LO{
|
||||
Email: f.ClientEmail,
|
||||
PrivateKey: []byte(f.PrivateKey),
|
||||
PrivateKeyID: f.PrivateKeyID,
|
||||
Scopes: opts.scopes(),
|
||||
TokenURL: f.TokenURL,
|
||||
Subject: opts.Subject,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
UniverseDomain: ud,
|
||||
}
|
||||
if opts2LO.TokenURL == "" {
|
||||
opts2LO.TokenURL = jwtTokenURL
|
||||
}
|
||||
|
||||
tp, err := auth.New2LOTokenProvider(opts2LO)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !trustBoundaryEnabled {
|
||||
return tp, nil
|
||||
}
|
||||
saConfig := trustboundary.NewServiceAccountConfigProvider(opts2LO.Email, opts2LO.UniverseDomain)
|
||||
return trustboundary.NewProvider(opts.client(), saConfig, opts.logger(), tp)
|
||||
}
|
||||
|
||||
func handleUserCredential(f *credsfile.UserCredentialsFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
opts3LO := &auth.Options3LO{
|
||||
ClientID: f.ClientID,
|
||||
ClientSecret: f.ClientSecret,
|
||||
Scopes: opts.scopes(),
|
||||
AuthURL: googleAuthURL,
|
||||
TokenURL: opts.tokenURL(),
|
||||
AuthStyle: auth.StyleInParams,
|
||||
EarlyTokenExpiry: opts.EarlyTokenRefresh,
|
||||
RefreshToken: f.RefreshToken,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
}
|
||||
return auth.New3LOTokenProvider(opts3LO)
|
||||
}
|
||||
|
||||
func handleExternalAccount(f *credsfile.ExternalAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
externalOpts := &externalaccount.Options{
|
||||
Audience: f.Audience,
|
||||
SubjectTokenType: f.SubjectTokenType,
|
||||
TokenURL: f.TokenURL,
|
||||
TokenInfoURL: f.TokenInfoURL,
|
||||
ServiceAccountImpersonationURL: f.ServiceAccountImpersonationURL,
|
||||
ClientSecret: f.ClientSecret,
|
||||
ClientID: f.ClientID,
|
||||
CredentialSource: f.CredentialSource,
|
||||
QuotaProjectID: f.QuotaProjectID,
|
||||
Scopes: opts.scopes(),
|
||||
WorkforcePoolUserProject: f.WorkforcePoolUserProject,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
IsDefaultClient: opts.Client == nil,
|
||||
}
|
||||
if f.ServiceAccountImpersonation != nil {
|
||||
externalOpts.ServiceAccountImpersonationLifetimeSeconds = f.ServiceAccountImpersonation.TokenLifetimeSeconds
|
||||
}
|
||||
tp, err := externalaccount.NewTokenProvider(externalOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !trustBoundaryEnabled {
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
var configProvider trustboundary.ConfigProvider
|
||||
|
||||
if f.ServiceAccountImpersonationURL == "" {
|
||||
// No impersonation, this is a direct external account credential.
|
||||
// The trust boundary is based on the workload/workforce pool.
|
||||
var err error
|
||||
configProvider, err = trustboundary.NewExternalAccountConfigProvider(f.Audience, ud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Impersonation is used. The trust boundary is based on the target service account.
|
||||
targetSAEmail, err := impersonate.ExtractServiceAccountEmail(f.ServiceAccountImpersonationURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: could not extract target service account email for trust boundary: %w", err)
|
||||
}
|
||||
configProvider = trustboundary.NewServiceAccountConfigProvider(targetSAEmail, ud)
|
||||
}
|
||||
|
||||
return trustboundary.NewProvider(opts.client(), configProvider, opts.logger(), tp)
|
||||
}
|
||||
|
||||
func handleExternalAccountAuthorizedUser(f *credsfile.ExternalAccountAuthorizedUserFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
externalOpts := &externalaccountuser.Options{
|
||||
Audience: f.Audience,
|
||||
RefreshToken: f.RefreshToken,
|
||||
TokenURL: f.TokenURL,
|
||||
TokenInfoURL: f.TokenInfoURL,
|
||||
ClientID: f.ClientID,
|
||||
ClientSecret: f.ClientSecret,
|
||||
Scopes: opts.scopes(),
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
}
|
||||
tp, err := externalaccountuser.NewTokenProvider(externalOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !trustBoundaryEnabled {
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
configProvider, err := trustboundary.NewExternalAccountConfigProvider(f.Audience, ud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return trustboundary.NewProvider(opts.client(), configProvider, opts.logger(), tp)
|
||||
}
|
||||
|
||||
func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
if f.ServiceAccountImpersonationURL == "" || f.CredSource == nil {
|
||||
return nil, errors.New("missing 'source_credentials' field or 'service_account_impersonation_url' in credentials")
|
||||
}
|
||||
|
||||
sourceOpts := *opts
|
||||
|
||||
// Source credential needs IAM or Cloud Platform scope to call the
|
||||
// iamcredentials endpoint. The scopes provided by the user are for the
|
||||
// impersonated credentials.
|
||||
sourceOpts.Scopes = []string{cloudPlatformScope}
|
||||
sourceTP, err := fileCredentials(f.CredSource, &sourceOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
scopes := opts.scopes()
|
||||
if len(scopes) == 0 {
|
||||
scopes = f.Scopes
|
||||
}
|
||||
impOpts := &impersonate.Options{
|
||||
URL: f.ServiceAccountImpersonationURL,
|
||||
Scopes: scopes,
|
||||
Tp: sourceTP,
|
||||
Delegates: f.Delegates,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
UniverseDomain: ud,
|
||||
}
|
||||
tp, err := impersonate.NewTokenProvider(impOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !trustBoundaryEnabled {
|
||||
return tp, nil
|
||||
}
|
||||
targetSAEmail, err := impersonate.ExtractServiceAccountEmail(f.ServiceAccountImpersonationURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: could not extract target service account email for trust boundary: %w", err)
|
||||
}
|
||||
targetSAConfig := trustboundary.NewServiceAccountConfigProvider(targetSAEmail, ud)
|
||||
return trustboundary.NewProvider(opts.client(), targetSAConfig, opts.logger(), tp)
|
||||
}
|
||||
func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
return gdch.NewTokenProvider(f, &gdch.Options{
|
||||
STSAudience: opts.STSAudience,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
})
|
||||
}
|
||||
531
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go
generated
vendored
Normal file
531
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go
generated
vendored
Normal file
@@ -0,0 +1,531 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
var (
|
||||
// getenv aliases os.Getenv for testing
|
||||
getenv = os.Getenv
|
||||
)
|
||||
|
||||
const (
|
||||
// AWS Signature Version 4 signing algorithm identifier.
|
||||
awsAlgorithm = "AWS4-HMAC-SHA256"
|
||||
|
||||
// The termination string for the AWS credential scope value as defined in
|
||||
// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||
awsRequestType = "aws4_request"
|
||||
|
||||
// The AWS authorization header name for the security session token if available.
|
||||
awsSecurityTokenHeader = "x-amz-security-token"
|
||||
|
||||
// The name of the header containing the session token for metadata endpoint calls
|
||||
awsIMDSv2SessionTokenHeader = "X-aws-ec2-metadata-token"
|
||||
|
||||
awsIMDSv2SessionTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
|
||||
|
||||
awsIMDSv2SessionTTL = "300"
|
||||
|
||||
// The AWS authorization header name for the auto-generated date.
|
||||
awsDateHeader = "x-amz-date"
|
||||
|
||||
defaultRegionalCredentialVerificationURL = "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
|
||||
|
||||
// Supported AWS configuration environment variables.
|
||||
awsAccessKeyIDEnvVar = "AWS_ACCESS_KEY_ID"
|
||||
awsDefaultRegionEnvVar = "AWS_DEFAULT_REGION"
|
||||
awsRegionEnvVar = "AWS_REGION"
|
||||
awsSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
|
||||
awsSessionTokenEnvVar = "AWS_SESSION_TOKEN"
|
||||
|
||||
awsTimeFormatLong = "20060102T150405Z"
|
||||
awsTimeFormatShort = "20060102"
|
||||
awsProviderType = "aws"
|
||||
)
|
||||
|
||||
type awsSubjectProvider struct {
|
||||
EnvironmentID string
|
||||
RegionURL string
|
||||
RegionalCredVerificationURL string
|
||||
CredVerificationURL string
|
||||
IMDSv2SessionTokenURL string
|
||||
TargetResource string
|
||||
requestSigner *awsRequestSigner
|
||||
region string
|
||||
securityCredentialsProvider AwsSecurityCredentialsProvider
|
||||
reqOpts *RequestOptions
|
||||
|
||||
Client *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error) {
|
||||
// Set Defaults
|
||||
if sp.RegionalCredVerificationURL == "" {
|
||||
sp.RegionalCredVerificationURL = defaultRegionalCredentialVerificationURL
|
||||
}
|
||||
headers := make(map[string]string)
|
||||
if sp.shouldUseMetadataServer() {
|
||||
awsSessionToken, err := sp.getAWSSessionToken(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if awsSessionToken != "" {
|
||||
headers[awsIMDSv2SessionTokenHeader] = awsSessionToken
|
||||
}
|
||||
}
|
||||
|
||||
awsSecurityCredentials, err := sp.getSecurityCredentials(ctx, headers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if sp.region, err = sp.getRegion(ctx, headers); err != nil {
|
||||
return "", err
|
||||
}
|
||||
sp.requestSigner = &awsRequestSigner{
|
||||
RegionName: sp.region,
|
||||
AwsSecurityCredentials: awsSecurityCredentials,
|
||||
}
|
||||
|
||||
// Generate the signed request to AWS STS GetCallerIdentity API.
|
||||
// Use the required regional endpoint. Otherwise, the request will fail.
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", strings.Replace(sp.RegionalCredVerificationURL, "{region}", sp.region, 1), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// The full, canonical resource name of the workload identity pool
|
||||
// provider, with or without the HTTPS prefix.
|
||||
// Including this header as part of the signature is recommended to
|
||||
// ensure data integrity.
|
||||
if sp.TargetResource != "" {
|
||||
req.Header.Set("x-goog-cloud-target-resource", sp.TargetResource)
|
||||
}
|
||||
sp.requestSigner.signRequest(req)
|
||||
|
||||
/*
|
||||
The GCP STS endpoint expects the headers to be formatted as:
|
||||
# [
|
||||
# {key: 'x-amz-date', value: '...'},
|
||||
# {key: 'Authorization', value: '...'},
|
||||
# ...
|
||||
# ]
|
||||
# And then serialized as:
|
||||
# quote(json.dumps({
|
||||
# url: '...',
|
||||
# method: 'POST',
|
||||
# headers: [{key: 'x-amz-date', value: '...'}, ...]
|
||||
# }))
|
||||
*/
|
||||
|
||||
awsSignedReq := awsRequest{
|
||||
URL: req.URL.String(),
|
||||
Method: "POST",
|
||||
}
|
||||
for headerKey, headerList := range req.Header {
|
||||
for _, headerValue := range headerList {
|
||||
awsSignedReq.Headers = append(awsSignedReq.Headers, awsRequestHeader{
|
||||
Key: headerKey,
|
||||
Value: headerValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
sort.Slice(awsSignedReq.Headers, func(i, j int) bool {
|
||||
headerCompare := strings.Compare(awsSignedReq.Headers[i].Key, awsSignedReq.Headers[j].Key)
|
||||
if headerCompare == 0 {
|
||||
return strings.Compare(awsSignedReq.Headers[i].Value, awsSignedReq.Headers[j].Value) < 0
|
||||
}
|
||||
return headerCompare < 0
|
||||
})
|
||||
|
||||
result, err := json.Marshal(awsSignedReq)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return url.QueryEscape(string(result)), nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) providerType() string {
|
||||
if sp.securityCredentialsProvider != nil {
|
||||
return programmaticProviderType
|
||||
}
|
||||
return awsProviderType
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getAWSSessionToken(ctx context.Context) (string, error) {
|
||||
if sp.IMDSv2SessionTokenURL == "" {
|
||||
return "", nil
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "PUT", sp.IMDSv2SessionTokenURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set(awsIMDSv2SessionTTLHeader, awsIMDSv2SessionTTL)
|
||||
|
||||
sp.logger.DebugContext(ctx, "aws session token request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws session token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("credentials: unable to retrieve AWS session token: %s", body)
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]string) (string, error) {
|
||||
if sp.securityCredentialsProvider != nil {
|
||||
return sp.securityCredentialsProvider.AwsRegion(ctx, sp.reqOpts)
|
||||
}
|
||||
if canRetrieveRegionFromEnvironment() {
|
||||
if envAwsRegion := getenv(awsRegionEnvVar); envAwsRegion != "" {
|
||||
return envAwsRegion, nil
|
||||
}
|
||||
return getenv(awsDefaultRegionEnvVar), nil
|
||||
}
|
||||
|
||||
if sp.RegionURL == "" {
|
||||
return "", errors.New("credentials: unable to determine AWS region")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", sp.RegionURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for name, value := range headers {
|
||||
req.Header.Add(name, value)
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws region request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws region response", "response", internallog.HTTPResponse(resp, body))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("credentials: unable to retrieve AWS region - %s", body)
|
||||
}
|
||||
|
||||
// This endpoint will return the region in format: us-east-2b.
|
||||
// Only the us-east-2 part should be used.
|
||||
bodyLen := len(body)
|
||||
if bodyLen == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return string(body[:bodyLen-1]), nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, headers map[string]string) (result *AwsSecurityCredentials, err error) {
|
||||
if sp.securityCredentialsProvider != nil {
|
||||
return sp.securityCredentialsProvider.AwsSecurityCredentials(ctx, sp.reqOpts)
|
||||
}
|
||||
if canRetrieveSecurityCredentialFromEnvironment() {
|
||||
return &AwsSecurityCredentials{
|
||||
AccessKeyID: getenv(awsAccessKeyIDEnvVar),
|
||||
SecretAccessKey: getenv(awsSecretAccessKeyEnvVar),
|
||||
SessionToken: getenv(awsSessionTokenEnvVar),
|
||||
}, nil
|
||||
}
|
||||
|
||||
roleName, err := sp.getMetadataRoleName(ctx, headers)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
credentials, err := sp.getMetadataSecurityCredentials(ctx, roleName, headers)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if credentials.AccessKeyID == "" {
|
||||
return result, errors.New("credentials: missing AccessKeyId credential")
|
||||
}
|
||||
if credentials.SecretAccessKey == "" {
|
||||
return result, errors.New("credentials: missing SecretAccessKey credential")
|
||||
}
|
||||
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context, roleName string, headers map[string]string) (*AwsSecurityCredentials, error) {
|
||||
var result *AwsSecurityCredentials
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s", sp.CredVerificationURL, roleName), nil)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
for name, value := range headers {
|
||||
req.Header.Add(name, value)
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws security credential request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws security credential response", "response", internallog.HTTPResponse(resp, body))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return result, fmt.Errorf("credentials: unable to retrieve AWS security credentials - %s", body)
|
||||
}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getMetadataRoleName(ctx context.Context, headers map[string]string) (string, error) {
|
||||
if sp.CredVerificationURL == "" {
|
||||
return "", errors.New("credentials: unable to determine the AWS metadata server security credentials endpoint")
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", sp.CredVerificationURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for name, value := range headers {
|
||||
req.Header.Add(name, value)
|
||||
}
|
||||
|
||||
sp.logger.DebugContext(ctx, "aws metadata role request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws metadata role response", "response", internallog.HTTPResponse(resp, body))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("credentials: unable to retrieve AWS role name - %s", body)
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
// awsRequestSigner is a utility class to sign http requests using a AWS V4 signature.
|
||||
type awsRequestSigner struct {
|
||||
RegionName string
|
||||
AwsSecurityCredentials *AwsSecurityCredentials
|
||||
}
|
||||
|
||||
// signRequest adds the appropriate headers to an http.Request
|
||||
// or returns an error if something prevented this.
|
||||
func (rs *awsRequestSigner) signRequest(req *http.Request) error {
|
||||
// req is assumed non-nil
|
||||
signedRequest := cloneRequest(req)
|
||||
timestamp := Now()
|
||||
signedRequest.Header.Set("host", requestHost(req))
|
||||
if rs.AwsSecurityCredentials.SessionToken != "" {
|
||||
signedRequest.Header.Set(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SessionToken)
|
||||
}
|
||||
if signedRequest.Header.Get("date") == "" {
|
||||
signedRequest.Header.Set(awsDateHeader, timestamp.Format(awsTimeFormatLong))
|
||||
}
|
||||
authorizationCode, err := rs.generateAuthentication(signedRequest, timestamp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedRequest.Header.Set("Authorization", authorizationCode)
|
||||
req.Header = signedRequest.Header
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, error) {
|
||||
canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req)
|
||||
dateStamp := timestamp.Format(awsTimeFormatShort)
|
||||
serviceName := ""
|
||||
|
||||
if splitHost := strings.Split(requestHost(req), "."); len(splitHost) > 0 {
|
||||
serviceName = splitHost[0]
|
||||
}
|
||||
credentialScope := strings.Join([]string{dateStamp, rs.RegionName, serviceName, awsRequestType}, "/")
|
||||
requestString, err := canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
requestHash, err := getSha256([]byte(requestString))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stringToSign := strings.Join([]string{awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, requestHash}, "\n")
|
||||
signingKey := []byte("AWS4" + rs.AwsSecurityCredentials.SecretAccessKey)
|
||||
for _, signingInput := range []string{
|
||||
dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign,
|
||||
} {
|
||||
signingKey, err = getHmacSha256(signingKey, []byte(signingInput))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyID, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil
|
||||
}
|
||||
|
||||
func getSha256(input []byte) (string, error) {
|
||||
hash := sha256.New()
|
||||
if _, err := hash.Write(input); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func getHmacSha256(key, input []byte) ([]byte, error) {
|
||||
hash := hmac.New(sha256.New, key)
|
||||
if _, err := hash.Write(input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hash.Sum(nil), nil
|
||||
}
|
||||
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
if r.Header != nil {
|
||||
r2.Header = make(http.Header, len(r.Header))
|
||||
|
||||
// Find total number of values.
|
||||
headerCount := 0
|
||||
for _, headerValues := range r.Header {
|
||||
headerCount += len(headerValues)
|
||||
}
|
||||
copiedHeaders := make([]string, headerCount) // shared backing array for headers' values
|
||||
|
||||
for headerKey, headerValues := range r.Header {
|
||||
headerCount = copy(copiedHeaders, headerValues)
|
||||
r2.Header[headerKey] = copiedHeaders[:headerCount:headerCount]
|
||||
copiedHeaders = copiedHeaders[headerCount:]
|
||||
}
|
||||
}
|
||||
return r2
|
||||
}
|
||||
|
||||
func canonicalPath(req *http.Request) string {
|
||||
result := req.URL.EscapedPath()
|
||||
if result == "" {
|
||||
return "/"
|
||||
}
|
||||
return path.Clean(result)
|
||||
}
|
||||
|
||||
func canonicalQuery(req *http.Request) string {
|
||||
queryValues := req.URL.Query()
|
||||
for queryKey := range queryValues {
|
||||
sort.Strings(queryValues[queryKey])
|
||||
}
|
||||
return queryValues.Encode()
|
||||
}
|
||||
|
||||
func canonicalHeaders(req *http.Request) (string, string) {
|
||||
// Header keys need to be sorted alphabetically.
|
||||
var headers []string
|
||||
lowerCaseHeaders := make(http.Header)
|
||||
for k, v := range req.Header {
|
||||
k := strings.ToLower(k)
|
||||
if _, ok := lowerCaseHeaders[k]; ok {
|
||||
// include additional values
|
||||
lowerCaseHeaders[k] = append(lowerCaseHeaders[k], v...)
|
||||
} else {
|
||||
headers = append(headers, k)
|
||||
lowerCaseHeaders[k] = v
|
||||
}
|
||||
}
|
||||
sort.Strings(headers)
|
||||
|
||||
var fullHeaders bytes.Buffer
|
||||
for _, header := range headers {
|
||||
headerValue := strings.Join(lowerCaseHeaders[header], ",")
|
||||
fullHeaders.WriteString(header)
|
||||
fullHeaders.WriteRune(':')
|
||||
fullHeaders.WriteString(headerValue)
|
||||
fullHeaders.WriteRune('\n')
|
||||
}
|
||||
|
||||
return strings.Join(headers, ";"), fullHeaders.String()
|
||||
}
|
||||
|
||||
func requestDataHash(req *http.Request) (string, error) {
|
||||
var requestData []byte
|
||||
if req.Body != nil {
|
||||
requestBody, err := req.GetBody()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer requestBody.Close()
|
||||
|
||||
requestData, err = internal.ReadAll(requestBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return getSha256(requestData)
|
||||
}
|
||||
|
||||
func requestHost(req *http.Request) string {
|
||||
if req.Host != "" {
|
||||
return req.Host
|
||||
}
|
||||
return req.URL.Host
|
||||
}
|
||||
|
||||
func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) (string, error) {
|
||||
dataHash, err := requestDataHash(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", req.Method, canonicalPath(req), canonicalQuery(req), canonicalHeaderData, canonicalHeaderColumns, dataHash), nil
|
||||
}
|
||||
|
||||
type awsRequestHeader struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type awsRequest struct {
|
||||
URL string `json:"url"`
|
||||
Method string `json:"method"`
|
||||
Headers []awsRequestHeader `json:"headers"`
|
||||
}
|
||||
|
||||
// The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
|
||||
// required.
|
||||
func canRetrieveRegionFromEnvironment() bool {
|
||||
return getenv(awsRegionEnvVar) != "" || getenv(awsDefaultRegionEnvVar) != ""
|
||||
}
|
||||
|
||||
// Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
|
||||
func canRetrieveSecurityCredentialFromEnvironment() bool {
|
||||
return getenv(awsAccessKeyIDEnvVar) != "" && getenv(awsSecretAccessKeyEnvVar) != ""
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) shouldUseMetadataServer() bool {
|
||||
return sp.securityCredentialsProvider == nil && (!canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment())
|
||||
}
|
||||
284
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go
generated
vendored
Normal file
284
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
executableSupportedMaxVersion = 1
|
||||
executableDefaultTimeout = 30 * time.Second
|
||||
executableSource = "response"
|
||||
executableProviderType = "executable"
|
||||
outputFileSource = "output file"
|
||||
|
||||
allowExecutablesEnvVar = "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES"
|
||||
|
||||
jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
|
||||
idTokenType = "urn:ietf:params:oauth:token-type:id_token"
|
||||
saml2TokenType = "urn:ietf:params:oauth:token-type:saml2"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceAccountImpersonationRE = regexp.MustCompile(`https://iamcredentials..+/v1/projects/-/serviceAccounts/(.*@.*):generateAccessToken`)
|
||||
)
|
||||
|
||||
type nonCacheableError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (nce nonCacheableError) Error() string {
|
||||
return nce.message
|
||||
}
|
||||
|
||||
// environment is a contract for testing
|
||||
type environment interface {
|
||||
existingEnv() []string
|
||||
getenv(string) string
|
||||
run(ctx context.Context, command string, env []string) ([]byte, error)
|
||||
now() time.Time
|
||||
}
|
||||
|
||||
type runtimeEnvironment struct{}
|
||||
|
||||
func (r runtimeEnvironment) existingEnv() []string {
|
||||
return os.Environ()
|
||||
}
|
||||
func (r runtimeEnvironment) getenv(key string) string {
|
||||
return os.Getenv(key)
|
||||
}
|
||||
func (r runtimeEnvironment) now() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
|
||||
func (r runtimeEnvironment) run(ctx context.Context, command string, env []string) ([]byte, error) {
|
||||
splitCommand := strings.Fields(command)
|
||||
cmd := exec.CommandContext(ctx, splitCommand[0], splitCommand[1:]...)
|
||||
cmd.Env = env
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, context.DeadlineExceeded
|
||||
}
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
return nil, exitCodeError(exitError)
|
||||
}
|
||||
return nil, executableError(err)
|
||||
}
|
||||
|
||||
bytesStdout := bytes.TrimSpace(stdout.Bytes())
|
||||
if len(bytesStdout) > 0 {
|
||||
return bytesStdout, nil
|
||||
}
|
||||
return bytes.TrimSpace(stderr.Bytes()), nil
|
||||
}
|
||||
|
||||
type executableSubjectProvider struct {
|
||||
Command string
|
||||
Timeout time.Duration
|
||||
OutputFile string
|
||||
client *http.Client
|
||||
opts *Options
|
||||
env environment
|
||||
}
|
||||
|
||||
type executableResponse struct {
|
||||
Version int `json:"version,omitempty"`
|
||||
Success *bool `json:"success,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
ExpirationTime int64 `json:"expiration_time,omitempty"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
SamlResponse string `json:"saml_response,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) parseSubjectTokenFromSource(response []byte, source string, now int64) (string, error) {
|
||||
var result executableResponse
|
||||
if err := json.Unmarshal(response, &result); err != nil {
|
||||
return "", jsonParsingError(source, string(response))
|
||||
}
|
||||
// Validate
|
||||
if result.Version == 0 {
|
||||
return "", missingFieldError(source, "version")
|
||||
}
|
||||
if result.Success == nil {
|
||||
return "", missingFieldError(source, "success")
|
||||
}
|
||||
if !*result.Success {
|
||||
if result.Code == "" || result.Message == "" {
|
||||
return "", malformedFailureError()
|
||||
}
|
||||
return "", userDefinedError(result.Code, result.Message)
|
||||
}
|
||||
if result.Version > executableSupportedMaxVersion || result.Version < 0 {
|
||||
return "", unsupportedVersionError(source, result.Version)
|
||||
}
|
||||
if result.ExpirationTime == 0 && sp.OutputFile != "" {
|
||||
return "", missingFieldError(source, "expiration_time")
|
||||
}
|
||||
if result.TokenType == "" {
|
||||
return "", missingFieldError(source, "token_type")
|
||||
}
|
||||
if result.ExpirationTime != 0 && result.ExpirationTime < now {
|
||||
return "", tokenExpiredError()
|
||||
}
|
||||
|
||||
switch result.TokenType {
|
||||
case jwtTokenType, idTokenType:
|
||||
if result.IDToken == "" {
|
||||
return "", missingFieldError(source, "id_token")
|
||||
}
|
||||
return result.IDToken, nil
|
||||
case saml2TokenType:
|
||||
if result.SamlResponse == "" {
|
||||
return "", missingFieldError(source, "saml_response")
|
||||
}
|
||||
return result.SamlResponse, nil
|
||||
default:
|
||||
return "", tokenTypeError(source)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) subjectToken(ctx context.Context) (string, error) {
|
||||
if token, err := sp.getTokenFromOutputFile(); token != "" || err != nil {
|
||||
return token, err
|
||||
}
|
||||
return sp.getTokenFromExecutableCommand(ctx)
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) providerType() string {
|
||||
return executableProviderType
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) getTokenFromOutputFile() (token string, err error) {
|
||||
if sp.OutputFile == "" {
|
||||
// This ExecutableCredentialSource doesn't use an OutputFile.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
file, err := os.Open(sp.OutputFile)
|
||||
if err != nil {
|
||||
// No OutputFile found. Hasn't been created yet, so skip it.
|
||||
return "", nil
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := internal.ReadAll(file)
|
||||
if err != nil || len(data) == 0 {
|
||||
// Cachefile exists, but no data found. Get new credential.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
token, err = sp.parseSubjectTokenFromSource(data, outputFileSource, sp.env.now().Unix())
|
||||
if err != nil {
|
||||
if _, ok := err.(nonCacheableError); ok {
|
||||
// If the cached token is expired we need a new token,
|
||||
// and if the cache contains a failure, we need to try again.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// There was an error in the cached token, and the developer should be aware of it.
|
||||
return "", err
|
||||
}
|
||||
// Token parsing succeeded. Use found token.
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) executableEnvironment() []string {
|
||||
result := sp.env.existingEnv()
|
||||
result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE=%v", sp.opts.Audience))
|
||||
result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE=%v", sp.opts.SubjectTokenType))
|
||||
result = append(result, "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE=0")
|
||||
if sp.opts.ServiceAccountImpersonationURL != "" {
|
||||
matches := serviceAccountImpersonationRE.FindStringSubmatch(sp.opts.ServiceAccountImpersonationURL)
|
||||
if matches != nil {
|
||||
result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL=%v", matches[1]))
|
||||
}
|
||||
}
|
||||
if sp.OutputFile != "" {
|
||||
result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE=%v", sp.OutputFile))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) getTokenFromExecutableCommand(ctx context.Context) (string, error) {
|
||||
// For security reasons, we need our consumers to set this environment variable to allow executables to be run.
|
||||
if sp.env.getenv(allowExecutablesEnvVar) != "1" {
|
||||
return "", errors.New("credentials: executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithDeadline(ctx, sp.env.now().Add(sp.Timeout))
|
||||
defer cancel()
|
||||
|
||||
output, err := sp.env.run(ctx, sp.Command, sp.executableEnvironment())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sp.parseSubjectTokenFromSource(output, executableSource, sp.env.now().Unix())
|
||||
}
|
||||
|
||||
func missingFieldError(source, field string) error {
|
||||
return fmt.Errorf("credentials: %q missing %q field", source, field)
|
||||
}
|
||||
|
||||
func jsonParsingError(source, data string) error {
|
||||
return fmt.Errorf("credentials: unable to parse %q: %v", source, data)
|
||||
}
|
||||
|
||||
func malformedFailureError() error {
|
||||
return nonCacheableError{"credentials: response must include `error` and `message` fields when unsuccessful"}
|
||||
}
|
||||
|
||||
func userDefinedError(code, message string) error {
|
||||
return nonCacheableError{fmt.Sprintf("credentials: response contains unsuccessful response: (%v) %v", code, message)}
|
||||
}
|
||||
|
||||
func unsupportedVersionError(source string, version int) error {
|
||||
return fmt.Errorf("credentials: %v contains unsupported version: %v", source, version)
|
||||
}
|
||||
|
||||
func tokenExpiredError() error {
|
||||
return nonCacheableError{"credentials: the token returned by the executable is expired"}
|
||||
}
|
||||
|
||||
func tokenTypeError(source string) error {
|
||||
return fmt.Errorf("credentials: %v contains unsupported token type", source)
|
||||
}
|
||||
|
||||
func exitCodeError(err *exec.ExitError) error {
|
||||
return fmt.Errorf("credentials: executable command failed with exit code %v: %w", err.ExitCode(), err)
|
||||
}
|
||||
|
||||
func executableError(err error) error {
|
||||
return fmt.Errorf("credentials: executable command failed: %w", err)
|
||||
}
|
||||
431
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go
generated
vendored
Normal file
431
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go
generated
vendored
Normal file
@@ -0,0 +1,431 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/credentials/internal/impersonate"
|
||||
"cloud.google.com/go/auth/credentials/internal/stsexchange"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
timeoutMinimum = 5 * time.Second
|
||||
timeoutMaximum = 120 * time.Second
|
||||
|
||||
universeDomainPlaceholder = "UNIVERSE_DOMAIN"
|
||||
defaultTokenURL = "https://sts.UNIVERSE_DOMAIN/v1/token"
|
||||
defaultUniverseDomain = "googleapis.com"
|
||||
)
|
||||
|
||||
var (
|
||||
// Now aliases time.Now for testing
|
||||
Now = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
validWorkforceAudiencePattern *regexp.Regexp = regexp.MustCompile(`//iam\.googleapis\.com/locations/[^/]+/workforcePools/`)
|
||||
)
|
||||
|
||||
// Options stores the configuration for fetching tokens with external credentials.
|
||||
type Options struct {
|
||||
// Audience is the Secure Token Service (STS) audience which contains the resource name for the workload
|
||||
// identity pool or the workforce pool and the provider identifier in that pool.
|
||||
Audience string
|
||||
// SubjectTokenType is the STS token type based on the Oauth2.0 token exchange spec
|
||||
// e.g. `urn:ietf:params:oauth:token-type:jwt`.
|
||||
SubjectTokenType string
|
||||
// TokenURL is the STS token exchange endpoint.
|
||||
TokenURL string
|
||||
// TokenInfoURL is the token_info endpoint used to retrieve the account related information (
|
||||
// user attributes like account identifier, eg. email, username, uid, etc). This is
|
||||
// needed for gCloud session account identification.
|
||||
TokenInfoURL string
|
||||
// ServiceAccountImpersonationURL is the URL for the service account impersonation request. This is only
|
||||
// required for workload identity pools when APIs to be accessed have not integrated with UberMint.
|
||||
ServiceAccountImpersonationURL string
|
||||
// ServiceAccountImpersonationLifetimeSeconds is the number of seconds the service account impersonation
|
||||
// token will be valid for.
|
||||
ServiceAccountImpersonationLifetimeSeconds int
|
||||
// ClientSecret is currently only required if token_info endpoint also
|
||||
// needs to be called with the generated GCP access token. When provided, STS will be
|
||||
// called with additional basic authentication using client_id as username and client_secret as password.
|
||||
ClientSecret string
|
||||
// ClientID is only required in conjunction with ClientSecret, as described above.
|
||||
ClientID string
|
||||
// CredentialSource contains the necessary information to retrieve the token itself, as well
|
||||
// as some environmental information.
|
||||
CredentialSource *credsfile.CredentialSource
|
||||
// QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth libraries
|
||||
// will set the x-goog-user-project which overrides the project associated with the credentials.
|
||||
QuotaProjectID string
|
||||
// Scopes contains the desired scopes for the returned access token.
|
||||
Scopes []string
|
||||
// WorkforcePoolUserProject should be set when it is a workforce pool and
|
||||
// not a workload identity pool. The underlying principal must still have
|
||||
// serviceusage.services.use IAM permission to use the project for
|
||||
// billing/quota. Optional.
|
||||
WorkforcePoolUserProject string
|
||||
// UniverseDomain is the default service domain for a given Cloud universe.
|
||||
// This value will be used in the default STS token URL. The default value
|
||||
// is "googleapis.com". It will not be used if TokenURL is set. Optional.
|
||||
UniverseDomain string
|
||||
// SubjectTokenProvider is an optional token provider for OIDC/SAML
|
||||
// credentials. One of SubjectTokenProvider, AWSSecurityCredentialProvider
|
||||
// or CredentialSource must be provided. Optional.
|
||||
SubjectTokenProvider SubjectTokenProvider
|
||||
// AwsSecurityCredentialsProvider is an AWS Security Credential provider
|
||||
// for AWS credentials. One of SubjectTokenProvider,
|
||||
// AWSSecurityCredentialProvider or CredentialSource must be provided. Optional.
|
||||
AwsSecurityCredentialsProvider AwsSecurityCredentialsProvider
|
||||
// Client for token request.
|
||||
Client *http.Client
|
||||
// IsDefaultClient marks whether the client passed in is a default client that can be overriden.
|
||||
// This is important for X509 credentials which should create a new client if the default was used
|
||||
// but should respect a client explicitly passed in by the user.
|
||||
IsDefaultClient bool
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// SubjectTokenProvider can be used to supply a subject token to exchange for a
|
||||
// GCP access token.
|
||||
type SubjectTokenProvider interface {
|
||||
// SubjectToken should return a valid subject token or an error.
|
||||
// The external account token provider does not cache the returned subject
|
||||
// token, so caching logic should be implemented in the provider to prevent
|
||||
// multiple requests for the same subject token.
|
||||
SubjectToken(ctx context.Context, opts *RequestOptions) (string, error)
|
||||
}
|
||||
|
||||
// RequestOptions contains information about the requested subject token or AWS
|
||||
// security credentials from the Google external account credential.
|
||||
type RequestOptions struct {
|
||||
// Audience is the requested audience for the external account credential.
|
||||
Audience string
|
||||
// Subject token type is the requested subject token type for the external
|
||||
// account credential. Expected values include:
|
||||
// “urn:ietf:params:oauth:token-type:jwt”
|
||||
// “urn:ietf:params:oauth:token-type:id-token”
|
||||
// “urn:ietf:params:oauth:token-type:saml2”
|
||||
// “urn:ietf:params:aws:token-type:aws4_request”
|
||||
SubjectTokenType string
|
||||
}
|
||||
|
||||
// AwsSecurityCredentialsProvider can be used to supply AwsSecurityCredentials
|
||||
// and an AWS Region to exchange for a GCP access token.
|
||||
type AwsSecurityCredentialsProvider interface {
|
||||
// AwsRegion should return the AWS region or an error.
|
||||
AwsRegion(ctx context.Context, opts *RequestOptions) (string, error)
|
||||
// GetAwsSecurityCredentials should return a valid set of
|
||||
// AwsSecurityCredentials or an error. The external account token provider
|
||||
// does not cache the returned security credentials, so caching logic should
|
||||
// be implemented in the provider to prevent multiple requests for the
|
||||
// same security credentials.
|
||||
AwsSecurityCredentials(ctx context.Context, opts *RequestOptions) (*AwsSecurityCredentials, error)
|
||||
}
|
||||
|
||||
// AwsSecurityCredentials models AWS security credentials.
|
||||
type AwsSecurityCredentials struct {
|
||||
// AccessKeyId is the AWS Access Key ID - Required.
|
||||
AccessKeyID string `json:"AccessKeyID"`
|
||||
// SecretAccessKey is the AWS Secret Access Key - Required.
|
||||
SecretAccessKey string `json:"SecretAccessKey"`
|
||||
// SessionToken is the AWS Session token. This should be provided for
|
||||
// temporary AWS security credentials - Optional.
|
||||
SessionToken string `json:"Token"`
|
||||
}
|
||||
|
||||
func (o *Options) validate() error {
|
||||
if o.Audience == "" {
|
||||
return fmt.Errorf("externalaccount: Audience must be set")
|
||||
}
|
||||
if o.SubjectTokenType == "" {
|
||||
return fmt.Errorf("externalaccount: Subject token type must be set")
|
||||
}
|
||||
if o.WorkforcePoolUserProject != "" {
|
||||
if valid := validWorkforceAudiencePattern.MatchString(o.Audience); !valid {
|
||||
return fmt.Errorf("externalaccount: workforce_pool_user_project should not be set for non-workforce pool credentials")
|
||||
}
|
||||
}
|
||||
count := 0
|
||||
if o.CredentialSource != nil {
|
||||
count++
|
||||
}
|
||||
if o.SubjectTokenProvider != nil {
|
||||
count++
|
||||
}
|
||||
if o.AwsSecurityCredentialsProvider != nil {
|
||||
count++
|
||||
}
|
||||
if count == 0 {
|
||||
return fmt.Errorf("externalaccount: one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
|
||||
}
|
||||
if count > 1 {
|
||||
return fmt.Errorf("externalaccount: only one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// client returns the http client that should be used for the token exchange. If a non-default client
|
||||
// is provided, then the client configured in the options will always be returned. If a default client
|
||||
// is provided and the options are configured for X509 credentials, a new client will be created.
|
||||
func (o *Options) client() (*http.Client, error) {
|
||||
// If a client was provided and no override certificate config location was provided, use the provided client.
|
||||
if o.CredentialSource == nil || o.CredentialSource.Certificate == nil || (!o.IsDefaultClient && o.CredentialSource.Certificate.CertificateConfigLocation == "") {
|
||||
return o.Client, nil
|
||||
}
|
||||
|
||||
// If a new client should be created, validate and use the certificate source to create a new mTLS client.
|
||||
cert := o.CredentialSource.Certificate
|
||||
if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
|
||||
return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
|
||||
}
|
||||
if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
|
||||
return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
|
||||
}
|
||||
return createX509Client(cert.CertificateConfigLocation)
|
||||
}
|
||||
|
||||
// resolveTokenURL sets the default STS token endpoint with the configured
|
||||
// universe domain.
|
||||
func (o *Options) resolveTokenURL() {
|
||||
if o.TokenURL != "" {
|
||||
return
|
||||
} else if o.UniverseDomain != "" {
|
||||
o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, o.UniverseDomain, 1)
|
||||
} else {
|
||||
o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, defaultUniverseDomain, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
|
||||
// configured with the provided options.
|
||||
func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.resolveTokenURL()
|
||||
logger := internallog.New(opts.Logger)
|
||||
stp, err := newSubjectTokenProvider(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := opts.client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tp := &tokenProvider{
|
||||
client: client,
|
||||
opts: opts,
|
||||
stp: stp,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
if opts.ServiceAccountImpersonationURL == "" {
|
||||
return auth.NewCachedTokenProvider(tp, nil), nil
|
||||
}
|
||||
|
||||
scopes := make([]string, len(opts.Scopes))
|
||||
copy(scopes, opts.Scopes)
|
||||
// needed for impersonation
|
||||
tp.opts.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
|
||||
imp, err := impersonate.NewTokenProvider(&impersonate.Options{
|
||||
Client: client,
|
||||
URL: opts.ServiceAccountImpersonationURL,
|
||||
Scopes: scopes,
|
||||
Tp: auth.NewCachedTokenProvider(tp, nil),
|
||||
TokenLifetimeSeconds: opts.ServiceAccountImpersonationLifetimeSeconds,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auth.NewCachedTokenProvider(imp, nil), nil
|
||||
}
|
||||
|
||||
type subjectTokenProvider interface {
|
||||
subjectToken(ctx context.Context) (string, error)
|
||||
providerType() string
|
||||
}
|
||||
|
||||
// tokenProvider is the provider that handles external credentials. It is used to retrieve Tokens.
|
||||
type tokenProvider struct {
|
||||
client *http.Client
|
||||
logger *slog.Logger
|
||||
opts *Options
|
||||
stp subjectTokenProvider
|
||||
}
|
||||
|
||||
func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
subjectToken, err := tp.stp.subjectToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stsRequest := &stsexchange.TokenRequest{
|
||||
GrantType: stsexchange.GrantType,
|
||||
Audience: tp.opts.Audience,
|
||||
Scope: tp.opts.Scopes,
|
||||
RequestedTokenType: stsexchange.TokenType,
|
||||
SubjectToken: subjectToken,
|
||||
SubjectTokenType: tp.opts.SubjectTokenType,
|
||||
}
|
||||
header := make(http.Header)
|
||||
header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
header.Add("x-goog-api-client", getGoogHeaderValue(tp.opts, tp.stp))
|
||||
clientAuth := stsexchange.ClientAuthentication{
|
||||
AuthStyle: auth.StyleInHeader,
|
||||
ClientID: tp.opts.ClientID,
|
||||
ClientSecret: tp.opts.ClientSecret,
|
||||
}
|
||||
var options map[string]interface{}
|
||||
// Do not pass workforce_pool_user_project when client authentication is used.
|
||||
// The client ID is sufficient for determining the user project.
|
||||
if tp.opts.WorkforcePoolUserProject != "" && tp.opts.ClientID == "" {
|
||||
options = map[string]interface{}{
|
||||
"userProject": tp.opts.WorkforcePoolUserProject,
|
||||
}
|
||||
}
|
||||
stsResp, err := stsexchange.ExchangeToken(ctx, &stsexchange.Options{
|
||||
Client: tp.client,
|
||||
Endpoint: tp.opts.TokenURL,
|
||||
Request: stsRequest,
|
||||
Authentication: clientAuth,
|
||||
Headers: header,
|
||||
ExtraOpts: options,
|
||||
Logger: tp.logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tok := &auth.Token{
|
||||
Value: stsResp.AccessToken,
|
||||
Type: stsResp.TokenType,
|
||||
}
|
||||
// The RFC8693 doesn't define the explicit 0 of "expires_in" field behavior.
|
||||
if stsResp.ExpiresIn <= 0 {
|
||||
return nil, fmt.Errorf("credentials: got invalid expiry from security token service")
|
||||
}
|
||||
tok.Expiry = Now().Add(time.Duration(stsResp.ExpiresIn) * time.Second)
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// newSubjectTokenProvider determines the type of credsfile.CredentialSource needed to create a
|
||||
// subjectTokenProvider
|
||||
func newSubjectTokenProvider(o *Options) (subjectTokenProvider, error) {
|
||||
logger := internallog.New(o.Logger)
|
||||
reqOpts := &RequestOptions{Audience: o.Audience, SubjectTokenType: o.SubjectTokenType}
|
||||
if o.AwsSecurityCredentialsProvider != nil {
|
||||
return &awsSubjectProvider{
|
||||
securityCredentialsProvider: o.AwsSecurityCredentialsProvider,
|
||||
TargetResource: o.Audience,
|
||||
reqOpts: reqOpts,
|
||||
logger: logger,
|
||||
}, nil
|
||||
} else if o.SubjectTokenProvider != nil {
|
||||
return &programmaticProvider{stp: o.SubjectTokenProvider, opts: reqOpts}, nil
|
||||
} else if len(o.CredentialSource.EnvironmentID) > 3 && o.CredentialSource.EnvironmentID[:3] == "aws" {
|
||||
if awsVersion, err := strconv.Atoi(o.CredentialSource.EnvironmentID[3:]); err == nil {
|
||||
if awsVersion != 1 {
|
||||
return nil, fmt.Errorf("credentials: aws version '%d' is not supported in the current build", awsVersion)
|
||||
}
|
||||
|
||||
awsProvider := &awsSubjectProvider{
|
||||
EnvironmentID: o.CredentialSource.EnvironmentID,
|
||||
RegionURL: o.CredentialSource.RegionURL,
|
||||
RegionalCredVerificationURL: o.CredentialSource.RegionalCredVerificationURL,
|
||||
CredVerificationURL: o.CredentialSource.URL,
|
||||
TargetResource: o.Audience,
|
||||
Client: o.Client,
|
||||
logger: logger,
|
||||
}
|
||||
if o.CredentialSource.IMDSv2SessionTokenURL != "" {
|
||||
awsProvider.IMDSv2SessionTokenURL = o.CredentialSource.IMDSv2SessionTokenURL
|
||||
}
|
||||
|
||||
return awsProvider, nil
|
||||
}
|
||||
} else if o.CredentialSource.File != "" {
|
||||
return &fileSubjectProvider{File: o.CredentialSource.File, Format: o.CredentialSource.Format}, nil
|
||||
} else if o.CredentialSource.URL != "" {
|
||||
return &urlSubjectProvider{
|
||||
URL: o.CredentialSource.URL,
|
||||
Headers: o.CredentialSource.Headers,
|
||||
Format: o.CredentialSource.Format,
|
||||
Client: o.Client,
|
||||
Logger: logger,
|
||||
}, nil
|
||||
} else if o.CredentialSource.Executable != nil {
|
||||
ec := o.CredentialSource.Executable
|
||||
if ec.Command == "" {
|
||||
return nil, errors.New("credentials: missing `command` field — executable command must be provided")
|
||||
}
|
||||
|
||||
execProvider := &executableSubjectProvider{}
|
||||
execProvider.Command = ec.Command
|
||||
if ec.TimeoutMillis == 0 {
|
||||
execProvider.Timeout = executableDefaultTimeout
|
||||
} else {
|
||||
execProvider.Timeout = time.Duration(ec.TimeoutMillis) * time.Millisecond
|
||||
if execProvider.Timeout < timeoutMinimum || execProvider.Timeout > timeoutMaximum {
|
||||
return nil, fmt.Errorf("credentials: invalid `timeout_millis` field — executable timeout must be between %v and %v seconds", timeoutMinimum.Seconds(), timeoutMaximum.Seconds())
|
||||
}
|
||||
}
|
||||
execProvider.OutputFile = ec.OutputFile
|
||||
execProvider.client = o.Client
|
||||
execProvider.opts = o
|
||||
execProvider.env = runtimeEnvironment{}
|
||||
return execProvider, nil
|
||||
} else if o.CredentialSource.Certificate != nil {
|
||||
cert := o.CredentialSource.Certificate
|
||||
if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
|
||||
return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
|
||||
}
|
||||
if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
|
||||
return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
|
||||
}
|
||||
return &x509Provider{
|
||||
TrustChainPath: o.CredentialSource.Certificate.TrustChainPath,
|
||||
ConfigFilePath: o.CredentialSource.Certificate.CertificateConfigLocation,
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("credentials: unable to parse credential source")
|
||||
}
|
||||
|
||||
func getGoogHeaderValue(conf *Options, p subjectTokenProvider) string {
|
||||
return fmt.Sprintf("gl-go/%s auth/%s google-byoid-sdk source/%s sa-impersonation/%t config-lifetime/%t",
|
||||
goVersion(),
|
||||
"unknown",
|
||||
p.providerType(),
|
||||
conf.ServiceAccountImpersonationURL != "",
|
||||
conf.ServiceAccountImpersonationLifetimeSeconds != 0)
|
||||
}
|
||||
78
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go
generated
vendored
Normal file
78
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
)
|
||||
|
||||
const (
|
||||
fileProviderType = "file"
|
||||
)
|
||||
|
||||
type fileSubjectProvider struct {
|
||||
File string
|
||||
Format *credsfile.Format
|
||||
}
|
||||
|
||||
func (sp *fileSubjectProvider) subjectToken(context.Context) (string, error) {
|
||||
tokenFile, err := os.Open(sp.File)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: failed to open credential file %q: %w", sp.File, err)
|
||||
}
|
||||
defer tokenFile.Close()
|
||||
tokenBytes, err := internal.ReadAll(tokenFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: failed to read credential file: %w", err)
|
||||
}
|
||||
tokenBytes = bytes.TrimSpace(tokenBytes)
|
||||
|
||||
if sp.Format == nil {
|
||||
return string(tokenBytes), nil
|
||||
}
|
||||
switch sp.Format.Type {
|
||||
case fileTypeJSON:
|
||||
jsonData := make(map[string]interface{})
|
||||
err = json.Unmarshal(tokenBytes, &jsonData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
|
||||
}
|
||||
val, ok := jsonData[sp.Format.SubjectTokenFieldName]
|
||||
if !ok {
|
||||
return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
|
||||
}
|
||||
token, ok := val.(string)
|
||||
if !ok {
|
||||
return "", errors.New("credentials: improperly formatted subject token")
|
||||
}
|
||||
return token, nil
|
||||
case fileTypeText:
|
||||
return string(tokenBytes), nil
|
||||
default:
|
||||
return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *fileSubjectProvider) providerType() string {
|
||||
return fileProviderType
|
||||
}
|
||||
74
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go
generated
vendored
Normal file
74
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
// version is a package internal global variable for testing purposes.
|
||||
version = runtime.Version
|
||||
)
|
||||
|
||||
// versionUnknown is only used when the runtime version cannot be determined.
|
||||
const versionUnknown = "UNKNOWN"
|
||||
|
||||
// goVersion returns a Go runtime version derived from the runtime environment
|
||||
// that is modified to be suitable for reporting in a header, meaning it has no
|
||||
// whitespace. If it is unable to determine the Go runtime version, it returns
|
||||
// versionUnknown.
|
||||
func goVersion() string {
|
||||
const develPrefix = "devel +"
|
||||
|
||||
s := version()
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
} else if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
|
||||
notSemverRune := func(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
// Some release candidates already have a dash in them.
|
||||
if !strings.HasPrefix(prerelease, "-") {
|
||||
prerelease = "-" + prerelease
|
||||
}
|
||||
s += prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return versionUnknown
|
||||
}
|
||||
30
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go
generated
vendored
Normal file
30
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import "context"
|
||||
|
||||
type programmaticProvider struct {
|
||||
opts *RequestOptions
|
||||
stp SubjectTokenProvider
|
||||
}
|
||||
|
||||
func (pp *programmaticProvider) providerType() string {
|
||||
return programmaticProviderType
|
||||
}
|
||||
|
||||
func (pp *programmaticProvider) subjectToken(ctx context.Context) (string, error) {
|
||||
return pp.stp.SubjectToken(ctx, pp.opts)
|
||||
}
|
||||
93
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go
generated
vendored
Normal file
93
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
fileTypeText = "text"
|
||||
fileTypeJSON = "json"
|
||||
urlProviderType = "url"
|
||||
programmaticProviderType = "programmatic"
|
||||
x509ProviderType = "x509"
|
||||
)
|
||||
|
||||
type urlSubjectProvider struct {
|
||||
URL string
|
||||
Headers map[string]string
|
||||
Format *credsfile.Format
|
||||
Client *http.Client
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (sp *urlSubjectProvider) subjectToken(ctx context.Context) (string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", sp.URL, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: HTTP request for URL-sourced credential failed: %w", err)
|
||||
}
|
||||
|
||||
for key, val := range sp.Headers {
|
||||
req.Header.Add(key, val)
|
||||
}
|
||||
sp.Logger.DebugContext(ctx, "url subject token request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: invalid response when retrieving subject token: %w", err)
|
||||
}
|
||||
sp.Logger.DebugContext(ctx, "url subject token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
|
||||
return "", fmt.Errorf("credentials: status code %d: %s", c, body)
|
||||
}
|
||||
|
||||
if sp.Format == nil {
|
||||
return string(body), nil
|
||||
}
|
||||
switch sp.Format.Type {
|
||||
case "json":
|
||||
jsonData := make(map[string]interface{})
|
||||
err = json.Unmarshal(body, &jsonData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
|
||||
}
|
||||
val, ok := jsonData[sp.Format.SubjectTokenFieldName]
|
||||
if !ok {
|
||||
return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
|
||||
}
|
||||
token, ok := val.(string)
|
||||
if !ok {
|
||||
return "", errors.New("credentials: improperly formatted subject token")
|
||||
}
|
||||
return token, nil
|
||||
case fileTypeText:
|
||||
return string(body), nil
|
||||
default:
|
||||
return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *urlSubjectProvider) providerType() string {
|
||||
return urlProviderType
|
||||
}
|
||||
220
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go
generated
vendored
Normal file
220
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go
generated
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
)
|
||||
|
||||
// x509Provider implements the subjectTokenProvider type for x509 workload
|
||||
// identity credentials. This provider retrieves and formats a JSON array
|
||||
// containing the leaf certificate and trust chain (if provided) as
|
||||
// base64-encoded strings. This JSON array serves as the subject token for
|
||||
// mTLS authentication.
|
||||
type x509Provider struct {
|
||||
// TrustChainPath is the path to the file containing the trust chain certificates.
|
||||
// The file should contain one or more PEM-encoded certificates.
|
||||
TrustChainPath string
|
||||
// ConfigFilePath is the path to the configuration file containing the path
|
||||
// to the leaf certificate file.
|
||||
ConfigFilePath string
|
||||
}
|
||||
|
||||
const pemCertificateHeader = "-----BEGIN CERTIFICATE-----"
|
||||
|
||||
func (xp *x509Provider) providerType() string {
|
||||
return x509ProviderType
|
||||
}
|
||||
|
||||
// loadLeafCertificate loads and parses the leaf certificate from the specified
|
||||
// configuration file. It retrieves the certificate path from the config file,
|
||||
// reads the certificate file, and parses the certificate data.
|
||||
func loadLeafCertificate(configFilePath string) (*x509.Certificate, error) {
|
||||
// Get the path to the certificate file from the configuration file.
|
||||
path, err := cert.GetCertificatePath(configFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get certificate path from config file: %w", err)
|
||||
}
|
||||
leafCertBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read leaf certificate file: %w", err)
|
||||
}
|
||||
// Parse the certificate bytes.
|
||||
return parseCertificate(leafCertBytes)
|
||||
}
|
||||
|
||||
// encodeCert encodes a x509.Certificate to a base64 string.
|
||||
func encodeCert(cert *x509.Certificate) string {
|
||||
// cert.Raw contains the raw DER-encoded certificate. Encode the raw certificate bytes to base64.
|
||||
return base64.StdEncoding.EncodeToString(cert.Raw)
|
||||
}
|
||||
|
||||
// parseCertificate parses a PEM-encoded certificate from the given byte slice.
|
||||
func parseCertificate(certData []byte) (*x509.Certificate, error) {
|
||||
if len(certData) == 0 {
|
||||
return nil, errors.New("invalid certificate data: empty input")
|
||||
}
|
||||
// Decode the PEM-encoded data.
|
||||
block, _ := pem.Decode(certData)
|
||||
if block == nil {
|
||||
return nil, errors.New("invalid PEM-encoded certificate data: no PEM block found")
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
return nil, fmt.Errorf("invalid PEM-encoded certificate data: expected CERTIFICATE block type, got %s", block.Type)
|
||||
}
|
||||
// Parse the DER-encoded certificate.
|
||||
certificate, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse certificate: %w", err)
|
||||
}
|
||||
return certificate, nil
|
||||
}
|
||||
|
||||
// readTrustChain reads a file of PEM-encoded X.509 certificates and returns a slice of parsed certificates.
|
||||
// It splits the file content into PEM certificate blocks and parses each one.
|
||||
func readTrustChain(trustChainPath string) ([]*x509.Certificate, error) {
|
||||
certificateTrustChain := []*x509.Certificate{}
|
||||
|
||||
// If no trust chain path is provided, return an empty slice.
|
||||
if trustChainPath == "" {
|
||||
return certificateTrustChain, nil
|
||||
}
|
||||
|
||||
// Read the trust chain file.
|
||||
trustChainData, err := os.ReadFile(trustChainPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, fmt.Errorf("trust chain file not found: %w", err)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read trust chain file: %w", err)
|
||||
}
|
||||
|
||||
// Split the file content into PEM certificate blocks.
|
||||
certBlocks := strings.Split(string(trustChainData), pemCertificateHeader)
|
||||
|
||||
// Iterate over each certificate block.
|
||||
for _, certBlock := range certBlocks {
|
||||
// Trim whitespace from the block.
|
||||
certBlock = strings.TrimSpace(certBlock)
|
||||
|
||||
if certBlock != "" {
|
||||
// Add the PEM header to the block.
|
||||
certData := pemCertificateHeader + "\n" + certBlock
|
||||
|
||||
// Parse the certificate data.
|
||||
cert, err := parseCertificate([]byte(certData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing certificate from trust chain file: %w", err)
|
||||
}
|
||||
|
||||
// Append the certificate to the trust chain.
|
||||
certificateTrustChain = append(certificateTrustChain, cert)
|
||||
}
|
||||
}
|
||||
|
||||
return certificateTrustChain, nil
|
||||
}
|
||||
|
||||
// subjectToken retrieves the X.509 subject token. It loads the leaf
|
||||
// certificate and, if a trust chain path is configured, the trust chain
|
||||
// certificates. It then constructs a JSON array containing the base64-encoded
|
||||
// leaf certificate and each base64-encoded certificate in the trust chain.
|
||||
// The leaf certificate must be at the top of the trust chain file. This JSON
|
||||
// array is used as the subject token for mTLS authentication.
|
||||
func (xp *x509Provider) subjectToken(context.Context) (string, error) {
|
||||
// Load the leaf certificate.
|
||||
leafCert, err := loadLeafCertificate(xp.ConfigFilePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load leaf certificate: %w", err)
|
||||
}
|
||||
|
||||
// Read the trust chain.
|
||||
trustChain, err := readTrustChain(xp.TrustChainPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read trust chain: %w", err)
|
||||
}
|
||||
|
||||
// Initialize the certificate chain with the leaf certificate.
|
||||
certChain := []string{encodeCert(leafCert)}
|
||||
|
||||
// If there is a trust chain, add certificates to the certificate chain.
|
||||
if len(trustChain) > 0 {
|
||||
firstCert := encodeCert(trustChain[0])
|
||||
|
||||
// If the first certificate in the trust chain is not the same as the leaf certificate, add it to the chain.
|
||||
if firstCert != certChain[0] {
|
||||
certChain = append(certChain, firstCert)
|
||||
}
|
||||
|
||||
// Iterate over the remaining certificates in the trust chain.
|
||||
for i := 1; i < len(trustChain); i++ {
|
||||
encoded := encodeCert(trustChain[i])
|
||||
|
||||
// Return an error if the current certificate is the same as the leaf certificate.
|
||||
if encoded == certChain[0] {
|
||||
return "", errors.New("the leaf certificate must be at the top of the trust chain file")
|
||||
}
|
||||
|
||||
// Add the current certificate to the chain.
|
||||
certChain = append(certChain, encoded)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the certificate chain to a JSON array of base64-encoded strings.
|
||||
jsonChain, err := json.Marshal(certChain)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to format certificate data: %w", err)
|
||||
}
|
||||
|
||||
// Return the JSON-formatted certificate chain.
|
||||
return string(jsonChain), nil
|
||||
|
||||
}
|
||||
|
||||
// createX509Client creates a new client that is configured with mTLS, using the
|
||||
// certificate configuration specified in the credential source.
|
||||
func createX509Client(certificateConfigLocation string) (*http.Client, error) {
|
||||
certProvider, err := cert.NewWorkloadX509CertProvider(certificateConfigLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trans := http.DefaultTransport.(*http.Transport).Clone()
|
||||
|
||||
trans.TLSClientConfig = &tls.Config{
|
||||
GetClientCertificate: certProvider,
|
||||
}
|
||||
|
||||
// Create a client with default settings plus the X509 workload cert and key.
|
||||
client := &http.Client{
|
||||
Transport: trans,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
115
vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go
generated
vendored
Normal file
115
vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccountuser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/credentials/internal/stsexchange"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
// Options stores the configuration for fetching tokens with external authorized
|
||||
// user credentials.
|
||||
type Options struct {
|
||||
// Audience is the Secure Token Service (STS) audience which contains the
|
||||
// resource name for the workforce pool and the provider identifier in that
|
||||
// pool.
|
||||
Audience string
|
||||
// RefreshToken is the OAuth 2.0 refresh token.
|
||||
RefreshToken string
|
||||
// TokenURL is the STS token exchange endpoint for refresh.
|
||||
TokenURL string
|
||||
// TokenInfoURL is the STS endpoint URL for token introspection. Optional.
|
||||
TokenInfoURL string
|
||||
// ClientID is only required in conjunction with ClientSecret, as described
|
||||
// below.
|
||||
ClientID string
|
||||
// ClientSecret is currently only required if token_info endpoint also needs
|
||||
// to be called with the generated a cloud access token. When provided, STS
|
||||
// will be called with additional basic authentication using client_id as
|
||||
// username and client_secret as password.
|
||||
ClientSecret string
|
||||
// Scopes contains the desired scopes for the returned access token.
|
||||
Scopes []string
|
||||
|
||||
// Client for token request.
|
||||
Client *http.Client
|
||||
// Logger for logging.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (c *Options) validate() bool {
|
||||
return c.ClientID != "" && c.ClientSecret != "" && c.RefreshToken != "" && c.TokenURL != ""
|
||||
}
|
||||
|
||||
// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
|
||||
// configured with the provided options.
|
||||
func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
|
||||
if !opts.validate() {
|
||||
return nil, errors.New("credentials: invalid external_account_authorized_user configuration")
|
||||
}
|
||||
|
||||
tp := &tokenProvider{
|
||||
o: opts,
|
||||
}
|
||||
return auth.NewCachedTokenProvider(tp, nil), nil
|
||||
}
|
||||
|
||||
type tokenProvider struct {
|
||||
o *Options
|
||||
}
|
||||
|
||||
func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
opts := tp.o
|
||||
|
||||
clientAuth := stsexchange.ClientAuthentication{
|
||||
AuthStyle: auth.StyleInHeader,
|
||||
ClientID: opts.ClientID,
|
||||
ClientSecret: opts.ClientSecret,
|
||||
}
|
||||
headers := make(http.Header)
|
||||
headers.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
stsResponse, err := stsexchange.RefreshAccessToken(ctx, &stsexchange.Options{
|
||||
Client: opts.Client,
|
||||
Endpoint: opts.TokenURL,
|
||||
RefreshToken: opts.RefreshToken,
|
||||
Authentication: clientAuth,
|
||||
Headers: headers,
|
||||
Logger: internallog.New(tp.o.Logger),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stsResponse.ExpiresIn < 0 {
|
||||
return nil, errors.New("credentials: invalid expiry from security token service")
|
||||
}
|
||||
|
||||
// guarded by the wrapping with CachedTokenProvider
|
||||
if stsResponse.RefreshToken != "" {
|
||||
opts.RefreshToken = stsResponse.RefreshToken
|
||||
}
|
||||
return &auth.Token{
|
||||
Value: stsResponse.AccessToken,
|
||||
Expiry: time.Now().UTC().Add(time.Duration(stsResponse.ExpiresIn) * time.Second),
|
||||
Type: internal.TokenTypeBearer,
|
||||
}, nil
|
||||
}
|
||||
191
vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go
generated
vendored
Normal file
191
vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gdch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/auth/internal/jwt"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// GrantType is the grant type for the token request.
|
||||
GrantType = "urn:ietf:params:oauth:token-type:token-exchange"
|
||||
requestTokenType = "urn:ietf:params:oauth:token-type:access_token"
|
||||
subjectTokenType = "urn:k8s:params:oauth:token-type:serviceaccount"
|
||||
)
|
||||
|
||||
var (
|
||||
gdchSupportFormatVersions map[string]bool = map[string]bool{
|
||||
"1": true,
|
||||
}
|
||||
)
|
||||
|
||||
// Options for [NewTokenProvider].
|
||||
type Options struct {
|
||||
STSAudience string
|
||||
Client *http.Client
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider] from a
|
||||
// GDCH cred file.
|
||||
func NewTokenProvider(f *credsfile.GDCHServiceAccountFile, o *Options) (auth.TokenProvider, error) {
|
||||
if !gdchSupportFormatVersions[f.FormatVersion] {
|
||||
return nil, fmt.Errorf("credentials: unsupported gdch_service_account format %q", f.FormatVersion)
|
||||
}
|
||||
if o.STSAudience == "" {
|
||||
return nil, errors.New("credentials: STSAudience must be set for the GDCH auth flows")
|
||||
}
|
||||
signer, err := internal.ParseKey([]byte(f.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool, err := loadCertPool(f.CertPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tp := gdchProvider{
|
||||
serviceIdentity: fmt.Sprintf("system:serviceaccount:%s:%s", f.Project, f.Name),
|
||||
tokenURL: f.TokenURL,
|
||||
aud: o.STSAudience,
|
||||
signer: signer,
|
||||
pkID: f.PrivateKeyID,
|
||||
certPool: certPool,
|
||||
client: o.Client,
|
||||
logger: internallog.New(o.Logger),
|
||||
}
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
func loadCertPool(path string) (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
pem, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to read certificate: %w", err)
|
||||
}
|
||||
pool.AppendCertsFromPEM(pem)
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
type gdchProvider struct {
|
||||
serviceIdentity string
|
||||
tokenURL string
|
||||
aud string
|
||||
signer crypto.Signer
|
||||
pkID string
|
||||
certPool *x509.CertPool
|
||||
|
||||
client *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
addCertToTransport(g.client, g.certPool)
|
||||
iat := time.Now()
|
||||
exp := iat.Add(time.Hour)
|
||||
claims := jwt.Claims{
|
||||
Iss: g.serviceIdentity,
|
||||
Sub: g.serviceIdentity,
|
||||
Aud: g.tokenURL,
|
||||
Iat: iat.Unix(),
|
||||
Exp: exp.Unix(),
|
||||
}
|
||||
h := jwt.Header{
|
||||
Algorithm: jwt.HeaderAlgRSA256,
|
||||
Type: jwt.HeaderType,
|
||||
KeyID: string(g.pkID),
|
||||
}
|
||||
payload, err := jwt.EncodeJWS(&h, &claims, g.signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Set("grant_type", GrantType)
|
||||
v.Set("audience", g.aud)
|
||||
v.Set("requested_token_type", requestTokenType)
|
||||
v.Set("subject_token", payload)
|
||||
v.Set("subject_token_type", subjectTokenType)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", g.tokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
g.logger.DebugContext(ctx, "gdch token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
|
||||
resp, body, err := internal.DoRequest(g.client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
|
||||
}
|
||||
g.logger.DebugContext(ctx, "gdch token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
|
||||
return nil, &auth.Error{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
|
||||
var tokenRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"` // relative seconds from now
|
||||
}
|
||||
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||
return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
|
||||
}
|
||||
token := &auth.Token{
|
||||
Value: tokenRes.AccessToken,
|
||||
Type: tokenRes.TokenType,
|
||||
}
|
||||
raw := make(map[string]interface{})
|
||||
json.Unmarshal(body, &raw) // no error checks for optional fields
|
||||
token.Metadata = raw
|
||||
|
||||
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// addCertToTransport makes a best effort attempt at adding in the cert info to
|
||||
// the client. It tries to keep all configured transport settings if the
|
||||
// underlying transport is an http.Transport. Or else it overwrites the
|
||||
// transport with defaults adding in the certs.
|
||||
func addCertToTransport(hc *http.Client, certPool *x509.CertPool) {
|
||||
trans, ok := hc.Transport.(*http.Transport)
|
||||
if !ok {
|
||||
trans = http.DefaultTransport.(*http.Transport).Clone()
|
||||
}
|
||||
trans.TLSClientConfig = &tls.Config{
|
||||
RootCAs: certPool,
|
||||
}
|
||||
}
|
||||
105
vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go
generated
vendored
Normal file
105
vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package impersonate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
var (
|
||||
universeDomainPlaceholder = "UNIVERSE_DOMAIN"
|
||||
iamCredentialsUniverseDomainEndpoint = "https://iamcredentials.UNIVERSE_DOMAIN"
|
||||
)
|
||||
|
||||
// IDTokenIAMOptions provides configuration for [IDTokenIAMOptions.Token].
|
||||
type IDTokenIAMOptions struct {
|
||||
// Client is required.
|
||||
Client *http.Client
|
||||
// Logger is required.
|
||||
Logger *slog.Logger
|
||||
UniverseDomain auth.CredentialsPropertyProvider
|
||||
ServiceAccountEmail string
|
||||
GenerateIDTokenRequest
|
||||
}
|
||||
|
||||
// GenerateIDTokenRequest holds the request to the IAM generateIdToken RPC.
|
||||
type GenerateIDTokenRequest struct {
|
||||
Audience string `json:"audience"`
|
||||
IncludeEmail bool `json:"includeEmail"`
|
||||
// Delegates are the ordered, fully-qualified resource name for service
|
||||
// accounts in a delegation chain. Each service account must be granted
|
||||
// roles/iam.serviceAccountTokenCreator on the next service account in the
|
||||
// chain. The delegates must have the following format:
|
||||
// projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID}. The - wildcard
|
||||
// character is required; replacing it with a project ID is invalid.
|
||||
// Optional.
|
||||
Delegates []string `json:"delegates,omitempty"`
|
||||
}
|
||||
|
||||
// GenerateIDTokenResponse holds the response from the IAM generateIdToken RPC.
|
||||
type GenerateIDTokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// Token call IAM generateIdToken with the configuration provided in [IDTokenIAMOptions].
|
||||
func (o IDTokenIAMOptions) Token(ctx context.Context) (*auth.Token, error) {
|
||||
universeDomain, err := o.UniverseDomain.GetProperty(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint := strings.Replace(iamCredentialsUniverseDomainEndpoint, universeDomainPlaceholder, universeDomain, 1)
|
||||
url := fmt.Sprintf("%s/v1/%s:generateIdToken", endpoint, internal.FormatIAMServiceAccountResource(o.ServiceAccountEmail))
|
||||
|
||||
bodyBytes, err := json.Marshal(o.GenerateIDTokenRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("impersonate: unable to marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("impersonate: unable to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
o.Logger.DebugContext(ctx, "impersonated idtoken request", "request", internallog.HTTPRequest(req, bodyBytes))
|
||||
resp, body, err := internal.DoRequest(o.Client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("impersonate: unable to generate ID token: %w", err)
|
||||
}
|
||||
o.Logger.DebugContext(ctx, "impersonated idtoken response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < 200 || c > 299 {
|
||||
return nil, fmt.Errorf("impersonate: status code %d: %s", c, body)
|
||||
}
|
||||
|
||||
var tokenResp GenerateIDTokenResponse
|
||||
if err := json.Unmarshal(body, &tokenResp); err != nil {
|
||||
return nil, fmt.Errorf("impersonate: unable to parse response: %w", err)
|
||||
}
|
||||
return &auth.Token{
|
||||
Value: tokenResp.Token,
|
||||
// Generated ID tokens are good for one hour.
|
||||
Expiry: time.Now().Add(1 * time.Hour),
|
||||
}, nil
|
||||
}
|
||||
168
vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go
generated
vendored
Normal file
168
vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package impersonate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/transport/headers"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTokenLifetime = "3600s"
|
||||
authHeaderKey = "Authorization"
|
||||
)
|
||||
|
||||
var serviceAccountEmailRegex = regexp.MustCompile(`serviceAccounts/(.+?):generateAccessToken`)
|
||||
|
||||
// generateAccesstokenReq is used for service account impersonation
|
||||
type generateAccessTokenReq struct {
|
||||
Delegates []string `json:"delegates,omitempty"`
|
||||
Lifetime string `json:"lifetime,omitempty"`
|
||||
Scope []string `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
type impersonateTokenResponse struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
ExpireTime string `json:"expireTime"`
|
||||
}
|
||||
|
||||
// NewTokenProvider uses a source credential, stored in Ts, to request an access token to the provided URL.
|
||||
// Scopes can be defined when the access token is requested.
|
||||
func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// Options for [NewTokenProvider].
|
||||
type Options struct {
|
||||
// Tp is the source credential used to generate a token on the
|
||||
// impersonated service account. Required.
|
||||
Tp auth.TokenProvider
|
||||
|
||||
// URL is the endpoint to call to generate a token
|
||||
// on behalf of the service account. Required.
|
||||
URL string
|
||||
// Scopes that the impersonated credential should have. Required.
|
||||
Scopes []string
|
||||
// Delegates are the service account email addresses in a delegation chain.
|
||||
// Each service account must be granted roles/iam.serviceAccountTokenCreator
|
||||
// on the next service account in the chain. Optional.
|
||||
Delegates []string
|
||||
// TokenLifetimeSeconds is the number of seconds the impersonation token will
|
||||
// be valid for. Defaults to 1 hour if unset. Optional.
|
||||
TokenLifetimeSeconds int
|
||||
// Client configures the underlying client used to make network requests
|
||||
// when fetching tokens. Required.
|
||||
Client *http.Client
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
// UniverseDomain is the default service domain for a given Cloud universe.
|
||||
UniverseDomain string
|
||||
}
|
||||
|
||||
func (o *Options) validate() error {
|
||||
if o.Tp == nil {
|
||||
return errors.New("credentials: missing required 'source_credentials' field in impersonated credentials")
|
||||
}
|
||||
if o.URL == "" {
|
||||
return errors.New("credentials: missing required 'service_account_impersonation_url' field in impersonated credentials")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Token performs the exchange to get a temporary service account token to allow access to GCP.
|
||||
func (o *Options) Token(ctx context.Context) (*auth.Token, error) {
|
||||
logger := internallog.New(o.Logger)
|
||||
lifetime := defaultTokenLifetime
|
||||
if o.TokenLifetimeSeconds != 0 {
|
||||
lifetime = fmt.Sprintf("%ds", o.TokenLifetimeSeconds)
|
||||
}
|
||||
reqBody := generateAccessTokenReq{
|
||||
Lifetime: lifetime,
|
||||
Scope: o.Scopes,
|
||||
Delegates: o.Delegates,
|
||||
}
|
||||
b, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to marshal request: %w", err)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", o.URL, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to create impersonation request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
sourceToken, err := o.Tp.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers.SetAuthHeader(sourceToken, req)
|
||||
logger.DebugContext(ctx, "impersonated token request", "request", internallog.HTTPRequest(req, b))
|
||||
resp, body, err := internal.DoRequest(o.Client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to generate access token: %w", err)
|
||||
}
|
||||
logger.DebugContext(ctx, "impersonated token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
|
||||
return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
|
||||
}
|
||||
|
||||
var accessTokenResp impersonateTokenResponse
|
||||
if err := json.Unmarshal(body, &accessTokenResp); err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to parse response: %w", err)
|
||||
}
|
||||
expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to parse expiry: %w", err)
|
||||
}
|
||||
token := &auth.Token{
|
||||
Value: accessTokenResp.AccessToken,
|
||||
Expiry: expiry,
|
||||
Type: internal.TokenTypeBearer,
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// ExtractServiceAccountEmail extracts the service account email from the impersonation URL.
|
||||
// The impersonation URL is expected to be in the format:
|
||||
// https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{SERVICE_ACCOUNT_EMAIL}:generateAccessToken
|
||||
// or
|
||||
// https://iamcredentials.googleapis.com/v1/projects/{PROJECT_ID}/serviceAccounts/{SERVICE_ACCOUNT_EMAIL}:generateAccessToken
|
||||
// Returns an error if the email cannot be extracted.
|
||||
func ExtractServiceAccountEmail(impersonationURL string) (string, error) {
|
||||
matches := serviceAccountEmailRegex.FindStringSubmatch(impersonationURL)
|
||||
|
||||
if len(matches) < 2 {
|
||||
return "", fmt.Errorf("credentials: invalid impersonation URL format: %s", impersonationURL)
|
||||
}
|
||||
|
||||
return matches[1], nil
|
||||
}
|
||||
167
vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go
generated
vendored
Normal file
167
vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stsexchange
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// GrantType for a sts exchange.
|
||||
GrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
|
||||
// TokenType for a sts exchange.
|
||||
TokenType = "urn:ietf:params:oauth:token-type:access_token"
|
||||
|
||||
jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
|
||||
)
|
||||
|
||||
// Options stores the configuration for making an sts exchange request.
|
||||
type Options struct {
|
||||
Client *http.Client
|
||||
Logger *slog.Logger
|
||||
Endpoint string
|
||||
Request *TokenRequest
|
||||
Authentication ClientAuthentication
|
||||
Headers http.Header
|
||||
// ExtraOpts are optional fields marshalled into the `options` field of the
|
||||
// request body.
|
||||
ExtraOpts map[string]interface{}
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
// RefreshAccessToken performs the token exchange using a refresh token flow.
|
||||
func RefreshAccessToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", opts.RefreshToken)
|
||||
return doRequest(ctx, opts, data)
|
||||
}
|
||||
|
||||
// ExchangeToken performs an oauth2 token exchange with the provided endpoint.
|
||||
func ExchangeToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
|
||||
data := url.Values{}
|
||||
data.Set("audience", opts.Request.Audience)
|
||||
data.Set("grant_type", GrantType)
|
||||
data.Set("requested_token_type", TokenType)
|
||||
data.Set("subject_token_type", opts.Request.SubjectTokenType)
|
||||
data.Set("subject_token", opts.Request.SubjectToken)
|
||||
data.Set("scope", strings.Join(opts.Request.Scope, " "))
|
||||
if opts.ExtraOpts != nil {
|
||||
opts, err := json.Marshal(opts.ExtraOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to marshal additional options: %w", err)
|
||||
}
|
||||
data.Set("options", string(opts))
|
||||
}
|
||||
return doRequest(ctx, opts, data)
|
||||
}
|
||||
|
||||
func doRequest(ctx context.Context, opts *Options, data url.Values) (*TokenResponse, error) {
|
||||
opts.Authentication.InjectAuthentication(data, opts.Headers)
|
||||
encodedData := data.Encode()
|
||||
logger := internallog.New(opts.Logger)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", opts.Endpoint, strings.NewReader(encodedData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to properly build http request: %w", err)
|
||||
|
||||
}
|
||||
for key, list := range opts.Headers {
|
||||
for _, val := range list {
|
||||
req.Header.Add(key, val)
|
||||
}
|
||||
}
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(encodedData)))
|
||||
|
||||
logger.DebugContext(ctx, "sts token request", "request", internallog.HTTPRequest(req, []byte(encodedData)))
|
||||
resp, body, err := internal.DoRequest(opts.Client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: invalid response from Secure Token Server: %w", err)
|
||||
}
|
||||
logger.DebugContext(ctx, "sts token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
|
||||
return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
|
||||
}
|
||||
var stsResp TokenResponse
|
||||
if err := json.Unmarshal(body, &stsResp); err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to unmarshal response body from Secure Token Server: %w", err)
|
||||
}
|
||||
|
||||
return &stsResp, nil
|
||||
}
|
||||
|
||||
// TokenRequest contains fields necessary to make an oauth2 token
|
||||
// exchange.
|
||||
type TokenRequest struct {
|
||||
ActingParty struct {
|
||||
ActorToken string
|
||||
ActorTokenType string
|
||||
}
|
||||
GrantType string
|
||||
Resource string
|
||||
Audience string
|
||||
Scope []string
|
||||
RequestedTokenType string
|
||||
SubjectToken string
|
||||
SubjectTokenType string
|
||||
}
|
||||
|
||||
// TokenResponse is used to decode the remote server response during
|
||||
// an oauth2 token exchange.
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
IssuedTokenType string `json:"issued_token_type"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// ClientAuthentication represents an OAuth client ID and secret and the
|
||||
// mechanism for passing these credentials as stated in rfc6749#2.3.1.
|
||||
type ClientAuthentication struct {
|
||||
AuthStyle auth.Style
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
// InjectAuthentication is used to add authentication to a Secure Token Service
|
||||
// exchange request. It modifies either the passed url.Values or http.Header
|
||||
// depending on the desired authentication format.
|
||||
func (c *ClientAuthentication) InjectAuthentication(values url.Values, headers http.Header) {
|
||||
if c.ClientID == "" || c.ClientSecret == "" || values == nil || headers == nil {
|
||||
return
|
||||
}
|
||||
switch c.AuthStyle {
|
||||
case auth.StyleInHeader:
|
||||
plainHeader := c.ClientID + ":" + c.ClientSecret
|
||||
headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(plainHeader)))
|
||||
default:
|
||||
values.Set("client_id", c.ClientID)
|
||||
values.Set("client_secret", c.ClientSecret)
|
||||
}
|
||||
}
|
||||
89
vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go
generated
vendored
Normal file
89
vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/auth/internal/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
// for testing
|
||||
now func() time.Time = time.Now
|
||||
)
|
||||
|
||||
// configureSelfSignedJWT uses the private key in the service account to create
|
||||
// a JWT without making a network call.
|
||||
func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
if len(opts.scopes()) == 0 && opts.Audience == "" {
|
||||
return nil, errors.New("credentials: both scopes and audience are empty")
|
||||
}
|
||||
signer, err := internal.ParseKey([]byte(f.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: could not parse key: %w", err)
|
||||
}
|
||||
return &selfSignedTokenProvider{
|
||||
email: f.ClientEmail,
|
||||
audience: opts.Audience,
|
||||
scopes: opts.scopes(),
|
||||
signer: signer,
|
||||
pkID: f.PrivateKeyID,
|
||||
logger: opts.logger(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type selfSignedTokenProvider struct {
|
||||
email string
|
||||
audience string
|
||||
scopes []string
|
||||
signer crypto.Signer
|
||||
pkID string
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (tp *selfSignedTokenProvider) Token(context.Context) (*auth.Token, error) {
|
||||
iat := now()
|
||||
exp := iat.Add(time.Hour)
|
||||
scope := strings.Join(tp.scopes, " ")
|
||||
c := &jwt.Claims{
|
||||
Iss: tp.email,
|
||||
Sub: tp.email,
|
||||
Aud: tp.audience,
|
||||
Scope: scope,
|
||||
Iat: iat.Unix(),
|
||||
Exp: exp.Unix(),
|
||||
}
|
||||
h := &jwt.Header{
|
||||
Algorithm: jwt.HeaderAlgRSA256,
|
||||
Type: jwt.HeaderType,
|
||||
KeyID: string(tp.pkID),
|
||||
}
|
||||
tok, err := jwt.EncodeJWS(h, c, tp.signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: could not encode JWT: %w", err)
|
||||
}
|
||||
tp.logger.Debug("created self-signed JWT", "token", tok)
|
||||
return &auth.Token{Value: tok, Type: internal.TokenTypeBearer, Expiry: exp}, nil
|
||||
}
|
||||
254
vendor/cloud.google.com/go/auth/httptransport/httptransport.go
generated
vendored
Normal file
254
vendor/cloud.google.com/go/auth/httptransport/httptransport.go
generated
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package httptransport provides functionality for managing HTTP client
|
||||
// connections to Google Cloud services.
|
||||
package httptransport
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
detect "cloud.google.com/go/auth/credentials"
|
||||
"cloud.google.com/go/auth/internal/transport"
|
||||
"cloud.google.com/go/auth/internal/transport/headers"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
// ClientCertProvider is a function that returns a TLS client certificate to be
|
||||
// used when opening TLS connections. It follows the same semantics as
|
||||
// [crypto/tls.Config.GetClientCertificate].
|
||||
type ClientCertProvider = func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
||||
|
||||
// Options used to configure a [net/http.Client] from [NewClient].
|
||||
type Options struct {
|
||||
// DisableTelemetry disables default telemetry (OpenTelemetry). An example
|
||||
// reason to do so would be to bind custom telemetry that overrides the
|
||||
// defaults.
|
||||
DisableTelemetry bool
|
||||
// DisableAuthentication specifies that no authentication should be used. It
|
||||
// is suitable only for testing and for accessing public resources, like
|
||||
// public Google Cloud Storage buckets.
|
||||
DisableAuthentication bool
|
||||
// Headers are extra HTTP headers that will be appended to every outgoing
|
||||
// request.
|
||||
Headers http.Header
|
||||
// BaseRoundTripper overrides the base transport used for serving requests.
|
||||
// If specified ClientCertProvider is ignored.
|
||||
BaseRoundTripper http.RoundTripper
|
||||
// Endpoint overrides the default endpoint to be used for a service.
|
||||
Endpoint string
|
||||
// APIKey specifies an API key to be used as the basis for authentication.
|
||||
// If set DetectOpts are ignored.
|
||||
APIKey string
|
||||
// Credentials used to add Authorization header to all requests. If set
|
||||
// DetectOpts are ignored.
|
||||
Credentials *auth.Credentials
|
||||
// ClientCertProvider is a function that returns a TLS client certificate to
|
||||
// be used when opening TLS connections. It follows the same semantics as
|
||||
// crypto/tls.Config.GetClientCertificate.
|
||||
ClientCertProvider ClientCertProvider
|
||||
// DetectOpts configures settings for detect Application Default
|
||||
// Credentials.
|
||||
DetectOpts *detect.DetectOptions
|
||||
// UniverseDomain is the default service domain for a given Cloud universe.
|
||||
// The default value is "googleapis.com". This is the universe domain
|
||||
// configured for the client, which will be compared to the universe domain
|
||||
// that is separately configured for the credentials.
|
||||
UniverseDomain string
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
|
||||
// InternalOptions are NOT meant to be set directly by consumers of this
|
||||
// package, they should only be set by generated client code.
|
||||
InternalOptions *InternalOptions
|
||||
}
|
||||
|
||||
func (o *Options) validate() error {
|
||||
if o == nil {
|
||||
return errors.New("httptransport: opts required to be non-nil")
|
||||
}
|
||||
if o.InternalOptions != nil && o.InternalOptions.SkipValidation {
|
||||
return nil
|
||||
}
|
||||
hasCreds := o.APIKey != "" ||
|
||||
o.Credentials != nil ||
|
||||
(o.DetectOpts != nil && len(o.DetectOpts.CredentialsJSON) > 0) ||
|
||||
(o.DetectOpts != nil && o.DetectOpts.CredentialsFile != "")
|
||||
if o.DisableAuthentication && hasCreds {
|
||||
return errors.New("httptransport: DisableAuthentication is incompatible with options that set or detect credentials")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// client returns the client a user set for the detect options or nil if one was
|
||||
// not set.
|
||||
func (o *Options) client() *http.Client {
|
||||
if o.DetectOpts != nil && o.DetectOpts.Client != nil {
|
||||
return o.DetectOpts.Client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options) logger() *slog.Logger {
|
||||
return internallog.New(o.Logger)
|
||||
}
|
||||
|
||||
func (o *Options) resolveDetectOptions() *detect.DetectOptions {
|
||||
io := o.InternalOptions
|
||||
// soft-clone these so we are not updating a ref the user holds and may reuse
|
||||
do := transport.CloneDetectOptions(o.DetectOpts)
|
||||
|
||||
// If scoped JWTs are enabled user provided an aud, allow self-signed JWT.
|
||||
if (io != nil && io.EnableJWTWithScope) || do.Audience != "" {
|
||||
do.UseSelfSignedJWT = true
|
||||
}
|
||||
// Only default scopes if user did not also set an audience.
|
||||
if len(do.Scopes) == 0 && do.Audience == "" && io != nil && len(io.DefaultScopes) > 0 {
|
||||
do.Scopes = make([]string, len(io.DefaultScopes))
|
||||
copy(do.Scopes, io.DefaultScopes)
|
||||
}
|
||||
if len(do.Scopes) == 0 && do.Audience == "" && io != nil {
|
||||
do.Audience = o.InternalOptions.DefaultAudience
|
||||
}
|
||||
if o.ClientCertProvider != nil {
|
||||
tlsConfig := &tls.Config{
|
||||
GetClientCertificate: o.ClientCertProvider,
|
||||
}
|
||||
do.Client = transport.DefaultHTTPClientWithTLS(tlsConfig)
|
||||
do.TokenURL = detect.GoogleMTLSTokenURL
|
||||
}
|
||||
if do.Logger == nil {
|
||||
do.Logger = o.logger()
|
||||
}
|
||||
return do
|
||||
}
|
||||
|
||||
// InternalOptions are only meant to be set by generated client code. These are
|
||||
// not meant to be set directly by consumers of this package. Configuration in
|
||||
// this type is considered EXPERIMENTAL and may be removed at any time in the
|
||||
// future without warning.
|
||||
type InternalOptions struct {
|
||||
// EnableJWTWithScope specifies if scope can be used with self-signed JWT.
|
||||
EnableJWTWithScope bool
|
||||
// DefaultAudience specifies a default audience to be used as the audience
|
||||
// field ("aud") for the JWT token authentication.
|
||||
DefaultAudience string
|
||||
// DefaultEndpointTemplate combined with UniverseDomain specifies the
|
||||
// default endpoint.
|
||||
DefaultEndpointTemplate string
|
||||
// DefaultMTLSEndpoint specifies the default mTLS endpoint.
|
||||
DefaultMTLSEndpoint string
|
||||
// DefaultScopes specifies the default OAuth2 scopes to be used for a
|
||||
// service.
|
||||
DefaultScopes []string
|
||||
// SkipValidation bypasses validation on Options. It should only be used
|
||||
// internally for clients that need more control over their transport.
|
||||
SkipValidation bool
|
||||
// SkipUniverseDomainValidation skips the verification that the universe
|
||||
// domain configured for the client matches the universe domain configured
|
||||
// for the credentials. It should only be used internally for clients that
|
||||
// need more control over their transport. The default is false.
|
||||
SkipUniverseDomainValidation bool
|
||||
// TelemetryAttributes specifies a map of telemetry attributes to be added
|
||||
// to all OpenTelemetry signals, such as tracing and metrics, for purposes
|
||||
// including representing the static identity of the client (e.g., service
|
||||
// name, version). These attributes are expected to be consistent across all
|
||||
// signals to enable cross-signal correlation.
|
||||
//
|
||||
// It should only be used internally by generated clients. Callers should not
|
||||
// modify the map after it is passed in.
|
||||
TelemetryAttributes map[string]string
|
||||
}
|
||||
|
||||
// AddAuthorizationMiddleware adds a middleware to the provided client's
|
||||
// transport that sets the Authorization header with the value produced by the
|
||||
// provided [cloud.google.com/go/auth.Credentials]. An error is returned only
|
||||
// if client or creds is nil.
|
||||
//
|
||||
// This function does not support setting a universe domain value on the client.
|
||||
func AddAuthorizationMiddleware(client *http.Client, creds *auth.Credentials) error {
|
||||
if client == nil || creds == nil {
|
||||
return fmt.Errorf("httptransport: client and tp must not be nil")
|
||||
}
|
||||
base := client.Transport
|
||||
if base == nil {
|
||||
if dt, ok := http.DefaultTransport.(*http.Transport); ok {
|
||||
base = dt.Clone()
|
||||
} else {
|
||||
// Directly reuse the DefaultTransport if the application has
|
||||
// replaced it with an implementation of RoundTripper other than
|
||||
// http.Transport.
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
}
|
||||
client.Transport = &authTransport{
|
||||
creds: creds,
|
||||
base: base,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewClient returns a [net/http.Client] that can be used to communicate with a
|
||||
// Google cloud service, configured with the provided [Options]. It
|
||||
// automatically appends Authorization headers to all outgoing requests.
|
||||
func NewClient(opts *Options) (*http.Client, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tOpts := &transport.Options{
|
||||
Endpoint: opts.Endpoint,
|
||||
ClientCertProvider: opts.ClientCertProvider,
|
||||
Client: opts.client(),
|
||||
UniverseDomain: opts.UniverseDomain,
|
||||
Logger: opts.logger(),
|
||||
}
|
||||
if io := opts.InternalOptions; io != nil {
|
||||
tOpts.DefaultEndpointTemplate = io.DefaultEndpointTemplate
|
||||
tOpts.DefaultMTLSEndpoint = io.DefaultMTLSEndpoint
|
||||
}
|
||||
clientCertProvider, dialTLSContext, err := transport.GetHTTPTransportConfig(tOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseRoundTripper := opts.BaseRoundTripper
|
||||
if baseRoundTripper == nil {
|
||||
baseRoundTripper = defaultBaseTransport(clientCertProvider, dialTLSContext)
|
||||
}
|
||||
// Ensure the token exchange transport uses the same ClientCertProvider as the API transport.
|
||||
opts.ClientCertProvider = clientCertProvider
|
||||
trans, err := newTransport(baseRoundTripper, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: trans,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetAuthHeader uses the provided token to set the Authorization and trust
|
||||
// boundary headers on an http.Request. If the token.Type is empty, the type is
|
||||
// assumed to be Bearer. This is the recommended way to set authorization
|
||||
// headers on a custom http.Request.
|
||||
func SetAuthHeader(token *auth.Token, req *http.Request) {
|
||||
headers.SetAuthHeader(token, req)
|
||||
}
|
||||
235
vendor/cloud.google.com/go/auth/httptransport/transport.go
generated
vendored
Normal file
235
vendor/cloud.google.com/go/auth/httptransport/transport.go
generated
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptransport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/credentials"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/transport"
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
"cloud.google.com/go/auth/internal/transport/headers"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
const (
|
||||
quotaProjectHeaderKey = "X-goog-user-project"
|
||||
)
|
||||
|
||||
func newTransport(base http.RoundTripper, opts *Options) (http.RoundTripper, error) {
|
||||
var headers = opts.Headers
|
||||
ht := &headerTransport{
|
||||
base: base,
|
||||
headers: headers,
|
||||
}
|
||||
var trans http.RoundTripper = ht
|
||||
trans = addOpenTelemetryTransport(trans, opts)
|
||||
switch {
|
||||
case opts.DisableAuthentication:
|
||||
// Do nothing.
|
||||
case opts.APIKey != "":
|
||||
qp := internal.GetQuotaProject(nil, opts.Headers.Get(quotaProjectHeaderKey))
|
||||
if qp != "" {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string, 1)
|
||||
}
|
||||
headers.Set(quotaProjectHeaderKey, qp)
|
||||
}
|
||||
trans = &apiKeyTransport{
|
||||
Transport: trans,
|
||||
Key: opts.APIKey,
|
||||
}
|
||||
default:
|
||||
var creds *auth.Credentials
|
||||
if opts.Credentials != nil {
|
||||
creds = opts.Credentials
|
||||
} else {
|
||||
var err error
|
||||
creds, err = credentials.DetectDefault(opts.resolveDetectOptions())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
qp, err := creds.QuotaProjectID(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if qp != "" {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string, 1)
|
||||
}
|
||||
// Don't overwrite user specified quota
|
||||
if v := headers.Get(quotaProjectHeaderKey); v == "" {
|
||||
headers.Set(quotaProjectHeaderKey, qp)
|
||||
}
|
||||
}
|
||||
var skipUD bool
|
||||
if iOpts := opts.InternalOptions; iOpts != nil {
|
||||
skipUD = iOpts.SkipUniverseDomainValidation
|
||||
}
|
||||
creds.TokenProvider = auth.NewCachedTokenProvider(creds.TokenProvider, nil)
|
||||
trans = &authTransport{
|
||||
base: trans,
|
||||
creds: creds,
|
||||
clientUniverseDomain: opts.UniverseDomain,
|
||||
skipUniverseDomainValidation: skipUD,
|
||||
}
|
||||
}
|
||||
return trans, nil
|
||||
}
|
||||
|
||||
// defaultBaseTransport returns the base HTTP transport.
|
||||
// On App Engine, this is urlfetch.Transport.
|
||||
// Otherwise, use a default transport, taking most defaults from
|
||||
// http.DefaultTransport.
|
||||
// If TLSCertificate is available, set TLSClientConfig as well.
|
||||
func defaultBaseTransport(clientCertSource cert.Provider, dialTLSContext func(context.Context, string, string) (net.Conn, error)) http.RoundTripper {
|
||||
defaultTransport, ok := http.DefaultTransport.(*http.Transport)
|
||||
if !ok {
|
||||
defaultTransport = transport.BaseTransport()
|
||||
}
|
||||
trans := defaultTransport.Clone()
|
||||
trans.MaxIdleConnsPerHost = 100
|
||||
|
||||
if clientCertSource != nil {
|
||||
trans.TLSClientConfig = &tls.Config{
|
||||
GetClientCertificate: clientCertSource,
|
||||
}
|
||||
}
|
||||
if dialTLSContext != nil {
|
||||
// If DialTLSContext is set, TLSClientConfig wil be ignored
|
||||
trans.DialTLSContext = dialTLSContext
|
||||
}
|
||||
|
||||
// Configures the ReadIdleTimeout HTTP/2 option for the
|
||||
// transport. This allows broken idle connections to be pruned more quickly,
|
||||
// preventing the client from attempting to re-use connections that will no
|
||||
// longer work.
|
||||
http2Trans, err := http2.ConfigureTransports(trans)
|
||||
if err == nil {
|
||||
http2Trans.ReadIdleTimeout = time.Second * 31
|
||||
}
|
||||
|
||||
return trans
|
||||
}
|
||||
|
||||
type apiKeyTransport struct {
|
||||
// Key is the API Key to set on requests.
|
||||
Key string
|
||||
// Transport is the underlying HTTP transport.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *apiKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
newReq := *req
|
||||
args := newReq.URL.Query()
|
||||
args.Set("key", t.Key)
|
||||
newReq.URL.RawQuery = args.Encode()
|
||||
return t.Transport.RoundTrip(&newReq)
|
||||
}
|
||||
|
||||
type headerTransport struct {
|
||||
headers http.Header
|
||||
base http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
rt := t.base
|
||||
newReq := *req
|
||||
newReq.Header = make(http.Header)
|
||||
for k, vv := range req.Header {
|
||||
newReq.Header[k] = vv
|
||||
}
|
||||
|
||||
for k, v := range t.headers {
|
||||
newReq.Header[k] = v
|
||||
}
|
||||
|
||||
return rt.RoundTrip(&newReq)
|
||||
}
|
||||
|
||||
func addOpenTelemetryTransport(trans http.RoundTripper, opts *Options) http.RoundTripper {
|
||||
if opts.DisableTelemetry {
|
||||
return trans
|
||||
}
|
||||
return otelhttp.NewTransport(trans)
|
||||
}
|
||||
|
||||
type authTransport struct {
|
||||
creds *auth.Credentials
|
||||
base http.RoundTripper
|
||||
clientUniverseDomain string
|
||||
skipUniverseDomainValidation bool
|
||||
}
|
||||
|
||||
// getClientUniverseDomain returns the default service domain for a given Cloud
|
||||
// universe, with the following precedence:
|
||||
//
|
||||
// 1. A non-empty option.WithUniverseDomain or similar client option.
|
||||
// 2. A non-empty environment variable GOOGLE_CLOUD_UNIVERSE_DOMAIN.
|
||||
// 3. The default value "googleapis.com".
|
||||
//
|
||||
// This is the universe domain configured for the client, which will be compared
|
||||
// to the universe domain that is separately configured for the credentials.
|
||||
func (t *authTransport) getClientUniverseDomain() string {
|
||||
if t.clientUniverseDomain != "" {
|
||||
return t.clientUniverseDomain
|
||||
}
|
||||
if envUD := os.Getenv(internal.UniverseDomainEnvVar); envUD != "" {
|
||||
return envUD
|
||||
}
|
||||
return internal.DefaultUniverseDomain
|
||||
}
|
||||
|
||||
// RoundTrip authorizes and authenticates the request with an
|
||||
// access token from Transport's Source. Per the RoundTripper contract we must
|
||||
// not modify the initial request, so we clone it, and we must close the body
|
||||
// on any errors that happens during our token logic.
|
||||
func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
reqBodyClosed := false
|
||||
if req.Body != nil {
|
||||
defer func() {
|
||||
if !reqBodyClosed {
|
||||
req.Body.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
token, err := t.creds.Token(req.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !t.skipUniverseDomainValidation && token.MetadataString("auth.google.tokenSource") != "compute-metadata" {
|
||||
credentialsUniverseDomain, err := t.creds.UniverseDomain(req.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := transport.ValidateUniverseDomain(t.getClientUniverseDomain(), credentialsUniverseDomain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
req2 := req.Clone(req.Context())
|
||||
headers.SetAuthHeader(token, req2)
|
||||
reqBodyClosed = true
|
||||
return t.base.RoundTrip(req2)
|
||||
}
|
||||
63
vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go
generated
vendored
Normal file
63
vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package credsfile is meant to hide implementation details from the pubic
|
||||
// surface of the detect package. It should not import any other packages in
|
||||
// this module. It is located under the main internal package so other
|
||||
// sub-packages can use these parsed types as well.
|
||||
package credsfile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
// GoogleAppCredsEnvVar is the environment variable for setting the
|
||||
// application default credentials.
|
||||
GoogleAppCredsEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||
userCredsFilename = "application_default_credentials.json"
|
||||
)
|
||||
|
||||
// GetFileNameFromEnv returns the override if provided or detects a filename
|
||||
// from the environment.
|
||||
func GetFileNameFromEnv(override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
return os.Getenv(GoogleAppCredsEnvVar)
|
||||
}
|
||||
|
||||
// GetWellKnownFileName tries to locate the filepath for the user credential
|
||||
// file based on the environment.
|
||||
func GetWellKnownFileName() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("APPDATA"), "gcloud", userCredsFilename)
|
||||
}
|
||||
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", userCredsFilename)
|
||||
}
|
||||
|
||||
// guessUnixHomeDir default to checking for HOME, but not all unix systems have
|
||||
// this set, do have a fallback.
|
||||
func guessUnixHomeDir() string {
|
||||
if v := os.Getenv("HOME"); v != "" {
|
||||
return v
|
||||
}
|
||||
if u, err := user.Current(); err == nil {
|
||||
return u.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
159
vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go
generated
vendored
Normal file
159
vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credsfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Config3LO is the internals of a client creds file.
|
||||
type Config3LO struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
AuthURI string `json:"auth_uri"`
|
||||
TokenURI string `json:"token_uri"`
|
||||
}
|
||||
|
||||
// ClientCredentialsFile representation.
|
||||
type ClientCredentialsFile struct {
|
||||
Web *Config3LO `json:"web"`
|
||||
Installed *Config3LO `json:"installed"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ServiceAccountFile representation.
|
||||
type ServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ProjectID string `json:"project_id"`
|
||||
PrivateKeyID string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
ClientEmail string `json:"client_email"`
|
||||
ClientID string `json:"client_id"`
|
||||
AuthURL string `json:"auth_uri"`
|
||||
TokenURL string `json:"token_uri"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// UserCredentialsFile representation.
|
||||
type UserCredentialsFile struct {
|
||||
Type string `json:"type"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ExternalAccountFile representation.
|
||||
type ExternalAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
Audience string `json:"audience"`
|
||||
SubjectTokenType string `json:"subject_token_type"`
|
||||
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
|
||||
TokenURL string `json:"token_url"`
|
||||
CredentialSource *CredentialSource `json:"credential_source,omitempty"`
|
||||
TokenInfoURL string `json:"token_info_url"`
|
||||
ServiceAccountImpersonation *ServiceAccountImpersonationInfo `json:"service_account_impersonation,omitempty"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
WorkforcePoolUserProject string `json:"workforce_pool_user_project"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ExternalAccountAuthorizedUserFile representation.
|
||||
type ExternalAccountAuthorizedUserFile struct {
|
||||
Type string `json:"type"`
|
||||
Audience string `json:"audience"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenURL string `json:"token_url"`
|
||||
TokenInfoURL string `json:"token_info_url"`
|
||||
RevokeURL string `json:"revoke_url"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// CredentialSource stores the information necessary to retrieve the credentials for the STS exchange.
|
||||
//
|
||||
// One field amongst File, URL, Certificate, and Executable should be filled, depending on the kind of credential in question.
|
||||
// The EnvironmentID should start with AWS if being used for an AWS credential.
|
||||
type CredentialSource struct {
|
||||
File string `json:"file"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Executable *ExecutableConfig `json:"executable,omitempty"`
|
||||
Certificate *CertificateConfig `json:"certificate"`
|
||||
EnvironmentID string `json:"environment_id"` // TODO: Make type for this
|
||||
RegionURL string `json:"region_url"`
|
||||
RegionalCredVerificationURL string `json:"regional_cred_verification_url"`
|
||||
CredVerificationURL string `json:"cred_verification_url"`
|
||||
IMDSv2SessionTokenURL string `json:"imdsv2_session_token_url"`
|
||||
Format *Format `json:"format,omitempty"`
|
||||
}
|
||||
|
||||
// Format describes the format of a [CredentialSource].
|
||||
type Format struct {
|
||||
// Type is either "text" or "json". When not provided "text" type is assumed.
|
||||
Type string `json:"type"`
|
||||
// SubjectTokenFieldName is only required for JSON format. This would be "access_token" for azure.
|
||||
SubjectTokenFieldName string `json:"subject_token_field_name"`
|
||||
}
|
||||
|
||||
// ExecutableConfig represents the command to run for an executable
|
||||
// [CredentialSource].
|
||||
type ExecutableConfig struct {
|
||||
Command string `json:"command"`
|
||||
TimeoutMillis int `json:"timeout_millis"`
|
||||
OutputFile string `json:"output_file"`
|
||||
}
|
||||
|
||||
// CertificateConfig represents the options used to set up X509 based workload
|
||||
// [CredentialSource]
|
||||
type CertificateConfig struct {
|
||||
UseDefaultCertificateConfig bool `json:"use_default_certificate_config"`
|
||||
CertificateConfigLocation string `json:"certificate_config_location"`
|
||||
TrustChainPath string `json:"trust_chain_path"`
|
||||
}
|
||||
|
||||
// ServiceAccountImpersonationInfo has impersonation configuration.
|
||||
type ServiceAccountImpersonationInfo struct {
|
||||
TokenLifetimeSeconds int `json:"token_lifetime_seconds"`
|
||||
}
|
||||
|
||||
// ImpersonatedServiceAccountFile representation.
|
||||
type ImpersonatedServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
|
||||
Delegates []string `json:"delegates"`
|
||||
Scopes []string `json:"scopes"`
|
||||
CredSource json.RawMessage `json:"source_credentials"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// GDCHServiceAccountFile represents the Google Distributed Cloud Hosted (GDCH) service identity file.
|
||||
type GDCHServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
FormatVersion string `json:"format_version"`
|
||||
Project string `json:"project"`
|
||||
Name string `json:"name"`
|
||||
CertPath string `json:"ca_cert_path"`
|
||||
PrivateKeyID string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
TokenURL string `json:"token_uri"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
99
vendor/cloud.google.com/go/auth/internal/credsfile/parse.go
generated
vendored
Normal file
99
vendor/cloud.google.com/go/auth/internal/credsfile/parse.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credsfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// ParseServiceAccount parses bytes into a [ServiceAccountFile].
|
||||
func ParseServiceAccount(b []byte) (*ServiceAccountFile, error) {
|
||||
var f *ServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseClientCredentials parses bytes into a
|
||||
// [credsfile.ClientCredentialsFile].
|
||||
func ParseClientCredentials(b []byte) (*ClientCredentialsFile, error) {
|
||||
var f *ClientCredentialsFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseUserCredentials parses bytes into a [UserCredentialsFile].
|
||||
func ParseUserCredentials(b []byte) (*UserCredentialsFile, error) {
|
||||
var f *UserCredentialsFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseExternalAccount parses bytes into a [ExternalAccountFile].
|
||||
func ParseExternalAccount(b []byte) (*ExternalAccountFile, error) {
|
||||
var f *ExternalAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseExternalAccountAuthorizedUser parses bytes into a
|
||||
// [ExternalAccountAuthorizedUserFile].
|
||||
func ParseExternalAccountAuthorizedUser(b []byte) (*ExternalAccountAuthorizedUserFile, error) {
|
||||
var f *ExternalAccountAuthorizedUserFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseImpersonatedServiceAccount parses bytes into a
|
||||
// [ImpersonatedServiceAccountFile].
|
||||
func ParseImpersonatedServiceAccount(b []byte) (*ImpersonatedServiceAccountFile, error) {
|
||||
var f *ImpersonatedServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseGDCHServiceAccount parses bytes into a [GDCHServiceAccountFile].
|
||||
func ParseGDCHServiceAccount(b []byte) (*GDCHServiceAccountFile, error) {
|
||||
var f *GDCHServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
type fileTypeChecker struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ParseFileType determines the [CredentialType] based on bytes provided.
|
||||
// Only returns error for json.Unmarshal.
|
||||
func ParseFileType(b []byte) (string, error) {
|
||||
var f fileTypeChecker
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return f.Type, nil
|
||||
}
|
||||
285
vendor/cloud.google.com/go/auth/internal/internal.go
generated
vendored
Normal file
285
vendor/cloud.google.com/go/auth/internal/internal.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
// TokenTypeBearer is the auth header prefix for bearer tokens.
|
||||
TokenTypeBearer = "Bearer"
|
||||
|
||||
// QuotaProjectEnvVar is the environment variable for setting the quota
|
||||
// project.
|
||||
QuotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
|
||||
// UniverseDomainEnvVar is the environment variable for setting the default
|
||||
// service domain for a given Cloud universe.
|
||||
UniverseDomainEnvVar = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
|
||||
projectEnvVar = "GOOGLE_CLOUD_PROJECT"
|
||||
maxBodySize = 1 << 20
|
||||
|
||||
// DefaultUniverseDomain is the default value for universe domain.
|
||||
// Universe domain is the default service domain for a given Cloud universe.
|
||||
DefaultUniverseDomain = "googleapis.com"
|
||||
|
||||
// TrustBoundaryNoOp is a constant indicating no trust boundary is enforced.
|
||||
TrustBoundaryNoOp = "0x0"
|
||||
|
||||
// TrustBoundaryDataKey is the key used to store trust boundary data in a token's metadata.
|
||||
TrustBoundaryDataKey = "google.auth.trust_boundary_data"
|
||||
)
|
||||
|
||||
type clonableTransport interface {
|
||||
Clone() *http.Transport
|
||||
}
|
||||
|
||||
// DefaultClient returns an [http.Client] with some defaults set. If
|
||||
// the current [http.DefaultTransport] is a [clonableTransport], as
|
||||
// is the case for an [*http.Transport], the clone will be used.
|
||||
// Otherwise the [http.DefaultTransport] is used directly.
|
||||
func DefaultClient() *http.Client {
|
||||
if transport, ok := http.DefaultTransport.(clonableTransport); ok {
|
||||
return &http.Client{
|
||||
Transport: transport.Clone(),
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: http.DefaultTransport,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseKey converts the binary contents of a private key file
|
||||
// to an crypto.Signer. It detects whether the private key is in a
|
||||
// PEM container or not. If so, it extracts the the private key
|
||||
// from PEM container before conversion. It only supports PEM
|
||||
// containers with no passphrase.
|
||||
func ParseKey(key []byte) (crypto.Signer, error) {
|
||||
block, _ := pem.Decode(key)
|
||||
if block != nil {
|
||||
key = block.Bytes
|
||||
}
|
||||
var parsedKey crypto.PrivateKey
|
||||
|
||||
var errPKCS8, errPKCS1, errEC error
|
||||
if parsedKey, errPKCS8 = x509.ParsePKCS8PrivateKey(key); errPKCS8 != nil {
|
||||
if parsedKey, errPKCS1 = x509.ParsePKCS1PrivateKey(key); errPKCS1 != nil {
|
||||
if parsedKey, errEC = x509.ParseECPrivateKey(key); errEC != nil {
|
||||
return nil, fmt.Errorf("failed to parse private key. Tried PKCS8, PKCS1, and EC formats. Errors: [PKCS8: %v], [PKCS1: %v], [EC: %v]", errPKCS8, errPKCS1, errEC)
|
||||
}
|
||||
}
|
||||
}
|
||||
parsed, ok := parsedKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, errors.New("private key is not a signer")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// GetQuotaProject retrieves quota project with precedence being: override,
|
||||
// environment variable, creds json file.
|
||||
func GetQuotaProject(b []byte, override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
if env := os.Getenv(QuotaProjectEnvVar); env != "" {
|
||||
return env
|
||||
}
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
var v struct {
|
||||
QuotaProject string `json:"quota_project_id"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return ""
|
||||
}
|
||||
return v.QuotaProject
|
||||
}
|
||||
|
||||
// GetProjectID retrieves project with precedence being: override,
|
||||
// environment variable, creds json file.
|
||||
func GetProjectID(b []byte, override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
if env := os.Getenv(projectEnvVar); env != "" {
|
||||
return env
|
||||
}
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
var v struct {
|
||||
ProjectID string `json:"project_id"` // standard service account key
|
||||
Project string `json:"project"` // gdch key
|
||||
}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return ""
|
||||
}
|
||||
if v.ProjectID != "" {
|
||||
return v.ProjectID
|
||||
}
|
||||
return v.Project
|
||||
}
|
||||
|
||||
// DoRequest executes the provided req with the client. It reads the response
|
||||
// body, closes it, and returns it.
|
||||
func DoRequest(client *http.Client, req *http.Request) (*http.Response, []byte, error) {
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ReadAll(io.LimitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
// ReadAll consumes the whole reader and safely reads the content of its body
|
||||
// with some overflow protection.
|
||||
func ReadAll(r io.Reader) ([]byte, error) {
|
||||
return io.ReadAll(io.LimitReader(r, maxBodySize))
|
||||
}
|
||||
|
||||
// StaticCredentialsProperty is a helper for creating static credentials
|
||||
// properties.
|
||||
func StaticCredentialsProperty(s string) StaticProperty {
|
||||
return StaticProperty(s)
|
||||
}
|
||||
|
||||
// StaticProperty always returns that value of the underlying string.
|
||||
type StaticProperty string
|
||||
|
||||
// GetProperty loads the properly value provided the given context.
|
||||
func (p StaticProperty) GetProperty(context.Context) (string, error) {
|
||||
return string(p), nil
|
||||
}
|
||||
|
||||
// ComputeUniverseDomainProvider fetches the credentials universe domain from
|
||||
// the google cloud metadata service.
|
||||
type ComputeUniverseDomainProvider struct {
|
||||
MetadataClient *metadata.Client
|
||||
universeDomainOnce sync.Once
|
||||
universeDomain string
|
||||
universeDomainErr error
|
||||
}
|
||||
|
||||
// GetProperty fetches the credentials universe domain from the google cloud
|
||||
// metadata service.
|
||||
func (c *ComputeUniverseDomainProvider) GetProperty(ctx context.Context) (string, error) {
|
||||
c.universeDomainOnce.Do(func() {
|
||||
c.universeDomain, c.universeDomainErr = getMetadataUniverseDomain(ctx, c.MetadataClient)
|
||||
})
|
||||
if c.universeDomainErr != nil {
|
||||
return "", c.universeDomainErr
|
||||
}
|
||||
return c.universeDomain, nil
|
||||
}
|
||||
|
||||
// httpGetMetadataUniverseDomain is a package var for unit test substitution.
|
||||
var httpGetMetadataUniverseDomain = func(ctx context.Context, client *metadata.Client) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancel()
|
||||
return client.GetWithContext(ctx, "universe/universe-domain")
|
||||
}
|
||||
|
||||
func getMetadataUniverseDomain(ctx context.Context, client *metadata.Client) (string, error) {
|
||||
universeDomain, err := httpGetMetadataUniverseDomain(ctx, client)
|
||||
if err == nil {
|
||||
return universeDomain, nil
|
||||
}
|
||||
if _, ok := err.(metadata.NotDefinedError); ok {
|
||||
// http.StatusNotFound (404)
|
||||
return DefaultUniverseDomain, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// FormatIAMServiceAccountResource sets a service account name in an IAM resource
|
||||
// name.
|
||||
func FormatIAMServiceAccountResource(name string) string {
|
||||
return fmt.Sprintf("projects/-/serviceAccounts/%s", name)
|
||||
}
|
||||
|
||||
// TrustBoundaryData represents the trust boundary data associated with a token.
|
||||
// It contains information about the regions or environments where the token is valid.
|
||||
type TrustBoundaryData struct {
|
||||
// Locations is the list of locations that the token is allowed to be used in.
|
||||
Locations []string
|
||||
// EncodedLocations represents the locations in an encoded format.
|
||||
EncodedLocations string
|
||||
}
|
||||
|
||||
// NewTrustBoundaryData returns a new TrustBoundaryData with the specified locations and encoded locations.
|
||||
func NewTrustBoundaryData(locations []string, encodedLocations string) *TrustBoundaryData {
|
||||
// Ensure consistency by treating a nil slice as an empty slice.
|
||||
if locations == nil {
|
||||
locations = []string{}
|
||||
}
|
||||
locationsCopy := make([]string, len(locations))
|
||||
copy(locationsCopy, locations)
|
||||
return &TrustBoundaryData{
|
||||
Locations: locationsCopy,
|
||||
EncodedLocations: encodedLocations,
|
||||
}
|
||||
}
|
||||
|
||||
// NewNoOpTrustBoundaryData returns a new TrustBoundaryData with no restrictions.
|
||||
func NewNoOpTrustBoundaryData() *TrustBoundaryData {
|
||||
return &TrustBoundaryData{
|
||||
Locations: []string{},
|
||||
EncodedLocations: TrustBoundaryNoOp,
|
||||
}
|
||||
}
|
||||
|
||||
// TrustBoundaryHeader returns the value for the x-allowed-locations header and a bool
|
||||
// indicating if the header should be set. The return values are structured to
|
||||
// handle three distinct states required by the backend:
|
||||
// 1. Header not set: (value="", present=false) -> data is empty.
|
||||
// 2. Header set to an empty string: (value="", present=true) -> data is a no-op.
|
||||
// 3. Header set to a value: (value="...", present=true) -> data has locations.
|
||||
func (t TrustBoundaryData) TrustBoundaryHeader() (value string, present bool) {
|
||||
if t.EncodedLocations == "" {
|
||||
// If the data is empty, the header should not be present.
|
||||
return "", false
|
||||
}
|
||||
|
||||
// If data is not empty, the header should always be present.
|
||||
present = true
|
||||
value = ""
|
||||
if t.EncodedLocations != TrustBoundaryNoOp {
|
||||
value = t.EncodedLocations
|
||||
}
|
||||
// For a no-op, the backend requires an empty string.
|
||||
return value, present
|
||||
}
|
||||
171
vendor/cloud.google.com/go/auth/internal/jwt/jwt.go
generated
vendored
Normal file
171
vendor/cloud.google.com/go/auth/internal/jwt/jwt.go
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// HeaderAlgRSA256 is the RS256 [Header.Algorithm].
|
||||
HeaderAlgRSA256 = "RS256"
|
||||
// HeaderAlgES256 is the ES256 [Header.Algorithm].
|
||||
HeaderAlgES256 = "ES256"
|
||||
// HeaderType is the standard [Header.Type].
|
||||
HeaderType = "JWT"
|
||||
)
|
||||
|
||||
// Header represents a JWT header.
|
||||
type Header struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ"`
|
||||
KeyID string `json:"kid"`
|
||||
}
|
||||
|
||||
func (h *Header) encode() (string, error) {
|
||||
b, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Claims represents the claims set of a JWT.
|
||||
type Claims struct {
|
||||
// Iss is the issuer JWT claim.
|
||||
Iss string `json:"iss"`
|
||||
// Scope is the scope JWT claim.
|
||||
Scope string `json:"scope,omitempty"`
|
||||
// Exp is the expiry JWT claim. If unset, default is in one hour from now.
|
||||
Exp int64 `json:"exp"`
|
||||
// Iat is the subject issued at claim. If unset, default is now.
|
||||
Iat int64 `json:"iat"`
|
||||
// Aud is the audience JWT claim. Optional.
|
||||
Aud string `json:"aud"`
|
||||
// Sub is the subject JWT claim. Optional.
|
||||
Sub string `json:"sub,omitempty"`
|
||||
// AdditionalClaims contains any additional non-standard JWT claims. Optional.
|
||||
AdditionalClaims map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
func (c *Claims) encode() (string, error) {
|
||||
// Compensate for skew
|
||||
now := time.Now().Add(-10 * time.Second)
|
||||
if c.Iat == 0 {
|
||||
c.Iat = now.Unix()
|
||||
}
|
||||
if c.Exp == 0 {
|
||||
c.Exp = now.Add(time.Hour).Unix()
|
||||
}
|
||||
if c.Exp < c.Iat {
|
||||
return "", fmt.Errorf("jwt: invalid Exp = %d; must be later than Iat = %d", c.Exp, c.Iat)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(c.AdditionalClaims) == 0 {
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Marshal private claim set and then append it to b.
|
||||
prv, err := json.Marshal(c.AdditionalClaims)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid map of additional claims %v: %w", c.AdditionalClaims, err)
|
||||
}
|
||||
|
||||
// Concatenate public and private claim JSON objects.
|
||||
if !bytes.HasSuffix(b, []byte{'}'}) {
|
||||
return "", fmt.Errorf("invalid JSON %s", b)
|
||||
}
|
||||
if !bytes.HasPrefix(prv, []byte{'{'}) {
|
||||
return "", fmt.Errorf("invalid JSON %s", prv)
|
||||
}
|
||||
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
|
||||
b = append(b, prv[1:]...) // Append private claims.
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// EncodeJWS encodes the data using the provided key as a JSON web signature.
|
||||
func EncodeJWS(header *Header, c *Claims, signer crypto.Signer) (string, error) {
|
||||
head, err := header.encode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims, err := c.encode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ss := fmt.Sprintf("%s.%s", head, claims)
|
||||
h := sha256.New()
|
||||
h.Write([]byte(ss))
|
||||
sig, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", ss, base64.RawURLEncoding.EncodeToString(sig)), nil
|
||||
}
|
||||
|
||||
// DecodeJWS decodes a claim set from a JWS payload.
|
||||
func DecodeJWS(payload string) (*Claims, error) {
|
||||
// decode returned id token to get expiry
|
||||
s := strings.Split(payload, ".")
|
||||
if len(s) < 2 {
|
||||
return nil, errors.New("invalid token received")
|
||||
}
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Claims{}
|
||||
if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(&c.AdditionalClaims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// VerifyJWS tests whether the provided JWT token's signature was produced by
|
||||
// the private key associated with the provided public key.
|
||||
func VerifyJWS(token string, key *rsa.PublicKey) error {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 3 {
|
||||
return errors.New("jwt: invalid token received, token must have 3 parts")
|
||||
}
|
||||
|
||||
signedContent := parts[0] + "." + parts[1]
|
||||
signatureString, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(signedContent))
|
||||
return rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), signatureString)
|
||||
}
|
||||
117
vendor/cloud.google.com/go/auth/internal/retry/retry.go
generated
vendored
Normal file
117
vendor/cloud.google.com/go/auth/internal/retry/retry.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetryAttempts = 5
|
||||
)
|
||||
|
||||
var (
|
||||
syscallRetryable = func(error) bool { return false }
|
||||
)
|
||||
|
||||
// defaultBackoff is basically equivalent to gax.Backoff without the need for
|
||||
// the dependency.
|
||||
type defaultBackoff struct {
|
||||
max time.Duration
|
||||
mul float64
|
||||
cur time.Duration
|
||||
}
|
||||
|
||||
func (b *defaultBackoff) Pause() time.Duration {
|
||||
d := time.Duration(1 + rand.Int63n(int64(b.cur)))
|
||||
b.cur = time.Duration(float64(b.cur) * b.mul)
|
||||
if b.cur > b.max {
|
||||
b.cur = b.max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Sleep is the equivalent of gax.Sleep without the need for the dependency.
|
||||
func Sleep(ctx context.Context, d time.Duration) error {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new Retryer with the default backoff strategy.
|
||||
func New() *Retryer {
|
||||
return &Retryer{bo: &defaultBackoff{
|
||||
cur: 100 * time.Millisecond,
|
||||
max: 30 * time.Second,
|
||||
mul: 2,
|
||||
}}
|
||||
}
|
||||
|
||||
type backoff interface {
|
||||
Pause() time.Duration
|
||||
}
|
||||
|
||||
// Retryer is a retryer for HTTP requests.
|
||||
type Retryer struct {
|
||||
bo backoff
|
||||
attempts int
|
||||
}
|
||||
|
||||
// Retry determines if a request should be retried.
|
||||
func (r *Retryer) Retry(status int, err error) (time.Duration, bool) {
|
||||
if status == http.StatusOK {
|
||||
return 0, false
|
||||
}
|
||||
retryOk := shouldRetry(status, err)
|
||||
if !retryOk {
|
||||
return 0, false
|
||||
}
|
||||
if r.attempts == maxRetryAttempts {
|
||||
return 0, false
|
||||
}
|
||||
r.attempts++
|
||||
return r.bo.Pause(), true
|
||||
}
|
||||
|
||||
func shouldRetry(status int, err error) bool {
|
||||
if 500 <= status && status <= 599 {
|
||||
return true
|
||||
}
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return true
|
||||
}
|
||||
// Transient network errors should be retried.
|
||||
if syscallRetryable(err) {
|
||||
return true
|
||||
}
|
||||
if err, ok := err.(interface{ Temporary() bool }); ok {
|
||||
if err.Temporary() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if err, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return shouldRetry(status, err.Unwrap())
|
||||
}
|
||||
return false
|
||||
}
|
||||
361
vendor/cloud.google.com/go/auth/internal/transport/cba.go
generated
vendored
Normal file
361
vendor/cloud.google.com/go/auth/internal/transport/cba.go
generated
vendored
Normal file
@@ -0,0 +1,361 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
"github.com/google/s2a-go"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
mTLSModeAlways = "always"
|
||||
mTLSModeNever = "never"
|
||||
mTLSModeAuto = "auto"
|
||||
|
||||
// Experimental: if true, the code will try MTLS with S2A as the default for transport security. Default value is false.
|
||||
googleAPIUseS2AEnv = "EXPERIMENTAL_GOOGLE_API_USE_S2A"
|
||||
googleAPIUseCertSource = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
|
||||
googleAPIUseMTLS = "GOOGLE_API_USE_MTLS_ENDPOINT"
|
||||
googleAPIUseMTLSOld = "GOOGLE_API_USE_MTLS"
|
||||
|
||||
universeDomainPlaceholder = "UNIVERSE_DOMAIN"
|
||||
|
||||
mtlsMDSRoot = "/run/google-mds-mtls/root.crt"
|
||||
mtlsMDSKey = "/run/google-mds-mtls/client.key"
|
||||
)
|
||||
|
||||
// Type represents the type of transport used.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// TransportTypeUnknown represents an unknown transport type and is the default option.
|
||||
TransportTypeUnknown Type = iota
|
||||
// TransportTypeMTLSS2A represents the mTLS transport type using S2A.
|
||||
TransportTypeMTLSS2A
|
||||
)
|
||||
|
||||
// Options is a struct that is duplicated information from the individual
|
||||
// transport packages in order to avoid cyclic deps. It correlates 1:1 with
|
||||
// fields on httptransport.Options and grpctransport.Options.
|
||||
type Options struct {
|
||||
Endpoint string
|
||||
DefaultEndpointTemplate string
|
||||
DefaultMTLSEndpoint string
|
||||
ClientCertProvider cert.Provider
|
||||
Client *http.Client
|
||||
UniverseDomain string
|
||||
EnableDirectPath bool
|
||||
EnableDirectPathXds bool
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// getUniverseDomain returns the default service domain for a given Cloud
|
||||
// universe.
|
||||
func (o *Options) getUniverseDomain() string {
|
||||
if o.UniverseDomain == "" {
|
||||
return internal.DefaultUniverseDomain
|
||||
}
|
||||
return o.UniverseDomain
|
||||
}
|
||||
|
||||
// isUniverseDomainGDU returns true if the universe domain is the default Google
|
||||
// universe.
|
||||
func (o *Options) isUniverseDomainGDU() bool {
|
||||
return o.getUniverseDomain() == internal.DefaultUniverseDomain
|
||||
}
|
||||
|
||||
// defaultEndpoint returns the DefaultEndpointTemplate merged with the
|
||||
// universe domain if the DefaultEndpointTemplate is set, otherwise returns an
|
||||
// empty string.
|
||||
func (o *Options) defaultEndpoint() string {
|
||||
if o.DefaultEndpointTemplate == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Replace(o.DefaultEndpointTemplate, universeDomainPlaceholder, o.getUniverseDomain(), 1)
|
||||
}
|
||||
|
||||
// defaultMTLSEndpoint returns the DefaultMTLSEndpointTemplate merged with the
|
||||
// universe domain if the DefaultMTLSEndpointTemplate is set, otherwise returns an
|
||||
// empty string.
|
||||
func (o *Options) defaultMTLSEndpoint() string {
|
||||
if o.DefaultMTLSEndpoint == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Replace(o.DefaultMTLSEndpoint, universeDomainPlaceholder, o.getUniverseDomain(), 1)
|
||||
}
|
||||
|
||||
// mergedEndpoint merges a user-provided Endpoint of format host[:port] with the
|
||||
// default endpoint.
|
||||
func (o *Options) mergedEndpoint() (string, error) {
|
||||
defaultEndpoint := o.defaultEndpoint()
|
||||
u, err := url.Parse(fixScheme(defaultEndpoint))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Replace(defaultEndpoint, u.Host, o.Endpoint, 1), nil
|
||||
}
|
||||
|
||||
func fixScheme(baseURL string) string {
|
||||
if !strings.Contains(baseURL, "://") {
|
||||
baseURL = "https://" + baseURL
|
||||
}
|
||||
return baseURL
|
||||
}
|
||||
|
||||
// GRPCTransportCredentials embeds interface TransportCredentials with additional data.
|
||||
type GRPCTransportCredentials struct {
|
||||
credentials.TransportCredentials
|
||||
Endpoint string
|
||||
TransportType Type
|
||||
}
|
||||
|
||||
// GetGRPCTransportCredsAndEndpoint returns an instance of
|
||||
// [google.golang.org/grpc/credentials.TransportCredentials], and the
|
||||
// corresponding endpoint and transport type to use for GRPC client.
|
||||
func GetGRPCTransportCredsAndEndpoint(opts *Options) (*GRPCTransportCredentials, error) {
|
||||
config, err := getTransportConfig(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultTransportCreds := credentials.NewTLS(&tls.Config{
|
||||
GetClientCertificate: config.clientCertSource,
|
||||
})
|
||||
|
||||
var s2aAddr string
|
||||
var transportCredsForS2A credentials.TransportCredentials
|
||||
|
||||
if config.mtlsS2AAddress != "" {
|
||||
s2aAddr = config.mtlsS2AAddress
|
||||
transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
|
||||
if err != nil {
|
||||
log.Printf("Loading MTLS MDS credentials failed: %v", err)
|
||||
if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return &GRPCTransportCredentials{defaultTransportCreds, config.endpoint, TransportTypeUnknown}, nil
|
||||
}
|
||||
}
|
||||
} else if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return &GRPCTransportCredentials{defaultTransportCreds, config.endpoint, TransportTypeUnknown}, nil
|
||||
}
|
||||
|
||||
s2aTransportCreds, err := s2a.NewClientCreds(&s2a.ClientOptions{
|
||||
S2AAddress: s2aAddr,
|
||||
TransportCreds: transportCredsForS2A,
|
||||
})
|
||||
if err != nil {
|
||||
// Use default if we cannot initialize S2A client transport credentials.
|
||||
return &GRPCTransportCredentials{defaultTransportCreds, config.endpoint, TransportTypeUnknown}, nil
|
||||
}
|
||||
return &GRPCTransportCredentials{s2aTransportCreds, config.s2aMTLSEndpoint, TransportTypeMTLSS2A}, nil
|
||||
}
|
||||
|
||||
// GetHTTPTransportConfig returns a client certificate source and a function for
|
||||
// dialing MTLS with S2A.
|
||||
func GetHTTPTransportConfig(opts *Options) (cert.Provider, func(context.Context, string, string) (net.Conn, error), error) {
|
||||
config, err := getTransportConfig(opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var s2aAddr string
|
||||
var transportCredsForS2A credentials.TransportCredentials
|
||||
|
||||
if config.mtlsS2AAddress != "" {
|
||||
s2aAddr = config.mtlsS2AAddress
|
||||
transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
|
||||
if err != nil {
|
||||
log.Printf("Loading MTLS MDS credentials failed: %v", err)
|
||||
if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return config.clientCertSource, nil, nil
|
||||
}
|
||||
}
|
||||
} else if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return config.clientCertSource, nil, nil
|
||||
}
|
||||
|
||||
dialTLSContextFunc := s2a.NewS2ADialTLSContextFunc(&s2a.ClientOptions{
|
||||
S2AAddress: s2aAddr,
|
||||
TransportCreds: transportCredsForS2A,
|
||||
})
|
||||
return nil, dialTLSContextFunc, nil
|
||||
}
|
||||
|
||||
func loadMTLSMDSTransportCreds(mtlsMDSRootFile, mtlsMDSKeyFile string) (credentials.TransportCredentials, error) {
|
||||
rootPEM, err := os.ReadFile(mtlsMDSRootFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok := caCertPool.AppendCertsFromPEM(rootPEM)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to load MTLS MDS root certificate")
|
||||
}
|
||||
// The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
|
||||
// followed by a PEM-encoded private key. For this reason, the concatenation is passed in to the
|
||||
// tls.X509KeyPair function as both the certificate chain and private key arguments.
|
||||
cert, err := tls.LoadX509KeyPair(mtlsMDSKeyFile, mtlsMDSKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
return credentials.NewTLS(&tlsConfig), nil
|
||||
}
|
||||
|
||||
func getTransportConfig(opts *Options) (*transportConfig, error) {
|
||||
clientCertSource, err := GetClientCertificateProvider(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint, err := getEndpoint(opts, clientCertSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultTransportConfig := transportConfig{
|
||||
clientCertSource: clientCertSource,
|
||||
endpoint: endpoint,
|
||||
}
|
||||
|
||||
if !shouldUseS2A(clientCertSource, opts) {
|
||||
return &defaultTransportConfig, nil
|
||||
}
|
||||
|
||||
s2aAddress := GetS2AAddress(opts.Logger)
|
||||
mtlsS2AAddress := GetMTLSS2AAddress(opts.Logger)
|
||||
if s2aAddress == "" && mtlsS2AAddress == "" {
|
||||
return &defaultTransportConfig, nil
|
||||
}
|
||||
return &transportConfig{
|
||||
clientCertSource: clientCertSource,
|
||||
endpoint: endpoint,
|
||||
s2aAddress: s2aAddress,
|
||||
mtlsS2AAddress: mtlsS2AAddress,
|
||||
s2aMTLSEndpoint: opts.defaultMTLSEndpoint(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetClientCertificateProvider returns a default client certificate source, if
|
||||
// not provided by the user.
|
||||
//
|
||||
// A nil default source can be returned if the source does not exist. Any exceptions
|
||||
// encountered while initializing the default source will be reported as client
|
||||
// error (ex. corrupt metadata file).
|
||||
func GetClientCertificateProvider(opts *Options) (cert.Provider, error) {
|
||||
if !isClientCertificateEnabled(opts) {
|
||||
return nil, nil
|
||||
} else if opts.ClientCertProvider != nil {
|
||||
return opts.ClientCertProvider, nil
|
||||
}
|
||||
return cert.DefaultProvider()
|
||||
|
||||
}
|
||||
|
||||
// isClientCertificateEnabled returns true by default for all GDU universe domain, unless explicitly overridden by env var
|
||||
func isClientCertificateEnabled(opts *Options) bool {
|
||||
if value, ok := os.LookupEnv(googleAPIUseCertSource); ok {
|
||||
// error as false is OK
|
||||
b, _ := strconv.ParseBool(value)
|
||||
return b
|
||||
}
|
||||
return opts.isUniverseDomainGDU()
|
||||
}
|
||||
|
||||
type transportConfig struct {
|
||||
// The client certificate source.
|
||||
clientCertSource cert.Provider
|
||||
// The corresponding endpoint to use based on client certificate source.
|
||||
endpoint string
|
||||
// The plaintext S2A address if it can be used, otherwise an empty string.
|
||||
s2aAddress string
|
||||
// The MTLS S2A address if it can be used, otherwise an empty string.
|
||||
mtlsS2AAddress string
|
||||
// The MTLS endpoint to use with S2A.
|
||||
s2aMTLSEndpoint string
|
||||
}
|
||||
|
||||
// getEndpoint returns the endpoint for the service, taking into account the
|
||||
// user-provided endpoint override "settings.Endpoint".
|
||||
//
|
||||
// If no endpoint override is specified, we will either return the default
|
||||
// endpoint or the default mTLS endpoint if a client certificate is available.
|
||||
//
|
||||
// You can override the default endpoint choice (mTLS vs. regular) by setting
|
||||
// the GOOGLE_API_USE_MTLS_ENDPOINT environment variable.
|
||||
//
|
||||
// If the endpoint override is an address (host:port) rather than full base
|
||||
// URL (ex. https://...), then the user-provided address will be merged into
|
||||
// the default endpoint. For example, WithEndpoint("myhost:8000") and
|
||||
// DefaultEndpointTemplate("https://UNIVERSE_DOMAIN/bar/baz") will return
|
||||
// "https://myhost:8080/bar/baz". Note that this does not apply to the mTLS
|
||||
// endpoint.
|
||||
func getEndpoint(opts *Options, clientCertSource cert.Provider) (string, error) {
|
||||
if opts.Endpoint == "" {
|
||||
mtlsMode := getMTLSMode()
|
||||
if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
|
||||
return opts.defaultMTLSEndpoint(), nil
|
||||
}
|
||||
return opts.defaultEndpoint(), nil
|
||||
}
|
||||
if strings.Contains(opts.Endpoint, "://") {
|
||||
// User passed in a full URL path, use it verbatim.
|
||||
return opts.Endpoint, nil
|
||||
}
|
||||
if opts.defaultEndpoint() == "" {
|
||||
// If DefaultEndpointTemplate is not configured,
|
||||
// use the user provided endpoint verbatim. This allows a naked
|
||||
// "host[:port]" URL to be used with GRPC Direct Path.
|
||||
return opts.Endpoint, nil
|
||||
}
|
||||
|
||||
// Assume user-provided endpoint is host[:port], merge it with the default endpoint.
|
||||
return opts.mergedEndpoint()
|
||||
}
|
||||
|
||||
func getMTLSMode() string {
|
||||
mode := os.Getenv(googleAPIUseMTLS)
|
||||
if mode == "" {
|
||||
mode = os.Getenv(googleAPIUseMTLSOld) // Deprecated.
|
||||
}
|
||||
if mode == "" {
|
||||
return mTLSModeAuto
|
||||
}
|
||||
return strings.ToLower(mode)
|
||||
}
|
||||
65
vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go
generated
vendored
Normal file
65
vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// defaultCertData holds all the variables pertaining to
|
||||
// the default certificate provider created by [DefaultProvider].
|
||||
//
|
||||
// A singleton model is used to allow the provider to be reused
|
||||
// by the transport layer. As mentioned in [DefaultProvider] (provider nil, nil)
|
||||
// may be returned to indicate a default provider could not be found, which
|
||||
// will skip extra tls config in the transport layer .
|
||||
type defaultCertData struct {
|
||||
once sync.Once
|
||||
provider Provider
|
||||
err error
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCert defaultCertData
|
||||
)
|
||||
|
||||
// Provider is a function that can be passed into crypto/tls.Config.GetClientCertificate.
|
||||
type Provider func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
||||
|
||||
// errSourceUnavailable is a sentinel error to indicate certificate source is unavailable.
|
||||
var errSourceUnavailable = errors.New("certificate source is unavailable")
|
||||
|
||||
// DefaultProvider returns a certificate source using the preferred EnterpriseCertificateProxySource.
|
||||
// If EnterpriseCertificateProxySource is not available, fall back to the legacy SecureConnectSource.
|
||||
//
|
||||
// If neither source is available (due to missing configurations), a nil Source and a nil Error are
|
||||
// returned to indicate that a default certificate source is unavailable.
|
||||
func DefaultProvider() (Provider, error) {
|
||||
defaultCert.once.Do(func() {
|
||||
defaultCert.provider, defaultCert.err = NewWorkloadX509CertProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = NewEnterpriseCertificateProxyProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = NewSecureConnectProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return defaultCert.provider, defaultCert.err
|
||||
}
|
||||
54
vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go
generated
vendored
Normal file
54
vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/googleapis/enterprise-certificate-proxy/client"
|
||||
)
|
||||
|
||||
type ecpSource struct {
|
||||
key *client.Key
|
||||
}
|
||||
|
||||
// NewEnterpriseCertificateProxyProvider creates a certificate source
|
||||
// using the Enterprise Certificate Proxy client, which delegates
|
||||
// certifcate related operations to an OS-specific "signer binary"
|
||||
// that communicates with the native keystore (ex. keychain on MacOS).
|
||||
//
|
||||
// The configFilePath points to a config file containing relevant parameters
|
||||
// such as the certificate issuer and the location of the signer binary.
|
||||
// If configFilePath is empty, the client will attempt to load the config from
|
||||
// a well-known gcloud location.
|
||||
func NewEnterpriseCertificateProxyProvider(configFilePath string) (Provider, error) {
|
||||
key, err := client.Cred(configFilePath)
|
||||
if err != nil {
|
||||
// TODO(codyoss): once this is fixed upstream can handle this error a
|
||||
// little better here. But be safe for now and assume unavailable.
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
|
||||
return (&ecpSource{
|
||||
key: key,
|
||||
}).getClientCertificate, nil
|
||||
}
|
||||
|
||||
func (s *ecpSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
var cert tls.Certificate
|
||||
cert.PrivateKey = s.key
|
||||
cert.Certificate = s.key.CertificateChain()
|
||||
return &cert, nil
|
||||
}
|
||||
124
vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go
generated
vendored
Normal file
124
vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataPath = ".secureConnect"
|
||||
metadataFile = "context_aware_metadata.json"
|
||||
)
|
||||
|
||||
type secureConnectSource struct {
|
||||
metadata secureConnectMetadata
|
||||
|
||||
// Cache the cert to avoid executing helper command repeatedly.
|
||||
cachedCertMutex sync.Mutex
|
||||
cachedCert *tls.Certificate
|
||||
}
|
||||
|
||||
type secureConnectMetadata struct {
|
||||
Cmd []string `json:"cert_provider_command"`
|
||||
}
|
||||
|
||||
// NewSecureConnectProvider creates a certificate source using
|
||||
// the Secure Connect Helper and its associated metadata file.
|
||||
//
|
||||
// The configFilePath points to the location of the context aware metadata file.
|
||||
// If configFilePath is empty, use the default context aware metadata location.
|
||||
func NewSecureConnectProvider(configFilePath string) (Provider, error) {
|
||||
if configFilePath == "" {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
// Error locating the default config means Secure Connect is not supported.
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
configFilePath = filepath.Join(user.HomeDir, metadataPath, metadataFile)
|
||||
}
|
||||
|
||||
file, err := os.ReadFile(configFilePath)
|
||||
if err != nil {
|
||||
// Config file missing means Secure Connect is not supported.
|
||||
// There are non-os.ErrNotExist errors that may be returned.
|
||||
// (e.g. if the home directory is /dev/null, *nix systems will
|
||||
// return ENOTDIR instead of ENOENT)
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
|
||||
var metadata secureConnectMetadata
|
||||
if err := json.Unmarshal(file, &metadata); err != nil {
|
||||
return nil, fmt.Errorf("cert: could not parse JSON in %q: %w", configFilePath, err)
|
||||
}
|
||||
if err := validateMetadata(metadata); err != nil {
|
||||
return nil, fmt.Errorf("cert: invalid config in %q: %w", configFilePath, err)
|
||||
}
|
||||
return (&secureConnectSource{
|
||||
metadata: metadata,
|
||||
}).getClientCertificate, nil
|
||||
}
|
||||
|
||||
func validateMetadata(metadata secureConnectMetadata) error {
|
||||
if len(metadata.Cmd) == 0 {
|
||||
return errors.New("empty cert_provider_command")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureConnectSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
s.cachedCertMutex.Lock()
|
||||
defer s.cachedCertMutex.Unlock()
|
||||
if s.cachedCert != nil && !isCertificateExpired(s.cachedCert) {
|
||||
return s.cachedCert, nil
|
||||
}
|
||||
// Expand OS environment variables in the cert provider command such as "$HOME".
|
||||
for i := 0; i < len(s.metadata.Cmd); i++ {
|
||||
s.metadata.Cmd[i] = os.ExpandEnv(s.metadata.Cmd[i])
|
||||
}
|
||||
command := s.metadata.Cmd
|
||||
data, err := exec.Command(command[0], command[1:]...).Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := tls.X509KeyPair(data, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.cachedCert = &cert
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// isCertificateExpired returns true if the given cert is expired or invalid.
|
||||
func isCertificateExpired(cert *tls.Certificate) bool {
|
||||
if len(cert.Certificate) == 0 {
|
||||
return true
|
||||
}
|
||||
parsed, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return time.Now().After(parsed.NotAfter)
|
||||
}
|
||||
138
vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go
generated
vendored
Normal file
138
vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/googleapis/enterprise-certificate-proxy/client/util"
|
||||
)
|
||||
|
||||
type certConfigs struct {
|
||||
Workload *workloadSource `json:"workload"`
|
||||
}
|
||||
|
||||
type workloadSource struct {
|
||||
CertPath string `json:"cert_path"`
|
||||
KeyPath string `json:"key_path"`
|
||||
}
|
||||
|
||||
type certificateConfig struct {
|
||||
CertConfigs certConfigs `json:"cert_configs"`
|
||||
}
|
||||
|
||||
// getconfigFilePath determines the path to the certificate configuration file.
|
||||
// It first checks for the presence of an environment variable that specifies
|
||||
// the file path. If the environment variable is not set, it falls back to
|
||||
// a default configuration file path.
|
||||
func getconfigFilePath() string {
|
||||
envFilePath := util.GetConfigFilePathFromEnv()
|
||||
if envFilePath != "" {
|
||||
return envFilePath
|
||||
}
|
||||
return util.GetDefaultConfigFilePath()
|
||||
|
||||
}
|
||||
|
||||
// GetCertificatePath retrieves the certificate file path from the provided
|
||||
// configuration file. If the configFilePath is empty, it attempts to load
|
||||
// the configuration from a well-known gcloud location.
|
||||
// This function is exposed to allow other packages, such as the
|
||||
// externalaccount package, to retrieve the certificate path without needing
|
||||
// to load the entire certificate configuration.
|
||||
func GetCertificatePath(configFilePath string) (string, error) {
|
||||
if configFilePath == "" {
|
||||
configFilePath = getconfigFilePath()
|
||||
}
|
||||
certFile, _, err := getCertAndKeyFiles(configFilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return certFile, nil
|
||||
}
|
||||
|
||||
// NewWorkloadX509CertProvider creates a certificate source
|
||||
// that reads a certificate and private key file from the local file system.
|
||||
// This is intended to be used for workload identity federation.
|
||||
//
|
||||
// The configFilePath points to a config file containing relevant parameters
|
||||
// such as the certificate and key file paths.
|
||||
// If configFilePath is empty, the client will attempt to load the config from
|
||||
// a well-known gcloud location.
|
||||
func NewWorkloadX509CertProvider(configFilePath string) (Provider, error) {
|
||||
if configFilePath == "" {
|
||||
configFilePath = getconfigFilePath()
|
||||
}
|
||||
certFile, keyFile, err := getCertAndKeyFiles(configFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source := &workloadSource{
|
||||
CertPath: certFile,
|
||||
KeyPath: keyFile,
|
||||
}
|
||||
return source.getClientCertificate, nil
|
||||
}
|
||||
|
||||
// getClientCertificate attempts to load the certificate and key from the files specified in the
|
||||
// certificate config.
|
||||
func (s *workloadSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(s.CertPath, s.KeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// getCertAndKeyFiles attempts to read the provided config file and return the certificate and private
|
||||
// key file paths.
|
||||
func getCertAndKeyFiles(configFilePath string) (string, string, error) {
|
||||
jsonFile, err := os.Open(configFilePath)
|
||||
if err != nil {
|
||||
return "", "", errSourceUnavailable
|
||||
}
|
||||
|
||||
byteValue, err := io.ReadAll(jsonFile)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var config certificateConfig
|
||||
if err := json.Unmarshal(byteValue, &config); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if config.CertConfigs.Workload == nil {
|
||||
return "", "", errSourceUnavailable
|
||||
}
|
||||
|
||||
certFile := config.CertConfigs.Workload.CertPath
|
||||
keyFile := config.CertConfigs.Workload.KeyPath
|
||||
|
||||
if certFile == "" {
|
||||
return "", "", errors.New("certificate configuration is missing the certificate file location")
|
||||
}
|
||||
|
||||
if keyFile == "" {
|
||||
return "", "", errors.New("certificate configuration is missing the key file location")
|
||||
}
|
||||
|
||||
return certFile, keyFile, nil
|
||||
}
|
||||
61
vendor/cloud.google.com/go/auth/internal/transport/headers/headers.go
generated
vendored
Normal file
61
vendor/cloud.google.com/go/auth/internal/transport/headers/headers.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package headers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
)
|
||||
|
||||
// SetAuthHeader uses the provided token to set the Authorization and trust
|
||||
// boundary headers on a request. If the token.Type is empty, the type is
|
||||
// assumed to be Bearer.
|
||||
func SetAuthHeader(token *auth.Token, req *http.Request) {
|
||||
typ := token.Type
|
||||
if typ == "" {
|
||||
typ = internal.TokenTypeBearer
|
||||
}
|
||||
req.Header.Set("Authorization", typ+" "+token.Value)
|
||||
|
||||
if headerVal, setHeader := getTrustBoundaryHeader(token); setHeader {
|
||||
req.Header.Set("x-allowed-locations", headerVal)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAuthMetadata uses the provided token to set the Authorization and trust
|
||||
// boundary metadata. If the token.Type is empty, the type is assumed to be
|
||||
// Bearer.
|
||||
func SetAuthMetadata(token *auth.Token, m map[string]string) {
|
||||
typ := token.Type
|
||||
if typ == "" {
|
||||
typ = internal.TokenTypeBearer
|
||||
}
|
||||
m["authorization"] = typ + " " + token.Value
|
||||
|
||||
if headerVal, setHeader := getTrustBoundaryHeader(token); setHeader {
|
||||
m["x-allowed-locations"] = headerVal
|
||||
}
|
||||
}
|
||||
|
||||
func getTrustBoundaryHeader(token *auth.Token) (val string, present bool) {
|
||||
if data, ok := token.Metadata[internal.TrustBoundaryDataKey]; ok {
|
||||
if tbd, ok := data.(internal.TrustBoundaryData); ok {
|
||||
return tbd.TrustBoundaryHeader()
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
138
vendor/cloud.google.com/go/auth/internal/transport/s2a.go
generated
vendored
Normal file
138
vendor/cloud.google.com/go/auth/internal/transport/s2a.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
configEndpointSuffix = "instance/platform-security/auto-mtls-configuration"
|
||||
)
|
||||
|
||||
var (
|
||||
mtlsConfiguration *mtlsConfig
|
||||
|
||||
mtlsOnce sync.Once
|
||||
)
|
||||
|
||||
// GetS2AAddress returns the S2A address to be reached via plaintext connection.
|
||||
// Returns empty string if not set or invalid.
|
||||
func GetS2AAddress(logger *slog.Logger) string {
|
||||
getMetadataMTLSAutoConfig(logger)
|
||||
if !mtlsConfiguration.valid() {
|
||||
return ""
|
||||
}
|
||||
return mtlsConfiguration.S2A.PlaintextAddress
|
||||
}
|
||||
|
||||
// GetMTLSS2AAddress returns the S2A address to be reached via MTLS connection.
|
||||
// Returns empty string if not set or invalid.
|
||||
func GetMTLSS2AAddress(logger *slog.Logger) string {
|
||||
getMetadataMTLSAutoConfig(logger)
|
||||
if !mtlsConfiguration.valid() {
|
||||
return ""
|
||||
}
|
||||
return mtlsConfiguration.S2A.MTLSAddress
|
||||
}
|
||||
|
||||
// mtlsConfig contains the configuration for establishing MTLS connections with Google APIs.
|
||||
type mtlsConfig struct {
|
||||
S2A *s2aAddresses `json:"s2a"`
|
||||
}
|
||||
|
||||
func (c *mtlsConfig) valid() bool {
|
||||
return c != nil && c.S2A != nil
|
||||
}
|
||||
|
||||
// s2aAddresses contains the plaintext and/or MTLS S2A addresses.
|
||||
type s2aAddresses struct {
|
||||
// PlaintextAddress is the plaintext address to reach S2A
|
||||
PlaintextAddress string `json:"plaintext_address"`
|
||||
// MTLSAddress is the MTLS address to reach S2A
|
||||
MTLSAddress string `json:"mtls_address"`
|
||||
}
|
||||
|
||||
func getMetadataMTLSAutoConfig(logger *slog.Logger) {
|
||||
var err error
|
||||
mtlsOnce.Do(func() {
|
||||
mtlsConfiguration, err = queryConfig(logger)
|
||||
if err != nil {
|
||||
log.Printf("Getting MTLS config failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var httpGetMetadataMTLSConfig = func(logger *slog.Logger) (string, error) {
|
||||
metadataClient := metadata.NewWithOptions(&metadata.Options{
|
||||
Logger: logger,
|
||||
})
|
||||
return metadataClient.GetWithContext(context.Background(), configEndpointSuffix)
|
||||
}
|
||||
|
||||
func queryConfig(logger *slog.Logger) (*mtlsConfig, error) {
|
||||
resp, err := httpGetMetadataMTLSConfig(logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying MTLS config from MDS endpoint failed: %w", err)
|
||||
}
|
||||
var config mtlsConfig
|
||||
err = json.Unmarshal([]byte(resp), &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshalling MTLS config from MDS endpoint failed: %w", err)
|
||||
}
|
||||
if config.S2A == nil {
|
||||
return nil, fmt.Errorf("returned MTLS config from MDS endpoint is invalid: %v", config)
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func shouldUseS2A(clientCertSource cert.Provider, opts *Options) bool {
|
||||
// If client cert is found, use that over S2A.
|
||||
if clientCertSource != nil {
|
||||
return false
|
||||
}
|
||||
// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
|
||||
if !isGoogleS2AEnabled() {
|
||||
return false
|
||||
}
|
||||
// If DefaultMTLSEndpoint is not set or has endpoint override, skip S2A.
|
||||
if opts.DefaultMTLSEndpoint == "" || opts.Endpoint != "" {
|
||||
return false
|
||||
}
|
||||
// If custom HTTP client is provided, skip S2A.
|
||||
if opts.Client != nil {
|
||||
return false
|
||||
}
|
||||
// If directPath is enabled, skip S2A.
|
||||
return !opts.EnableDirectPath && !opts.EnableDirectPathXds
|
||||
}
|
||||
|
||||
func isGoogleS2AEnabled() bool {
|
||||
b, err := strconv.ParseBool(os.Getenv(googleAPIUseS2AEnv))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return b
|
||||
}
|
||||
107
vendor/cloud.google.com/go/auth/internal/transport/transport.go
generated
vendored
Normal file
107
vendor/cloud.google.com/go/auth/internal/transport/transport.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package transport provided internal helpers for the two transport packages
|
||||
// (grpctransport and httptransport).
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/credentials"
|
||||
)
|
||||
|
||||
// CloneDetectOptions clones a user set detect option into some new memory that
|
||||
// we can internally manipulate before sending onto the detect package.
|
||||
func CloneDetectOptions(oldDo *credentials.DetectOptions) *credentials.DetectOptions {
|
||||
if oldDo == nil {
|
||||
// it is valid for users not to set this, but we will need to to default
|
||||
// some options for them in this case so return some initialized memory
|
||||
// to work with.
|
||||
return &credentials.DetectOptions{}
|
||||
}
|
||||
newDo := &credentials.DetectOptions{
|
||||
// Simple types
|
||||
TokenBindingType: oldDo.TokenBindingType,
|
||||
Audience: oldDo.Audience,
|
||||
Subject: oldDo.Subject,
|
||||
EarlyTokenRefresh: oldDo.EarlyTokenRefresh,
|
||||
TokenURL: oldDo.TokenURL,
|
||||
STSAudience: oldDo.STSAudience,
|
||||
CredentialsFile: oldDo.CredentialsFile,
|
||||
UseSelfSignedJWT: oldDo.UseSelfSignedJWT,
|
||||
UniverseDomain: oldDo.UniverseDomain,
|
||||
|
||||
// These fields are pointer types that we just want to use exactly as
|
||||
// the user set, copy the ref
|
||||
Client: oldDo.Client,
|
||||
Logger: oldDo.Logger,
|
||||
AuthHandlerOptions: oldDo.AuthHandlerOptions,
|
||||
}
|
||||
|
||||
// Smartly size this memory and copy below.
|
||||
if len(oldDo.CredentialsJSON) > 0 {
|
||||
newDo.CredentialsJSON = make([]byte, len(oldDo.CredentialsJSON))
|
||||
copy(newDo.CredentialsJSON, oldDo.CredentialsJSON)
|
||||
}
|
||||
if len(oldDo.Scopes) > 0 {
|
||||
newDo.Scopes = make([]string, len(oldDo.Scopes))
|
||||
copy(newDo.Scopes, oldDo.Scopes)
|
||||
}
|
||||
|
||||
return newDo
|
||||
}
|
||||
|
||||
// ValidateUniverseDomain verifies that the universe domain configured for the
|
||||
// client matches the universe domain configured for the credentials.
|
||||
func ValidateUniverseDomain(clientUniverseDomain, credentialsUniverseDomain string) error {
|
||||
if clientUniverseDomain != credentialsUniverseDomain {
|
||||
return fmt.Errorf(
|
||||
"the configured universe domain (%q) does not match the universe "+
|
||||
"domain found in the credentials (%q). If you haven't configured "+
|
||||
"the universe domain explicitly, \"googleapis.com\" is the default",
|
||||
clientUniverseDomain,
|
||||
credentialsUniverseDomain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultHTTPClientWithTLS constructs an HTTPClient using the provided tlsConfig, to support mTLS.
|
||||
func DefaultHTTPClientWithTLS(tlsConfig *tls.Config) *http.Client {
|
||||
trans := BaseTransport()
|
||||
trans.TLSClientConfig = tlsConfig
|
||||
return &http.Client{Transport: trans}
|
||||
}
|
||||
|
||||
// BaseTransport returns a default [http.Transport] which can be used if
|
||||
// [http.DefaultTransport] has been overwritten.
|
||||
func BaseTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
100
vendor/cloud.google.com/go/auth/internal/trustboundary/external_accounts_config_providers.go
generated
vendored
Normal file
100
vendor/cloud.google.com/go/auth/internal/trustboundary/external_accounts_config_providers.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trustboundary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
workloadAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"
|
||||
workforceAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/locations/global/workforcePools/%s/allowedLocations"
|
||||
)
|
||||
|
||||
var (
|
||||
workforceAudiencePattern = regexp.MustCompile(`//iam\.([^/]+)/locations/global/workforcePools/([^/]+)`)
|
||||
workloadAudiencePattern = regexp.MustCompile(`//iam\.([^/]+)/projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)`)
|
||||
)
|
||||
|
||||
// NewExternalAccountConfigProvider creates a new ConfigProvider for external accounts.
|
||||
func NewExternalAccountConfigProvider(audience, inputUniverseDomain string) (ConfigProvider, error) {
|
||||
var audienceDomain, projectNumber, poolID string
|
||||
var isWorkload bool
|
||||
|
||||
matches := workloadAudiencePattern.FindStringSubmatch(audience)
|
||||
if len(matches) == 4 { // Expecting full match, domain, projectNumber, poolID
|
||||
audienceDomain = matches[1]
|
||||
projectNumber = matches[2]
|
||||
poolID = matches[3]
|
||||
isWorkload = true
|
||||
} else {
|
||||
matches = workforceAudiencePattern.FindStringSubmatch(audience)
|
||||
if len(matches) == 3 { // Expecting full match, domain, poolID
|
||||
audienceDomain = matches[1]
|
||||
poolID = matches[2]
|
||||
isWorkload = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("trustboundary: unknown audience format: %q", audience)
|
||||
}
|
||||
}
|
||||
|
||||
effectiveUniverseDomain := inputUniverseDomain
|
||||
if effectiveUniverseDomain == "" {
|
||||
effectiveUniverseDomain = audienceDomain
|
||||
} else if audienceDomain != "" && effectiveUniverseDomain != audienceDomain {
|
||||
return nil, fmt.Errorf("trustboundary: provided universe domain (%q) does not match domain in audience (%q)", inputUniverseDomain, audienceDomain)
|
||||
}
|
||||
|
||||
if isWorkload {
|
||||
return &workloadIdentityPoolConfigProvider{
|
||||
projectNumber: projectNumber,
|
||||
poolID: poolID,
|
||||
universeDomain: effectiveUniverseDomain,
|
||||
}, nil
|
||||
}
|
||||
return &workforcePoolConfigProvider{
|
||||
poolID: poolID,
|
||||
universeDomain: effectiveUniverseDomain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type workforcePoolConfigProvider struct {
|
||||
poolID string
|
||||
universeDomain string
|
||||
}
|
||||
|
||||
func (p *workforcePoolConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf(workforceAllowedLocationsEndpoint, p.universeDomain, p.poolID), nil
|
||||
}
|
||||
|
||||
func (p *workforcePoolConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
return p.universeDomain, nil
|
||||
}
|
||||
|
||||
type workloadIdentityPoolConfigProvider struct {
|
||||
projectNumber string
|
||||
poolID string
|
||||
universeDomain string
|
||||
}
|
||||
|
||||
func (p *workloadIdentityPoolConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf(workloadAllowedLocationsEndpoint, p.universeDomain, p.projectNumber, p.poolID), nil
|
||||
}
|
||||
|
||||
func (p *workloadIdentityPoolConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
return p.universeDomain, nil
|
||||
}
|
||||
392
vendor/cloud.google.com/go/auth/internal/trustboundary/trust_boundary.go
generated
vendored
Normal file
392
vendor/cloud.google.com/go/auth/internal/trustboundary/trust_boundary.go
generated
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trustboundary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/retry"
|
||||
"cloud.google.com/go/auth/internal/transport/headers"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// serviceAccountAllowedLocationsEndpoint is the URL for fetching allowed locations for a given service account email.
|
||||
serviceAccountAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s/allowedLocations"
|
||||
)
|
||||
|
||||
// isEnabled wraps isTrustBoundaryEnabled with sync.OnceValues to ensure it's
|
||||
// called only once.
|
||||
var isEnabled = sync.OnceValues(isTrustBoundaryEnabled)
|
||||
|
||||
// IsEnabled returns if the trust boundary feature is enabled and an error if
|
||||
// the configuration is invalid. The underlying check is performed only once.
|
||||
func IsEnabled() (bool, error) {
|
||||
return isEnabled()
|
||||
}
|
||||
|
||||
// isTrustBoundaryEnabled checks if the trust boundary feature is enabled via
|
||||
// GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED environment variable.
|
||||
//
|
||||
// If the environment variable is not set, it is considered false.
|
||||
//
|
||||
// The environment variable is interpreted as a boolean with the following
|
||||
// (case-insensitive) rules:
|
||||
// - "true", "1" are considered true.
|
||||
// - "false", "0" are considered false.
|
||||
//
|
||||
// Any other values will return an error.
|
||||
func isTrustBoundaryEnabled() (bool, error) {
|
||||
const envVar = "GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED"
|
||||
val, ok := os.LookupEnv(envVar)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
val = strings.ToLower(val)
|
||||
switch val {
|
||||
case "true", "1":
|
||||
return true, nil
|
||||
case "false", "0":
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf(`invalid value for %s: %q. Must be one of "true", "false", "1", or "0"`, envVar, val)
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigProvider provides specific configuration for trust boundary lookups.
|
||||
type ConfigProvider interface {
|
||||
// GetTrustBoundaryEndpoint returns the endpoint URL for the trust boundary lookup.
|
||||
GetTrustBoundaryEndpoint(ctx context.Context) (url string, err error)
|
||||
// GetUniverseDomain returns the universe domain associated with the credential.
|
||||
// It may return an error if the universe domain cannot be determined.
|
||||
GetUniverseDomain(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
// AllowedLocationsResponse is the structure of the response from the Trust Boundary API.
|
||||
type AllowedLocationsResponse struct {
|
||||
// Locations is the list of allowed locations.
|
||||
Locations []string `json:"locations"`
|
||||
// EncodedLocations is the encoded representation of the allowed locations.
|
||||
EncodedLocations string `json:"encodedLocations"`
|
||||
}
|
||||
|
||||
// fetchTrustBoundaryData fetches the trust boundary data from the API.
|
||||
func fetchTrustBoundaryData(ctx context.Context, client *http.Client, url string, token *auth.Token, logger *slog.Logger) (*internal.TrustBoundaryData, error) {
|
||||
if logger == nil {
|
||||
logger = slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
if client == nil {
|
||||
return nil, errors.New("trustboundary: HTTP client is required")
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
return nil, errors.New("trustboundary: URL cannot be empty")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to create trust boundary request: %w", err)
|
||||
}
|
||||
|
||||
if token == nil || token.Value == "" {
|
||||
return nil, errors.New("trustboundary: access token required for lookup API authentication")
|
||||
}
|
||||
headers.SetAuthHeader(token, req)
|
||||
logger.DebugContext(ctx, "trust boundary request", "request", internallog.HTTPRequest(req, nil))
|
||||
|
||||
retryer := retry.New()
|
||||
var response *http.Response
|
||||
for {
|
||||
response, err = client.Do(req)
|
||||
|
||||
var statusCode int
|
||||
if response != nil {
|
||||
statusCode = response.StatusCode
|
||||
}
|
||||
pause, shouldRetry := retryer.Retry(statusCode, err)
|
||||
|
||||
if !shouldRetry {
|
||||
break
|
||||
}
|
||||
|
||||
if response != nil {
|
||||
// Drain and close the body to reuse the connection
|
||||
io.Copy(io.Discard, response.Body)
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
if err := retry.Sleep(ctx, pause); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to fetch trust boundary: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to read trust boundary response: %w", err)
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "trust boundary response", "response", internallog.HTTPResponse(response, body))
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("trustboundary: trust boundary request failed with status: %s, body: %s", response.Status, string(body))
|
||||
}
|
||||
|
||||
apiResponse := AllowedLocationsResponse{}
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to unmarshal trust boundary response: %w", err)
|
||||
}
|
||||
|
||||
if apiResponse.EncodedLocations == "" {
|
||||
return nil, errors.New("trustboundary: invalid API response: encodedLocations is empty")
|
||||
}
|
||||
|
||||
return internal.NewTrustBoundaryData(apiResponse.Locations, apiResponse.EncodedLocations), nil
|
||||
}
|
||||
|
||||
// serviceAccountConfig holds configuration for SA trust boundary lookups.
|
||||
// It implements the ConfigProvider interface.
|
||||
type serviceAccountConfig struct {
|
||||
ServiceAccountEmail string
|
||||
UniverseDomain string
|
||||
}
|
||||
|
||||
// NewServiceAccountConfigProvider creates a new config for service accounts.
|
||||
func NewServiceAccountConfigProvider(saEmail, universeDomain string) ConfigProvider {
|
||||
return &serviceAccountConfig{
|
||||
ServiceAccountEmail: saEmail,
|
||||
UniverseDomain: universeDomain,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTrustBoundaryEndpoint returns the formatted URL for fetching allowed locations
|
||||
// for the configured service account and universe domain.
|
||||
func (sac *serviceAccountConfig) GetTrustBoundaryEndpoint(ctx context.Context) (url string, err error) {
|
||||
if sac.ServiceAccountEmail == "" {
|
||||
return "", errors.New("trustboundary: service account email cannot be empty for config")
|
||||
}
|
||||
ud := sac.UniverseDomain
|
||||
if ud == "" {
|
||||
ud = internal.DefaultUniverseDomain
|
||||
}
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, ud, sac.ServiceAccountEmail), nil
|
||||
}
|
||||
|
||||
// GetUniverseDomain returns the configured universe domain, defaulting to
|
||||
// [internal.DefaultUniverseDomain] if not explicitly set.
|
||||
func (sac *serviceAccountConfig) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
if sac.UniverseDomain == "" {
|
||||
return internal.DefaultUniverseDomain, nil
|
||||
}
|
||||
return sac.UniverseDomain, nil
|
||||
}
|
||||
|
||||
// DataProvider fetches and caches trust boundary Data.
|
||||
// It implements the DataProvider interface and uses a ConfigProvider
|
||||
// to get type-specific details for the lookup.
|
||||
type DataProvider struct {
|
||||
client *http.Client
|
||||
configProvider ConfigProvider
|
||||
data *internal.TrustBoundaryData
|
||||
logger *slog.Logger
|
||||
base auth.TokenProvider
|
||||
}
|
||||
|
||||
// NewProvider wraps the provided base [auth.TokenProvider] to create a new
|
||||
// provider that injects tokens with trust boundary data. It uses the provided
|
||||
// HTTP client and configProvider to fetch the data and attach it to the token's
|
||||
// metadata.
|
||||
func NewProvider(client *http.Client, configProvider ConfigProvider, logger *slog.Logger, base auth.TokenProvider) (*DataProvider, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("trustboundary: HTTP client cannot be nil for DataProvider")
|
||||
}
|
||||
if configProvider == nil {
|
||||
return nil, errors.New("trustboundary: ConfigProvider cannot be nil for DataProvider")
|
||||
}
|
||||
p := &DataProvider{
|
||||
client: client,
|
||||
configProvider: configProvider,
|
||||
logger: internallog.New(logger),
|
||||
base: base,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Token retrieves a token from the base provider and injects it with trust
|
||||
// boundary data.
|
||||
func (p *DataProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
// Get the original token.
|
||||
token, err := p.base.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tbData, err := p.GetTrustBoundaryData(ctx, token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error fetching the trust boundary data: %w", err)
|
||||
}
|
||||
if tbData != nil {
|
||||
if token.Metadata == nil {
|
||||
token.Metadata = make(map[string]interface{})
|
||||
}
|
||||
token.Metadata[internal.TrustBoundaryDataKey] = *tbData
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GetTrustBoundaryData retrieves the trust boundary data.
|
||||
// It first checks the universe domain: if it's non-default, a NoOp is returned.
|
||||
// Otherwise, it checks a local cache. If the data is not cached as NoOp,
|
||||
// it fetches new data from the endpoint provided by its ConfigProvider,
|
||||
// using the given accessToken for authentication. Results are cached.
|
||||
// If fetching fails, it returns previously cached data if available, otherwise the fetch error.
|
||||
func (p *DataProvider) GetTrustBoundaryData(ctx context.Context, token *auth.Token) (*internal.TrustBoundaryData, error) {
|
||||
// Check the universe domain.
|
||||
uniDomain, err := p.configProvider.GetUniverseDomain(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error getting universe domain: %w", err)
|
||||
}
|
||||
if uniDomain != "" && uniDomain != internal.DefaultUniverseDomain {
|
||||
if p.data == nil || p.data.EncodedLocations != internal.TrustBoundaryNoOp {
|
||||
p.data = internal.NewNoOpTrustBoundaryData()
|
||||
}
|
||||
return p.data, nil
|
||||
}
|
||||
|
||||
// Check cache for a no-op result from a previous API call.
|
||||
cachedData := p.data
|
||||
if cachedData != nil && cachedData.EncodedLocations == internal.TrustBoundaryNoOp {
|
||||
return cachedData, nil
|
||||
}
|
||||
|
||||
// Get the endpoint
|
||||
url, err := p.configProvider.GetTrustBoundaryEndpoint(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error getting the lookup endpoint: %w", err)
|
||||
}
|
||||
|
||||
// Proceed to fetch new data.
|
||||
newData, fetchErr := fetchTrustBoundaryData(ctx, p.client, url, token, p.logger)
|
||||
|
||||
if fetchErr != nil {
|
||||
// Fetch failed. Fallback to cachedData if available.
|
||||
if cachedData != nil {
|
||||
return cachedData, nil // Successful fallback
|
||||
}
|
||||
// No cache to fallback to.
|
||||
return nil, fmt.Errorf("trustboundary: failed to fetch trust boundary data for endpoint %s and no cache available: %w", url, fetchErr)
|
||||
}
|
||||
|
||||
// Fetch successful. Update cache.
|
||||
p.data = newData
|
||||
return newData, nil
|
||||
}
|
||||
|
||||
// GCEConfigProvider implements ConfigProvider for GCE environments.
|
||||
// It lazily fetches and caches the necessary metadata (service account email, universe domain)
|
||||
// from the GCE metadata server.
|
||||
type GCEConfigProvider struct {
|
||||
// universeDomainProvider provides the universe domain and underlying metadata client.
|
||||
universeDomainProvider *internal.ComputeUniverseDomainProvider
|
||||
|
||||
// Caching for service account email
|
||||
saOnce sync.Once
|
||||
saEmail string
|
||||
saEmailErr error
|
||||
|
||||
// Caching for universe domain
|
||||
udOnce sync.Once
|
||||
ud string
|
||||
udErr error
|
||||
}
|
||||
|
||||
// NewGCEConfigProvider creates a new GCEConfigProvider
|
||||
// which uses the provided gceUDP to interact with the GCE metadata server.
|
||||
func NewGCEConfigProvider(gceUDP *internal.ComputeUniverseDomainProvider) *GCEConfigProvider {
|
||||
// The validity of gceUDP and its internal MetadataClient will be checked
|
||||
// within the GetTrustBoundaryEndpoint and GetUniverseDomain methods.
|
||||
return &GCEConfigProvider{
|
||||
universeDomainProvider: gceUDP,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GCEConfigProvider) fetchSA(ctx context.Context) {
|
||||
if g.universeDomainProvider == nil || g.universeDomainProvider.MetadataClient == nil {
|
||||
g.saEmailErr = errors.New("trustboundary: GCEConfigProvider not properly initialized (missing ComputeUniverseDomainProvider or MetadataClient)")
|
||||
return
|
||||
}
|
||||
mdClient := g.universeDomainProvider.MetadataClient
|
||||
saEmail, err := mdClient.EmailWithContext(ctx, "default")
|
||||
if err != nil {
|
||||
g.saEmailErr = fmt.Errorf("trustboundary: GCE config: failed to get service account email: %w", err)
|
||||
return
|
||||
}
|
||||
g.saEmail = saEmail
|
||||
}
|
||||
|
||||
func (g *GCEConfigProvider) fetchUD(ctx context.Context) {
|
||||
if g.universeDomainProvider == nil || g.universeDomainProvider.MetadataClient == nil {
|
||||
g.udErr = errors.New("trustboundary: GCEConfigProvider not properly initialized (missing ComputeUniverseDomainProvider or MetadataClient)")
|
||||
return
|
||||
}
|
||||
ud, err := g.universeDomainProvider.GetProperty(ctx)
|
||||
if err != nil {
|
||||
g.udErr = fmt.Errorf("trustboundary: GCE config: failed to get universe domain: %w", err)
|
||||
return
|
||||
}
|
||||
if ud == "" {
|
||||
ud = internal.DefaultUniverseDomain
|
||||
}
|
||||
g.ud = ud
|
||||
}
|
||||
|
||||
// GetTrustBoundaryEndpoint constructs the trust boundary lookup URL for a GCE environment.
|
||||
// It uses cached metadata (service account email, universe domain) after the first call.
|
||||
func (g *GCEConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
g.saOnce.Do(func() { g.fetchSA(ctx) })
|
||||
if g.saEmailErr != nil {
|
||||
return "", g.saEmailErr
|
||||
}
|
||||
g.udOnce.Do(func() { g.fetchUD(ctx) })
|
||||
if g.udErr != nil {
|
||||
return "", g.udErr
|
||||
}
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, g.ud, g.saEmail), nil
|
||||
}
|
||||
|
||||
// GetUniverseDomain retrieves the universe domain from the GCE metadata server.
|
||||
// It uses a cached value after the first call.
|
||||
func (g *GCEConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
g.udOnce.Do(func() { g.fetchUD(ctx) })
|
||||
if g.udErr != nil {
|
||||
return "", g.udErr
|
||||
}
|
||||
return g.ud, nil
|
||||
}
|
||||
20
vendor/cloud.google.com/go/auth/internal/version.go
generated
vendored
Normal file
20
vendor/cloud.google.com/go/auth/internal/version.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2026 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by gapicgen. DO NOT EDIT.
|
||||
|
||||
package internal
|
||||
|
||||
// Version is the current tagged release of the library.
|
||||
const Version = "0.18.1"
|
||||
82
vendor/cloud.google.com/go/auth/oauth2adapt/CHANGES.md
generated
vendored
Normal file
82
vendor/cloud.google.com/go/auth/oauth2adapt/CHANGES.md
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
# Changelog
|
||||
|
||||
## [0.2.8](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.7...auth/oauth2adapt/v0.2.8) (2025-03-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to 0.37.0 ([1144978](https://github.com/googleapis/google-cloud-go/commit/11449782c7fb4896bf8b8b9cde8e7441c84fb2fd))
|
||||
|
||||
## [0.2.7](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.6...auth/oauth2adapt/v0.2.7) (2025-01-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to v0.33.0 ([e9b0b69](https://github.com/googleapis/google-cloud-go/commit/e9b0b69644ea5b276cacff0a707e8a5e87efafc9))
|
||||
|
||||
## [0.2.6](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.5...auth/oauth2adapt/v0.2.6) (2024-11-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Copy map in tokenSourceAdapter.Token ([#11164](https://github.com/googleapis/google-cloud-go/issues/11164)) ([8cb0cbc](https://github.com/googleapis/google-cloud-go/commit/8cb0cbccdc32886dfb3af49fee04012937d114d2)), refs [#11161](https://github.com/googleapis/google-cloud-go/issues/11161)
|
||||
|
||||
## [0.2.5](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.4...auth/oauth2adapt/v0.2.5) (2024-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Convert token metadata where possible ([#11062](https://github.com/googleapis/google-cloud-go/issues/11062)) ([34bf1c1](https://github.com/googleapis/google-cloud-go/commit/34bf1c164465d66745c0cfdf7cd10a8e2da92e52))
|
||||
|
||||
## [0.2.4](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.3...auth/oauth2adapt/v0.2.4) (2024-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
|
||||
|
||||
## [0.2.3](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.2...auth/oauth2adapt/v0.2.3) (2024-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Bump google.golang.org/api@v0.187.0 ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
|
||||
|
||||
## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.1...auth/oauth2adapt/v0.2.2) (2024-04-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Bump x/net to v0.24.0 ([ba31ed5](https://github.com/googleapis/google-cloud-go/commit/ba31ed5fda2c9664f2e1cf972469295e63deb5b4))
|
||||
|
||||
## [0.2.1](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.0...auth/oauth2adapt/v0.2.1) (2024-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Adapt Token Types to be translated ([#9801](https://github.com/googleapis/google-cloud-go/issues/9801)) ([70f4115](https://github.com/googleapis/google-cloud-go/commit/70f411555ebbf2b71e6d425cc8d2030644c6b438)), refs [#9800](https://github.com/googleapis/google-cloud-go/issues/9800)
|
||||
|
||||
## [0.2.0](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.1.0...auth/oauth2adapt/v0.2.0) (2024-04-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/oauth2adapt:** Add helpers for working with credentials types ([#9694](https://github.com/googleapis/google-cloud-go/issues/9694)) ([cf33b55](https://github.com/googleapis/google-cloud-go/commit/cf33b5514423a2ac5c2a323a1cd99aac34fd4233))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
|
||||
|
||||
## 0.1.0 (2023-10-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/oauth2adapt:** Adds a new module to translate types ([#8595](https://github.com/googleapis/google-cloud-go/issues/8595)) ([6933c5a](https://github.com/googleapis/google-cloud-go/commit/6933c5a0c1fc8e58cbfff8bbca439d671b94672f))
|
||||
* **auth/oauth2adapt:** Fixup deps for release ([#8747](https://github.com/googleapis/google-cloud-go/issues/8747)) ([749d243](https://github.com/googleapis/google-cloud-go/commit/749d243862b025a6487a4d2d339219889b4cfe70))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
|
||||
202
vendor/cloud.google.com/go/auth/oauth2adapt/LICENSE
generated
vendored
Normal file
202
vendor/cloud.google.com/go/auth/oauth2adapt/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
200
vendor/cloud.google.com/go/auth/oauth2adapt/oauth2adapt.go
generated
vendored
Normal file
200
vendor/cloud.google.com/go/auth/oauth2adapt/oauth2adapt.go
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package oauth2adapt helps converts types used in [cloud.google.com/go/auth]
|
||||
// and [golang.org/x/oauth2].
|
||||
package oauth2adapt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
const (
|
||||
oauth2TokenSourceKey = "oauth2.google.tokenSource"
|
||||
oauth2ServiceAccountKey = "oauth2.google.serviceAccount"
|
||||
authTokenSourceKey = "auth.google.tokenSource"
|
||||
authServiceAccountKey = "auth.google.serviceAccount"
|
||||
)
|
||||
|
||||
// TokenProviderFromTokenSource converts any [golang.org/x/oauth2.TokenSource]
|
||||
// into a [cloud.google.com/go/auth.TokenProvider].
|
||||
func TokenProviderFromTokenSource(ts oauth2.TokenSource) auth.TokenProvider {
|
||||
return &tokenProviderAdapter{ts: ts}
|
||||
}
|
||||
|
||||
type tokenProviderAdapter struct {
|
||||
ts oauth2.TokenSource
|
||||
}
|
||||
|
||||
// Token fulfills the [cloud.google.com/go/auth.TokenProvider] interface. It
|
||||
// is a light wrapper around the underlying TokenSource.
|
||||
func (tp *tokenProviderAdapter) Token(context.Context) (*auth.Token, error) {
|
||||
tok, err := tp.ts.Token()
|
||||
if err != nil {
|
||||
var err2 *oauth2.RetrieveError
|
||||
if ok := errors.As(err, &err2); ok {
|
||||
return nil, AuthErrorFromRetrieveError(err2)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// Preserve compute token metadata, for both types of tokens.
|
||||
metadata := map[string]interface{}{}
|
||||
if val, ok := tok.Extra(oauth2TokenSourceKey).(string); ok {
|
||||
metadata[authTokenSourceKey] = val
|
||||
metadata[oauth2TokenSourceKey] = val
|
||||
}
|
||||
if val, ok := tok.Extra(oauth2ServiceAccountKey).(string); ok {
|
||||
metadata[authServiceAccountKey] = val
|
||||
metadata[oauth2ServiceAccountKey] = val
|
||||
}
|
||||
return &auth.Token{
|
||||
Value: tok.AccessToken,
|
||||
Type: tok.Type(),
|
||||
Expiry: tok.Expiry,
|
||||
Metadata: metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TokenSourceFromTokenProvider converts any
|
||||
// [cloud.google.com/go/auth.TokenProvider] into a
|
||||
// [golang.org/x/oauth2.TokenSource].
|
||||
func TokenSourceFromTokenProvider(tp auth.TokenProvider) oauth2.TokenSource {
|
||||
return &tokenSourceAdapter{tp: tp}
|
||||
}
|
||||
|
||||
type tokenSourceAdapter struct {
|
||||
tp auth.TokenProvider
|
||||
}
|
||||
|
||||
// Token fulfills the [golang.org/x/oauth2.TokenSource] interface. It
|
||||
// is a light wrapper around the underlying TokenProvider.
|
||||
func (ts *tokenSourceAdapter) Token() (*oauth2.Token, error) {
|
||||
tok, err := ts.tp.Token(context.Background())
|
||||
if err != nil {
|
||||
var err2 *auth.Error
|
||||
if ok := errors.As(err, &err2); ok {
|
||||
return nil, AddRetrieveErrorToAuthError(err2)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
tok2 := &oauth2.Token{
|
||||
AccessToken: tok.Value,
|
||||
TokenType: tok.Type,
|
||||
Expiry: tok.Expiry,
|
||||
}
|
||||
// Preserve token metadata.
|
||||
m := tok.Metadata
|
||||
if m != nil {
|
||||
// Copy map to avoid concurrent map writes error (#11161).
|
||||
metadata := make(map[string]interface{}, len(m)+2)
|
||||
for k, v := range m {
|
||||
metadata[k] = v
|
||||
}
|
||||
// Append compute token metadata in converted form.
|
||||
if val, ok := metadata[authTokenSourceKey].(string); ok && val != "" {
|
||||
metadata[oauth2TokenSourceKey] = val
|
||||
}
|
||||
if val, ok := metadata[authServiceAccountKey].(string); ok && val != "" {
|
||||
metadata[oauth2ServiceAccountKey] = val
|
||||
}
|
||||
tok2 = tok2.WithExtra(metadata)
|
||||
}
|
||||
return tok2, nil
|
||||
}
|
||||
|
||||
// AuthCredentialsFromOauth2Credentials converts a [golang.org/x/oauth2/google.Credentials]
|
||||
// to a [cloud.google.com/go/auth.Credentials].
|
||||
func AuthCredentialsFromOauth2Credentials(creds *google.Credentials) *auth.Credentials {
|
||||
if creds == nil {
|
||||
return nil
|
||||
}
|
||||
return auth.NewCredentials(&auth.CredentialsOptions{
|
||||
TokenProvider: TokenProviderFromTokenSource(creds.TokenSource),
|
||||
JSON: creds.JSON,
|
||||
ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
|
||||
return creds.ProjectID, nil
|
||||
}),
|
||||
UniverseDomainProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
|
||||
return creds.GetUniverseDomain()
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
// Oauth2CredentialsFromAuthCredentials converts a [cloud.google.com/go/auth.Credentials]
|
||||
// to a [golang.org/x/oauth2/google.Credentials].
|
||||
func Oauth2CredentialsFromAuthCredentials(creds *auth.Credentials) *google.Credentials {
|
||||
if creds == nil {
|
||||
return nil
|
||||
}
|
||||
// Throw away errors as old credentials are not request aware. Also, no
|
||||
// network requests are currently happening for this use case.
|
||||
projectID, _ := creds.ProjectID(context.Background())
|
||||
|
||||
return &google.Credentials{
|
||||
TokenSource: TokenSourceFromTokenProvider(creds.TokenProvider),
|
||||
ProjectID: projectID,
|
||||
JSON: creds.JSON(),
|
||||
UniverseDomainProvider: func() (string, error) {
|
||||
return creds.UniverseDomain(context.Background())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type oauth2Error struct {
|
||||
ErrorCode string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
ErrorURI string `json:"error_uri"`
|
||||
}
|
||||
|
||||
// AddRetrieveErrorToAuthError returns the same error provided and adds a
|
||||
// [golang.org/x/oauth2.RetrieveError] to the error chain by setting the `Err` field on the
|
||||
// [cloud.google.com/go/auth.Error].
|
||||
func AddRetrieveErrorToAuthError(err *auth.Error) *auth.Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
e := &oauth2.RetrieveError{
|
||||
Response: err.Response,
|
||||
Body: err.Body,
|
||||
}
|
||||
err.Err = e
|
||||
if len(err.Body) > 0 {
|
||||
var oErr oauth2Error
|
||||
// ignore the error as it only fills in extra details
|
||||
json.Unmarshal(err.Body, &oErr)
|
||||
e.ErrorCode = oErr.ErrorCode
|
||||
e.ErrorDescription = oErr.ErrorDescription
|
||||
e.ErrorURI = oErr.ErrorURI
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// AuthErrorFromRetrieveError returns an [cloud.google.com/go/auth.Error] that
|
||||
// wraps the provided [golang.org/x/oauth2.RetrieveError].
|
||||
func AuthErrorFromRetrieveError(err *oauth2.RetrieveError) *auth.Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &auth.Error{
|
||||
Response: err.Response,
|
||||
Body: err.Body,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
382
vendor/cloud.google.com/go/auth/threelegged.go
generated
vendored
Normal file
382
vendor/cloud.google.com/go/auth/threelegged.go
generated
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
// AuthorizationHandler is a 3-legged-OAuth helper that prompts the user for
|
||||
// OAuth consent at the specified auth code URL and returns an auth code and
|
||||
// state upon approval.
|
||||
type AuthorizationHandler func(authCodeURL string) (code string, state string, err error)
|
||||
|
||||
// Options3LO are the options for doing a 3-legged OAuth2 flow.
|
||||
type Options3LO struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string
|
||||
// ClientSecret is the application's secret. Not required if AuthHandlerOpts
|
||||
// is set.
|
||||
ClientSecret string
|
||||
// AuthURL is the URL for authenticating.
|
||||
AuthURL string
|
||||
// TokenURL is the URL for retrieving a token.
|
||||
TokenURL string
|
||||
// AuthStyle is used to describe how to client info in the token request.
|
||||
AuthStyle Style
|
||||
// RefreshToken is the token used to refresh the credential. Not required
|
||||
// if AuthHandlerOpts is set.
|
||||
RefreshToken string
|
||||
// RedirectURL is the URL to redirect users to. Optional.
|
||||
RedirectURL string
|
||||
// Scopes specifies requested permissions for the Token. Optional.
|
||||
Scopes []string
|
||||
|
||||
// URLParams are the set of values to apply to the token exchange. Optional.
|
||||
URLParams url.Values
|
||||
// Client is the client to be used to make the underlying token requests.
|
||||
// Optional.
|
||||
Client *http.Client
|
||||
// EarlyTokenExpiry is the time before the token expires that it should be
|
||||
// refreshed. If not set the default value is 3 minutes and 45 seconds.
|
||||
// Optional.
|
||||
EarlyTokenExpiry time.Duration
|
||||
|
||||
// AuthHandlerOpts provides a set of options for doing a
|
||||
// 3-legged OAuth2 flow with a custom [AuthorizationHandler]. Optional.
|
||||
AuthHandlerOpts *AuthorizationHandlerOptions
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (o *Options3LO) validate() error {
|
||||
if o == nil {
|
||||
return errors.New("auth: options must be provided")
|
||||
}
|
||||
if o.ClientID == "" {
|
||||
return errors.New("auth: client ID must be provided")
|
||||
}
|
||||
if o.AuthHandlerOpts == nil && o.ClientSecret == "" {
|
||||
return errors.New("auth: client secret must be provided")
|
||||
}
|
||||
if o.AuthURL == "" {
|
||||
return errors.New("auth: auth URL must be provided")
|
||||
}
|
||||
if o.TokenURL == "" {
|
||||
return errors.New("auth: token URL must be provided")
|
||||
}
|
||||
if o.AuthStyle == StyleUnknown {
|
||||
return errors.New("auth: auth style must be provided")
|
||||
}
|
||||
if o.AuthHandlerOpts == nil && o.RefreshToken == "" {
|
||||
return errors.New("auth: refresh token must be provided")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options3LO) logger() *slog.Logger {
|
||||
return internallog.New(o.Logger)
|
||||
}
|
||||
|
||||
// PKCEOptions holds parameters to support PKCE.
|
||||
type PKCEOptions struct {
|
||||
// Challenge is the un-padded, base64-url-encoded string of the encrypted code verifier.
|
||||
Challenge string // The un-padded, base64-url-encoded string of the encrypted code verifier.
|
||||
// ChallengeMethod is the encryption method (ex. S256).
|
||||
ChallengeMethod string
|
||||
// Verifier is the original, non-encrypted secret.
|
||||
Verifier string // The original, non-encrypted secret.
|
||||
}
|
||||
|
||||
type tokenJSON struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
// error fields
|
||||
ErrorCode string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
ErrorURI string `json:"error_uri"`
|
||||
}
|
||||
|
||||
func (e *tokenJSON) expiry() (t time.Time) {
|
||||
if v := e.ExpiresIn; v != 0 {
|
||||
return time.Now().Add(time.Duration(v) * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Options3LO) client() *http.Client {
|
||||
if o.Client != nil {
|
||||
return o.Client
|
||||
}
|
||||
return internal.DefaultClient()
|
||||
}
|
||||
|
||||
// authCodeURL returns a URL that points to a OAuth2 consent page.
|
||||
func (o *Options3LO) authCodeURL(state string, values url.Values) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(o.AuthURL)
|
||||
v := url.Values{
|
||||
"response_type": {"code"},
|
||||
"client_id": {o.ClientID},
|
||||
}
|
||||
if o.RedirectURL != "" {
|
||||
v.Set("redirect_uri", o.RedirectURL)
|
||||
}
|
||||
if len(o.Scopes) > 0 {
|
||||
v.Set("scope", strings.Join(o.Scopes, " "))
|
||||
}
|
||||
if state != "" {
|
||||
v.Set("state", state)
|
||||
}
|
||||
if o.AuthHandlerOpts != nil {
|
||||
if o.AuthHandlerOpts.PKCEOpts != nil &&
|
||||
o.AuthHandlerOpts.PKCEOpts.Challenge != "" {
|
||||
v.Set(codeChallengeKey, o.AuthHandlerOpts.PKCEOpts.Challenge)
|
||||
}
|
||||
if o.AuthHandlerOpts.PKCEOpts != nil &&
|
||||
o.AuthHandlerOpts.PKCEOpts.ChallengeMethod != "" {
|
||||
v.Set(codeChallengeMethodKey, o.AuthHandlerOpts.PKCEOpts.ChallengeMethod)
|
||||
}
|
||||
}
|
||||
for k := range values {
|
||||
v.Set(k, v.Get(k))
|
||||
}
|
||||
if strings.Contains(o.AuthURL, "?") {
|
||||
buf.WriteByte('&')
|
||||
} else {
|
||||
buf.WriteByte('?')
|
||||
}
|
||||
buf.WriteString(v.Encode())
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// New3LOTokenProvider returns a [TokenProvider] based on the 3-legged OAuth2
|
||||
// configuration. The TokenProvider is caches and auto-refreshes tokens by
|
||||
// default.
|
||||
func New3LOTokenProvider(opts *Options3LO) (TokenProvider, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.AuthHandlerOpts != nil {
|
||||
return new3LOTokenProviderWithAuthHandler(opts), nil
|
||||
}
|
||||
return NewCachedTokenProvider(&tokenProvider3LO{opts: opts, refreshToken: opts.RefreshToken, client: opts.client()}, &CachedTokenProviderOptions{
|
||||
ExpireEarly: opts.EarlyTokenExpiry,
|
||||
}), nil
|
||||
}
|
||||
|
||||
// AuthorizationHandlerOptions provides a set of options to specify for doing a
|
||||
// 3-legged OAuth2 flow with a custom [AuthorizationHandler].
|
||||
type AuthorizationHandlerOptions struct {
|
||||
// AuthorizationHandler specifies the handler used to for the authorization
|
||||
// part of the flow.
|
||||
Handler AuthorizationHandler
|
||||
// State is used verify that the "state" is identical in the request and
|
||||
// response before exchanging the auth code for OAuth2 token.
|
||||
State string
|
||||
// PKCEOpts allows setting configurations for PKCE. Optional.
|
||||
PKCEOpts *PKCEOptions
|
||||
}
|
||||
|
||||
func new3LOTokenProviderWithAuthHandler(opts *Options3LO) TokenProvider {
|
||||
return NewCachedTokenProvider(&tokenProviderWithHandler{opts: opts, state: opts.AuthHandlerOpts.State}, &CachedTokenProviderOptions{
|
||||
ExpireEarly: opts.EarlyTokenExpiry,
|
||||
})
|
||||
}
|
||||
|
||||
// exchange handles the final exchange portion of the 3lo flow. Returns a Token,
|
||||
// refreshToken, and error.
|
||||
func (o *Options3LO) exchange(ctx context.Context, code string) (*Token, string, error) {
|
||||
// Build request
|
||||
v := url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
}
|
||||
if o.RedirectURL != "" {
|
||||
v.Set("redirect_uri", o.RedirectURL)
|
||||
}
|
||||
if o.AuthHandlerOpts != nil &&
|
||||
o.AuthHandlerOpts.PKCEOpts != nil &&
|
||||
o.AuthHandlerOpts.PKCEOpts.Verifier != "" {
|
||||
v.Set(codeVerifierKey, o.AuthHandlerOpts.PKCEOpts.Verifier)
|
||||
}
|
||||
for k := range o.URLParams {
|
||||
v.Set(k, o.URLParams.Get(k))
|
||||
}
|
||||
return fetchToken(ctx, o, v)
|
||||
}
|
||||
|
||||
// This struct is not safe for concurrent access alone, but the way it is used
|
||||
// in this package by wrapping it with a cachedTokenProvider makes it so.
|
||||
type tokenProvider3LO struct {
|
||||
opts *Options3LO
|
||||
client *http.Client
|
||||
refreshToken string
|
||||
}
|
||||
|
||||
func (tp *tokenProvider3LO) Token(ctx context.Context) (*Token, error) {
|
||||
if tp.refreshToken == "" {
|
||||
return nil, errors.New("auth: token expired and refresh token is not set")
|
||||
}
|
||||
v := url.Values{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {tp.refreshToken},
|
||||
}
|
||||
for k := range tp.opts.URLParams {
|
||||
v.Set(k, tp.opts.URLParams.Get(k))
|
||||
}
|
||||
|
||||
tk, rt, err := fetchToken(ctx, tp.opts, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tp.refreshToken != rt && rt != "" {
|
||||
tp.refreshToken = rt
|
||||
}
|
||||
return tk, err
|
||||
}
|
||||
|
||||
type tokenProviderWithHandler struct {
|
||||
opts *Options3LO
|
||||
state string
|
||||
}
|
||||
|
||||
func (tp tokenProviderWithHandler) Token(ctx context.Context) (*Token, error) {
|
||||
url := tp.opts.authCodeURL(tp.state, nil)
|
||||
code, state, err := tp.opts.AuthHandlerOpts.Handler(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if state != tp.state {
|
||||
return nil, errors.New("auth: state mismatch in 3-legged-OAuth flow")
|
||||
}
|
||||
tok, _, err := tp.opts.exchange(ctx, code)
|
||||
return tok, err
|
||||
}
|
||||
|
||||
// fetchToken returns a Token, refresh token, and/or an error.
|
||||
func fetchToken(ctx context.Context, o *Options3LO, v url.Values) (*Token, string, error) {
|
||||
var refreshToken string
|
||||
if o.AuthStyle == StyleInParams {
|
||||
if o.ClientID != "" {
|
||||
v.Set("client_id", o.ClientID)
|
||||
}
|
||||
if o.ClientSecret != "" {
|
||||
v.Set("client_secret", o.ClientSecret)
|
||||
}
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", o.TokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, refreshToken, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if o.AuthStyle == StyleInHeader {
|
||||
req.SetBasicAuth(url.QueryEscape(o.ClientID), url.QueryEscape(o.ClientSecret))
|
||||
}
|
||||
logger := o.logger()
|
||||
|
||||
logger.DebugContext(ctx, "3LO token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
|
||||
// Make request
|
||||
resp, body, err := internal.DoRequest(o.client(), req)
|
||||
if err != nil {
|
||||
return nil, refreshToken, err
|
||||
}
|
||||
logger.DebugContext(ctx, "3LO token response", "response", internallog.HTTPResponse(resp, body))
|
||||
failureStatus := resp.StatusCode < 200 || resp.StatusCode > 299
|
||||
tokError := &Error{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
var token *Token
|
||||
// errors ignored because of default switch on content
|
||||
content, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
switch content {
|
||||
case "application/x-www-form-urlencoded", "text/plain":
|
||||
// some endpoints return a query string
|
||||
vals, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
if failureStatus {
|
||||
return nil, refreshToken, tokError
|
||||
}
|
||||
return nil, refreshToken, fmt.Errorf("auth: cannot parse response: %w", err)
|
||||
}
|
||||
tokError.code = vals.Get("error")
|
||||
tokError.description = vals.Get("error_description")
|
||||
tokError.uri = vals.Get("error_uri")
|
||||
token = &Token{
|
||||
Value: vals.Get("access_token"),
|
||||
Type: vals.Get("token_type"),
|
||||
Metadata: make(map[string]interface{}, len(vals)),
|
||||
}
|
||||
for k, v := range vals {
|
||||
token.Metadata[k] = v
|
||||
}
|
||||
refreshToken = vals.Get("refresh_token")
|
||||
e := vals.Get("expires_in")
|
||||
expires, _ := strconv.Atoi(e)
|
||||
if expires != 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
|
||||
}
|
||||
default:
|
||||
var tj tokenJSON
|
||||
if err = json.Unmarshal(body, &tj); err != nil {
|
||||
if failureStatus {
|
||||
return nil, refreshToken, tokError
|
||||
}
|
||||
return nil, refreshToken, fmt.Errorf("auth: cannot parse json: %w", err)
|
||||
}
|
||||
tokError.code = tj.ErrorCode
|
||||
tokError.description = tj.ErrorDescription
|
||||
tokError.uri = tj.ErrorURI
|
||||
token = &Token{
|
||||
Value: tj.AccessToken,
|
||||
Type: tj.TokenType,
|
||||
Expiry: tj.expiry(),
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
json.Unmarshal(body, &token.Metadata) // optional field, skip err check
|
||||
refreshToken = tj.RefreshToken
|
||||
}
|
||||
// according to spec, servers should respond status 400 in error case
|
||||
// https://www.rfc-editor.org/rfc/rfc6749#section-5.2
|
||||
// but some unorthodox servers respond 200 in error case
|
||||
if failureStatus || tokError.code != "" {
|
||||
return nil, refreshToken, tokError
|
||||
}
|
||||
if token.Value == "" {
|
||||
return nil, refreshToken, errors.New("auth: server response missing access_token")
|
||||
}
|
||||
return token, refreshToken, nil
|
||||
}
|
||||
115
vendor/cloud.google.com/go/compute/metadata/CHANGES.md
generated
vendored
Normal file
115
vendor/cloud.google.com/go/compute/metadata/CHANGES.md
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
# Changes
|
||||
|
||||
## [0.9.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.8.4...compute/metadata/v0.9.0) (2025-09-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Retry on HTTP 429 ([#12932](https://github.com/googleapis/google-cloud-go/issues/12932)) ([1e91f5c](https://github.com/googleapis/google-cloud-go/commit/1e91f5c07acacd38ecdd4ff3e83e092b745e0bc2))
|
||||
|
||||
## [0.8.4](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.8.3...compute/metadata/v0.8.4) (2025-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Set subClient for UseDefaultClient case ([#12911](https://github.com/googleapis/google-cloud-go/issues/12911)) ([9e2646b](https://github.com/googleapis/google-cloud-go/commit/9e2646b1821231183fd775bb107c062865eeaccd))
|
||||
|
||||
## [0.8.3](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.8.2...compute/metadata/v0.8.3) (2025-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Disable Client timeouts for subscription client ([#12910](https://github.com/googleapis/google-cloud-go/issues/12910)) ([187a58a](https://github.com/googleapis/google-cloud-go/commit/187a58a540494e1e8562b046325b8cad8cf7af4a))
|
||||
|
||||
## [0.8.2](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.8.1...compute/metadata/v0.8.2) (2025-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Racy test and uninitialized subClient ([#12892](https://github.com/googleapis/google-cloud-go/issues/12892)) ([4943ca2](https://github.com/googleapis/google-cloud-go/commit/4943ca2bf83908a23806247bc4252dfb440d09cc)), refs [#12888](https://github.com/googleapis/google-cloud-go/issues/12888)
|
||||
|
||||
## [0.8.1](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.8.0...compute/metadata/v0.8.1) (2025-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Use separate client for subscribe methods ([#12885](https://github.com/googleapis/google-cloud-go/issues/12885)) ([76b80f8](https://github.com/googleapis/google-cloud-go/commit/76b80f8df9bf9339d175407e8c15936fe1ac1c9c))
|
||||
|
||||
## [0.8.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.7.0...compute/metadata/v0.8.0) (2025-08-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add Options.UseDefaultClient ([#12657](https://github.com/googleapis/google-cloud-go/issues/12657)) ([1a88209](https://github.com/googleapis/google-cloud-go/commit/1a8820900f20e038291c4bb2c5284a449196e81f)), refs [#11078](https://github.com/googleapis/google-cloud-go/issues/11078)
|
||||
|
||||
## [0.7.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.6.0...compute/metadata/v0.7.0) (2025-05-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Allow canceling GCE detection ([#11786](https://github.com/googleapis/google-cloud-go/issues/11786)) ([78100fe](https://github.com/googleapis/google-cloud-go/commit/78100fe7e28cd30f1e10b47191ac3c9839663b64))
|
||||
|
||||
## [0.6.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.2...compute/metadata/v0.6.0) (2024-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add debug logging ([#11078](https://github.com/googleapis/google-cloud-go/issues/11078)) ([a816814](https://github.com/googleapis/google-cloud-go/commit/a81681463906e4473570a2f426eb0dc2de64e53f))
|
||||
|
||||
## [0.5.2](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.1...compute/metadata/v0.5.2) (2024-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Close Response Body for failed request ([#10891](https://github.com/googleapis/google-cloud-go/issues/10891)) ([e91d45e](https://github.com/googleapis/google-cloud-go/commit/e91d45e4757a9e354114509ba9800085d9e0ff1f))
|
||||
|
||||
## [0.5.1](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.0...compute/metadata/v0.5.1) (2024-09-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Check error chain for retryable error ([#10840](https://github.com/googleapis/google-cloud-go/issues/10840)) ([2bdedef](https://github.com/googleapis/google-cloud-go/commit/2bdedeff621b223d63cebc4355fcf83bc68412cd))
|
||||
|
||||
## [0.5.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.4.0...compute/metadata/v0.5.0) (2024-07-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add sys check for windows OnGCE ([#10521](https://github.com/googleapis/google-cloud-go/issues/10521)) ([3b9a830](https://github.com/googleapis/google-cloud-go/commit/3b9a83063960d2a2ac20beb47cc15818a68bd302))
|
||||
|
||||
## [0.4.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.3.0...compute/metadata/v0.4.0) (2024-07-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add context for all functions/methods ([#10370](https://github.com/googleapis/google-cloud-go/issues/10370)) ([66b8efe](https://github.com/googleapis/google-cloud-go/commit/66b8efe7ad877e052b2987bb4475477e38c67bb3))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* **compute/metadata:** Update OnGCE description ([#10408](https://github.com/googleapis/google-cloud-go/issues/10408)) ([6a46dca](https://github.com/googleapis/google-cloud-go/commit/6a46dca4eae4f88ec6f88822e01e5bf8aeca787f))
|
||||
|
||||
## [0.3.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.3...compute/metadata/v0.3.0) (2024-04-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add context aware functions ([#9733](https://github.com/googleapis/google-cloud-go/issues/9733)) ([e4eb5b4](https://github.com/googleapis/google-cloud-go/commit/e4eb5b46ee2aec9d2fc18300bfd66015e25a0510))
|
||||
|
||||
## [0.2.3](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.2...compute/metadata/v0.2.3) (2022-12-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Switch DNS lookup to an absolute lookup ([119b410](https://github.com/googleapis/google-cloud-go/commit/119b41060c7895e45e48aee5621ad35607c4d021)), refs [#7165](https://github.com/googleapis/google-cloud-go/issues/7165)
|
||||
|
||||
## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.1...compute/metadata/v0.2.2) (2022-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Set IdleConnTimeout for http.Client ([#7084](https://github.com/googleapis/google-cloud-go/issues/7084)) ([766516a](https://github.com/googleapis/google-cloud-go/commit/766516aaf3816bfb3159efeea65aa3d1d205a3e2)), refs [#5430](https://github.com/googleapis/google-cloud-go/issues/5430)
|
||||
|
||||
## [0.1.0] (2022-10-26)
|
||||
|
||||
Initial release of metadata being it's own module.
|
||||
202
vendor/cloud.google.com/go/compute/metadata/LICENSE
generated
vendored
Normal file
202
vendor/cloud.google.com/go/compute/metadata/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
27
vendor/cloud.google.com/go/compute/metadata/README.md
generated
vendored
Normal file
27
vendor/cloud.google.com/go/compute/metadata/README.md
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Compute API
|
||||
|
||||
[](https://pkg.go.dev/cloud.google.com/go/compute/metadata)
|
||||
|
||||
This is a utility library for communicating with Google Cloud metadata service
|
||||
on Google Cloud.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get cloud.google.com/go/compute/metadata
|
||||
```
|
||||
|
||||
## Go Version Support
|
||||
|
||||
See the [Go Versions Supported](https://github.com/googleapis/google-cloud-go#go-versions-supported)
|
||||
section in the root directory's README.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please, see the [CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md)
|
||||
document for details.
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms. See
|
||||
[Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md#contributor-code-of-conduct)
|
||||
for more information.
|
||||
149
vendor/cloud.google.com/go/compute/metadata/log.go
generated
vendored
Normal file
149
vendor/cloud.google.com/go/compute/metadata/log.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Code below this point is copied from github.com/googleapis/gax-go/v2/internallog
|
||||
// to avoid the dependency. The compute/metadata module is used by too many
|
||||
// non-client library modules that can't justify the dependency.
|
||||
|
||||
// The handler returned if logging is not enabled.
|
||||
type noOpHandler struct{}
|
||||
|
||||
func (h noOpHandler) Enabled(_ context.Context, _ slog.Level) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h noOpHandler) Handle(_ context.Context, _ slog.Record) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h noOpHandler) WithAttrs(_ []slog.Attr) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func (h noOpHandler) WithGroup(_ string) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
// httpRequest returns a lazily evaluated [slog.LogValuer] for a
|
||||
// [http.Request] and the associated body.
|
||||
func httpRequest(req *http.Request, body []byte) slog.LogValuer {
|
||||
return &request{
|
||||
req: req,
|
||||
payload: body,
|
||||
}
|
||||
}
|
||||
|
||||
type request struct {
|
||||
req *http.Request
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (r *request) LogValue() slog.Value {
|
||||
if r == nil || r.req == nil {
|
||||
return slog.Value{}
|
||||
}
|
||||
var groupValueAttrs []slog.Attr
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("method", r.req.Method))
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("url", r.req.URL.String()))
|
||||
|
||||
var headerAttr []slog.Attr
|
||||
for k, val := range r.req.Header {
|
||||
headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ",")))
|
||||
}
|
||||
if len(headerAttr) > 0 {
|
||||
groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr))
|
||||
}
|
||||
|
||||
if len(r.payload) > 0 {
|
||||
if attr, ok := processPayload(r.payload); ok {
|
||||
groupValueAttrs = append(groupValueAttrs, attr)
|
||||
}
|
||||
}
|
||||
return slog.GroupValue(groupValueAttrs...)
|
||||
}
|
||||
|
||||
// httpResponse returns a lazily evaluated [slog.LogValuer] for a
|
||||
// [http.Response] and the associated body.
|
||||
func httpResponse(resp *http.Response, body []byte) slog.LogValuer {
|
||||
return &response{
|
||||
resp: resp,
|
||||
payload: body,
|
||||
}
|
||||
}
|
||||
|
||||
type response struct {
|
||||
resp *http.Response
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (r *response) LogValue() slog.Value {
|
||||
if r == nil {
|
||||
return slog.Value{}
|
||||
}
|
||||
var groupValueAttrs []slog.Attr
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("status", fmt.Sprint(r.resp.StatusCode)))
|
||||
|
||||
var headerAttr []slog.Attr
|
||||
for k, val := range r.resp.Header {
|
||||
headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ",")))
|
||||
}
|
||||
if len(headerAttr) > 0 {
|
||||
groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr))
|
||||
}
|
||||
|
||||
if len(r.payload) > 0 {
|
||||
if attr, ok := processPayload(r.payload); ok {
|
||||
groupValueAttrs = append(groupValueAttrs, attr)
|
||||
}
|
||||
}
|
||||
return slog.GroupValue(groupValueAttrs...)
|
||||
}
|
||||
|
||||
func processPayload(payload []byte) (slog.Attr, bool) {
|
||||
peekChar := payload[0]
|
||||
if peekChar == '{' {
|
||||
// JSON object
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(payload, &m); err == nil {
|
||||
return slog.Any("payload", m), true
|
||||
}
|
||||
} else if peekChar == '[' {
|
||||
// JSON array
|
||||
var m []any
|
||||
if err := json.Unmarshal(payload, &m); err == nil {
|
||||
return slog.Any("payload", m), true
|
||||
}
|
||||
} else {
|
||||
// Everything else
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.Compact(buf, payload); err != nil {
|
||||
// Write raw payload incase of error
|
||||
buf.Write(payload)
|
||||
}
|
||||
return slog.String("payload", buf.String()), true
|
||||
}
|
||||
return slog.Attr{}, false
|
||||
}
|
||||
937
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Normal file
937
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Normal file
@@ -0,0 +1,937 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://cloud.google.com/compute/docs/metadata/overview.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
|
||||
userAgent = "gcloud-golang/0.1"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var defaultClient = &Client{
|
||||
hc: newDefaultHTTPClient(true),
|
||||
subClient: newDefaultHTTPClient(false),
|
||||
logger: slog.New(noOpHandler{}),
|
||||
}
|
||||
|
||||
func newDefaultHTTPClient(enableTimeouts bool) *http.Client {
|
||||
transport := &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
}
|
||||
c := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
if enableTimeouts {
|
||||
transport.IdleConnTimeout = 60 * time.Second
|
||||
c.Timeout = 5 * time.Second
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
func (c *cachedValue) get(ctx context.Context, cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = cl.getTrimmed(ctx, c.k)
|
||||
} else {
|
||||
v, err = cl.GetWithContext(ctx, c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Platforms.
|
||||
// NOTE: True returned from `OnGCE` does not guarantee that the metadata server
|
||||
// is accessible from this process and have all the metadata defined.
|
||||
func OnGCE() bool {
|
||||
return OnGCEWithContext(context.Background())
|
||||
}
|
||||
|
||||
// OnGCEWithContext reports whether this process is running on Google Compute Platforms.
|
||||
// This function's return value is memoized for better performance.
|
||||
// NOTE: True returned from `OnGCEWithContext` does not guarantee that the metadata server
|
||||
// is accessible from this process and have all the metadata defined.
|
||||
func OnGCEWithContext(ctx context.Context) bool {
|
||||
onGCEOnce.Do(func() {
|
||||
onGCE = defaultClient.OnGCEWithContext(ctx)
|
||||
})
|
||||
return onGCE
|
||||
}
|
||||
|
||||
// Subscribe calls Client.SubscribeWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [SubscribeWithContext].
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return defaultClient.SubscribeWithContext(context.Background(), suffix, func(ctx context.Context, v string, ok bool) error { return fn(v, ok) })
|
||||
}
|
||||
|
||||
// SubscribeWithContext calls Client.SubscribeWithContext on the default client.
|
||||
func SubscribeWithContext(ctx context.Context, suffix string, fn func(ctx context.Context, v string, ok bool) error) error {
|
||||
return defaultClient.SubscribeWithContext(ctx, suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.GetWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [GetWithContext].
|
||||
func Get(suffix string) (string, error) {
|
||||
return defaultClient.GetWithContext(context.Background(), suffix)
|
||||
}
|
||||
|
||||
// GetWithContext calls Client.GetWithContext on the default client.
|
||||
func GetWithContext(ctx context.Context, suffix string) (string, error) {
|
||||
return defaultClient.GetWithContext(ctx, suffix)
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ProjectIDWithContext].
|
||||
func ProjectID() (string, error) {
|
||||
return defaultClient.ProjectIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ProjectIDWithContext returns the current instance's project ID string.
|
||||
func ProjectIDWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.ProjectIDWithContext(ctx)
|
||||
}
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [NumericProjectIDWithContext].
|
||||
func NumericProjectID() (string, error) {
|
||||
return defaultClient.NumericProjectIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NumericProjectIDWithContext returns the current instance's numeric project ID.
|
||||
func NumericProjectIDWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.NumericProjectIDWithContext(ctx)
|
||||
}
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InternalIPWithContext].
|
||||
func InternalIP() (string, error) {
|
||||
return defaultClient.InternalIPWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InternalIPWithContext returns the instance's primary internal IP address.
|
||||
func InternalIPWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.InternalIPWithContext(ctx)
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ExternalIPWithContext].
|
||||
func ExternalIP() (string, error) {
|
||||
return defaultClient.ExternalIPWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ExternalIPWithContext returns the instance's primary external (public) IP address.
|
||||
func ExternalIPWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.ExternalIPWithContext(ctx)
|
||||
}
|
||||
|
||||
// Email calls Client.EmailWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [EmailWithContext].
|
||||
func Email(serviceAccount string) (string, error) {
|
||||
return defaultClient.EmailWithContext(context.Background(), serviceAccount)
|
||||
}
|
||||
|
||||
// EmailWithContext calls Client.EmailWithContext on the default client.
|
||||
func EmailWithContext(ctx context.Context, serviceAccount string) (string, error) {
|
||||
return defaultClient.EmailWithContext(ctx, serviceAccount)
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [HostnameWithContext].
|
||||
func Hostname() (string, error) {
|
||||
return defaultClient.HostnameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// HostnameWithContext returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func HostnameWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.HostnameWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceTagsWithContext].
|
||||
func InstanceTags() ([]string, error) {
|
||||
return defaultClient.InstanceTagsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceTagsWithContext returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTagsWithContext(ctx context.Context) ([]string, error) {
|
||||
return defaultClient.InstanceTagsWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceIDWithContext].
|
||||
func InstanceID() (string, error) {
|
||||
return defaultClient.InstanceIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceIDWithContext returns the current VM's numeric instance ID.
|
||||
func InstanceIDWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.InstanceIDWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceNameWithContext].
|
||||
func InstanceName() (string, error) {
|
||||
return defaultClient.InstanceNameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceNameWithContext returns the current VM's instance ID string.
|
||||
func InstanceNameWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.InstanceNameWithContext(ctx)
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ZoneWithContext].
|
||||
func Zone() (string, error) {
|
||||
return defaultClient.ZoneWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ZoneWithContext returns the current VM's zone, such as "us-central1-b".
|
||||
func ZoneWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.ZoneWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributesWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceAttributesWithContext.
|
||||
func InstanceAttributes() ([]string, error) {
|
||||
return defaultClient.InstanceAttributesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceAttributesWithContext calls Client.ProjectAttributesWithContext on the default client.
|
||||
func InstanceAttributesWithContext(ctx context.Context) ([]string, error) {
|
||||
return defaultClient.InstanceAttributesWithContext(ctx)
|
||||
}
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributesWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ProjectAttributesWithContext].
|
||||
func ProjectAttributes() ([]string, error) {
|
||||
return defaultClient.ProjectAttributesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ProjectAttributesWithContext calls Client.ProjectAttributesWithContext on the default client.
|
||||
func ProjectAttributesWithContext(ctx context.Context) ([]string, error) {
|
||||
return defaultClient.ProjectAttributesWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValueWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceAttributeValueWithContext].
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValueWithContext(context.Background(), attr)
|
||||
}
|
||||
|
||||
// InstanceAttributeValueWithContext calls Client.InstanceAttributeValueWithContext on the default client.
|
||||
func InstanceAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValueWithContext(ctx, attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValueWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ProjectAttributeValueWithContext].
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValueWithContext(context.Background(), attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValueWithContext calls Client.ProjectAttributeValueWithContext on the default client.
|
||||
func ProjectAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValueWithContext(ctx, attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.ScopesWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ScopesWithContext].
|
||||
func Scopes(serviceAccount string) ([]string, error) {
|
||||
return defaultClient.ScopesWithContext(context.Background(), serviceAccount)
|
||||
}
|
||||
|
||||
// ScopesWithContext calls Client.ScopesWithContext on the default client.
|
||||
func ScopesWithContext(ctx context.Context, serviceAccount string) ([]string, error) {
|
||||
return defaultClient.ScopesWithContext(ctx, serviceAccount)
|
||||
}
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
// subClient by default is a HTTP Client that is only used for subscribe
|
||||
// methods that should not specify a timeout. If the user specifies a client
|
||||
// this with be the same as 'hc'.
|
||||
subClient *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// Options for configuring a [Client].
|
||||
type Options struct {
|
||||
// Client is the HTTP client used to make requests. Optional.
|
||||
// If UseDefaultClient is true, this field is ignored.
|
||||
// If this field is nil, a new default http.Client will be created.
|
||||
Client *http.Client
|
||||
// Logger is used to log information about HTTP request and responses.
|
||||
// If not provided, nothing will be logged. Optional.
|
||||
Logger *slog.Logger
|
||||
// UseDefaultClient specifies that the client should use the same default
|
||||
// internal http.Client that is used in functions such as GetWithContext.
|
||||
// This is useful for sharing a single TCP connection pool across requests.
|
||||
// The difference vs GetWithContext is the ability to use this struct
|
||||
// to provide a custom logger. If this field is true, the Client
|
||||
// field is ignored.
|
||||
UseDefaultClient bool
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata.
|
||||
// Returns the client that uses the specified http.Client for HTTP requests.
|
||||
// If nil is specified, returns the default internal Client that is
|
||||
// also used in functions such as GetWithContext. This is useful for sharing
|
||||
// a single TCP connection pool across requests.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
if c == nil {
|
||||
// Preserve original behavior for nil argument.
|
||||
return defaultClient
|
||||
}
|
||||
// Return a new client with a no-op logger for backward compatibility.
|
||||
return &Client{hc: c, subClient: c, logger: slog.New(noOpHandler{})}
|
||||
}
|
||||
|
||||
// NewWithOptions returns a Client that is configured with the provided Options.
|
||||
func NewWithOptions(opts *Options) *Client {
|
||||
// Preserve original behavior for nil opts.
|
||||
if opts == nil {
|
||||
return defaultClient
|
||||
}
|
||||
|
||||
// Handle explicit request for the internal default http.Client.
|
||||
if opts.UseDefaultClient {
|
||||
logger := opts.Logger
|
||||
if logger == nil {
|
||||
logger = slog.New(noOpHandler{})
|
||||
}
|
||||
return &Client{hc: defaultClient.hc, subClient: defaultClient.subClient, logger: logger}
|
||||
}
|
||||
|
||||
// Handle isolated client creation.
|
||||
client := opts.Client
|
||||
subClient := opts.Client
|
||||
if client == nil {
|
||||
client = newDefaultHTTPClient(true)
|
||||
subClient = newDefaultHTTPClient(false)
|
||||
}
|
||||
logger := opts.Logger
|
||||
if logger == nil {
|
||||
logger = slog.New(noOpHandler{})
|
||||
}
|
||||
return &Client{hc: client, subClient: subClient, logger: logger}
|
||||
}
|
||||
|
||||
// NOTE: metadataRequestStrategy is assigned to a variable for test stubbing purposes.
|
||||
var metadataRequestStrategy = func(ctx context.Context, httpClient *http.Client, resc chan bool) {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := httpClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}
|
||||
|
||||
// NOTE: dnsRequestStrategy is assigned to a variable for test stubbing purposes.
|
||||
var dnsRequestStrategy = func(ctx context.Context, resc chan bool) {
|
||||
resolver := &net.Resolver{}
|
||||
addrs, err := resolver.LookupHost(ctx, "metadata.google.internal.")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}
|
||||
|
||||
// OnGCEWithContext reports whether this process is running on Google Compute Platforms.
|
||||
// NOTE: True returned from `OnGCEWithContext` does not guarantee that the metadata server
|
||||
// is accessible from this process and have all the metadata defined.
|
||||
func (c *Client) OnGCEWithContext(ctx context.Context) bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go metadataRequestStrategy(ctx, c.hc, resc)
|
||||
go dnsRequestStrategy(ctx, resc)
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
// Ensure cancellations from the calling context are respected.
|
||||
waitContext, cancelWait := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancelWait()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-waitContext.Done():
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(ctx context.Context, suffix string) (value, etag string, err error) {
|
||||
return c.getETagWithSubClient(ctx, suffix, false)
|
||||
}
|
||||
|
||||
func (c *Client) getETagWithSubClient(ctx context.Context, suffix string, enableSubClient bool) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
suffix = strings.TrimLeft(suffix, "/")
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
var res *http.Response
|
||||
var reqErr error
|
||||
var body []byte
|
||||
retryer := newRetryer()
|
||||
hc := c.hc
|
||||
if enableSubClient {
|
||||
hc = c.subClient
|
||||
}
|
||||
for {
|
||||
c.logger.DebugContext(ctx, "metadata request", "request", httpRequest(req, nil))
|
||||
res, reqErr = hc.Do(req)
|
||||
var code int
|
||||
if res != nil {
|
||||
code = res.StatusCode
|
||||
body, err = io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
res.Body.Close()
|
||||
return "", "", err
|
||||
}
|
||||
c.logger.DebugContext(ctx, "metadata response", "response", httpResponse(res, body))
|
||||
res.Body.Close()
|
||||
}
|
||||
if delay, shouldRetry := retryer.Retry(code, reqErr); shouldRetry {
|
||||
if res != nil && res.Body != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
if err := sleep(ctx, delay); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if reqErr != nil {
|
||||
return "", "", reqErr
|
||||
}
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(body)}
|
||||
}
|
||||
return string(body), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.GetWithContext].
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
return c.GetWithContext(context.Background(), suffix)
|
||||
}
|
||||
|
||||
// GetWithContext returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// NOTE: Without an extra deadline in the context this call can take in the
|
||||
// worst case, with internal backoff retries, up to 15 seconds (e.g. when server
|
||||
// is responding slowly). Pass context with additional timeouts when needed.
|
||||
func (c *Client) GetWithContext(ctx context.Context, suffix string) (string, error) {
|
||||
val, _, err := c.getETag(ctx, suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(ctx context.Context, suffix string) (s string, err error) {
|
||||
s, err = c.GetWithContext(ctx, suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(ctx context.Context, suffix string) ([]string, error) {
|
||||
j, err := c.GetWithContext(ctx, suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ProjectIDWithContext].
|
||||
func (c *Client) ProjectID() (string, error) { return c.ProjectIDWithContext(context.Background()) }
|
||||
|
||||
// ProjectIDWithContext returns the current instance's project ID string.
|
||||
func (c *Client) ProjectIDWithContext(ctx context.Context) (string, error) { return projID.get(ctx, c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.NumericProjectIDWithContext].
|
||||
func (c *Client) NumericProjectID() (string, error) {
|
||||
return c.NumericProjectIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NumericProjectIDWithContext returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectIDWithContext(ctx context.Context) (string, error) {
|
||||
return projNum.get(ctx, c)
|
||||
}
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceIDWithContext].
|
||||
func (c *Client) InstanceID() (string, error) {
|
||||
return c.InstanceIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceIDWithContext returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceIDWithContext(ctx context.Context) (string, error) {
|
||||
return instID.get(ctx, c)
|
||||
}
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InternalIPWithContext].
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.InternalIPWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InternalIPWithContext returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIPWithContext(ctx context.Context) (string, error) {
|
||||
return c.getTrimmed(ctx, "instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// Email returns the email address associated with the service account.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.EmailWithContext].
|
||||
func (c *Client) Email(serviceAccount string) (string, error) {
|
||||
return c.EmailWithContext(context.Background(), serviceAccount)
|
||||
}
|
||||
|
||||
// EmailWithContext returns the email address associated with the service account.
|
||||
// The serviceAccount parameter default value (empty string or "default" value)
|
||||
// will use the instance's main account.
|
||||
func (c *Client) EmailWithContext(ctx context.Context, serviceAccount string) (string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.getTrimmed(ctx, "instance/service-accounts/"+serviceAccount+"/email")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ExternalIPWithContext].
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.ExternalIPWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ExternalIPWithContext returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIPWithContext(ctx context.Context) (string, error) {
|
||||
return c.getTrimmed(ctx, "instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.HostnameWithContext].
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.HostnameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// HostnameWithContext returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) HostnameWithContext(ctx context.Context) (string, error) {
|
||||
return c.getTrimmed(ctx, "instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceTagsWithContext].
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
return c.InstanceTagsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceTagsWithContext returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTagsWithContext(ctx context.Context) ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.GetWithContext(ctx, "instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceNameWithContext].
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
return c.InstanceNameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceNameWithContext returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceNameWithContext(ctx context.Context) (string, error) {
|
||||
return c.getTrimmed(ctx, "instance/name")
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ZoneWithContext].
|
||||
func (c *Client) Zone() (string, error) {
|
||||
return c.ZoneWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ZoneWithContext returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) ZoneWithContext(ctx context.Context) (string, error) {
|
||||
zone, err := c.getTrimmed(ctx, "instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceAttributesWithContext].
|
||||
func (c *Client) InstanceAttributes() ([]string, error) {
|
||||
return c.InstanceAttributesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceAttributesWithContext returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributesWithContext(ctx context.Context) ([]string, error) {
|
||||
return c.lines(ctx, "instance/attributes/")
|
||||
}
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ProjectAttributesWithContext].
|
||||
func (c *Client) ProjectAttributes() ([]string, error) {
|
||||
return c.ProjectAttributesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ProjectAttributesWithContext returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributesWithContext(ctx context.Context) ([]string, error) {
|
||||
return c.lines(ctx, "project/attributes/")
|
||||
}
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceAttributeValueWithContext].
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.InstanceAttributeValueWithContext(context.Background(), attr)
|
||||
}
|
||||
|
||||
// InstanceAttributeValueWithContext returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
|
||||
return c.GetWithContext(ctx, "instance/attributes/"+attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ProjectAttributeValueWithContext].
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.ProjectAttributeValueWithContext(context.Background(), attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValueWithContext returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
|
||||
return c.GetWithContext(ctx, "project/attributes/"+attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ScopesWithContext].
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
return c.ScopesWithContext(context.Background(), serviceAccount)
|
||||
}
|
||||
|
||||
// ScopesWithContext returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) ScopesWithContext(ctx context.Context, serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines(ctx, "instance/service-accounts/"+serviceAccount+"/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.SubscribeWithContext].
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return c.SubscribeWithContext(context.Background(), suffix, func(ctx context.Context, v string, ok bool) error { return fn(v, ok) })
|
||||
}
|
||||
|
||||
// SubscribeWithContext subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// SubscribeWithContext calls fn with the latest metadata value indicated by the
|
||||
// provided suffix. If the metadata value is deleted, fn is called with the
|
||||
// empty string and ok false. Subscribe blocks until fn returns a non-nil error
|
||||
// or the value is deleted. Subscribe returns the error value returned from the
|
||||
// last call to fn, which may be nil when ok == false.
|
||||
func (c *Client) SubscribeWithContext(ctx context.Context, suffix string, fn func(ctx context.Context, v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETagWithSubClient(ctx, suffix, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(ctx, val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := c.getETagWithSubClient(ctx, suffix+url.QueryEscape(lastETag), true)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return err
|
||||
}
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(ctx, val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
||||
117
vendor/cloud.google.com/go/compute/metadata/retry.go
generated
vendored
Normal file
117
vendor/cloud.google.com/go/compute/metadata/retry.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetryAttempts = 5
|
||||
)
|
||||
|
||||
var (
|
||||
syscallRetryable = func(error) bool { return false }
|
||||
)
|
||||
|
||||
// defaultBackoff is basically equivalent to gax.Backoff without the need for
|
||||
// the dependency.
|
||||
type defaultBackoff struct {
|
||||
max time.Duration
|
||||
mul float64
|
||||
cur time.Duration
|
||||
}
|
||||
|
||||
func (b *defaultBackoff) Pause() time.Duration {
|
||||
d := time.Duration(1 + rand.Int63n(int64(b.cur)))
|
||||
b.cur = time.Duration(float64(b.cur) * b.mul)
|
||||
if b.cur > b.max {
|
||||
b.cur = b.max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// sleep is the equivalent of gax.Sleep without the need for the dependency.
|
||||
func sleep(ctx context.Context, d time.Duration) error {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func newRetryer() *metadataRetryer {
|
||||
return &metadataRetryer{bo: &defaultBackoff{
|
||||
cur: 100 * time.Millisecond,
|
||||
max: 30 * time.Second,
|
||||
mul: 2,
|
||||
}}
|
||||
}
|
||||
|
||||
type backoff interface {
|
||||
Pause() time.Duration
|
||||
}
|
||||
|
||||
type metadataRetryer struct {
|
||||
bo backoff
|
||||
attempts int
|
||||
}
|
||||
|
||||
func (r *metadataRetryer) Retry(status int, err error) (time.Duration, bool) {
|
||||
if status == http.StatusOK {
|
||||
return 0, false
|
||||
}
|
||||
retryOk := shouldRetry(status, err)
|
||||
if !retryOk {
|
||||
return 0, false
|
||||
}
|
||||
if r.attempts == maxRetryAttempts {
|
||||
return 0, false
|
||||
}
|
||||
r.attempts++
|
||||
return r.bo.Pause(), true
|
||||
}
|
||||
|
||||
func shouldRetry(status int, err error) bool {
|
||||
if 500 <= status && status <= 599 {
|
||||
return true
|
||||
}
|
||||
if status == http.StatusTooManyRequests {
|
||||
return true
|
||||
}
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return true
|
||||
}
|
||||
// Transient network errors should be retried.
|
||||
if syscallRetryable(err) {
|
||||
return true
|
||||
}
|
||||
if err, ok := err.(interface{ Temporary() bool }); ok {
|
||||
if err.Temporary() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if err, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return shouldRetry(status, err.Unwrap())
|
||||
}
|
||||
return false
|
||||
}
|
||||
31
vendor/cloud.google.com/go/compute/metadata/retry_linux.go
generated
vendored
Normal file
31
vendor/cloud.google.com/go/compute/metadata/retry_linux.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize syscallRetryable to return true on transient socket-level
|
||||
// errors. These errors are specific to Linux.
|
||||
syscallRetryable = func(err error) bool {
|
||||
return errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.ECONNREFUSED)
|
||||
}
|
||||
}
|
||||
28
vendor/cloud.google.com/go/compute/metadata/syscheck.go
generated
vendored
Normal file
28
vendor/cloud.google.com/go/compute/metadata/syscheck.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows && !linux
|
||||
|
||||
package metadata
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
//
|
||||
// NOTE: systemInfoSuggestsGCE is assigned to a varible for test stubbing purposes.
|
||||
var systemInfoSuggestsGCE = func() bool {
|
||||
// We don't currently have checks for other GOOS
|
||||
return false
|
||||
}
|
||||
30
vendor/cloud.google.com/go/compute/metadata/syscheck_linux.go
generated
vendored
Normal file
30
vendor/cloud.google.com/go/compute/metadata/syscheck_linux.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NOTE: systemInfoSuggestsGCE is assigned to a varible for test stubbing purposes.
|
||||
var systemInfoSuggestsGCE = func() bool {
|
||||
b, _ := os.ReadFile("/sys/class/dmi/id/product_name")
|
||||
|
||||
name := strings.TrimSpace(string(b))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
39
vendor/cloud.google.com/go/compute/metadata/syscheck_windows.go
generated
vendored
Normal file
39
vendor/cloud.google.com/go/compute/metadata/syscheck_windows.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
// NOTE: systemInfoSuggestsGCE is assigned to a varible for test stubbing purposes.
|
||||
var systemInfoSuggestsGCE = func() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\HardwareConfig\Current`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
s, _, err := k.GetStringValue("SystemProductName")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
return strings.HasPrefix(s, "Google")
|
||||
}
|
||||
201
vendor/github.com/bytedance/gopkg/LICENSE
generated
vendored
Normal file
201
vendor/github.com/bytedance/gopkg/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
43
vendor/github.com/bytedance/gopkg/lang/dirtmake/bytes.go
generated
vendored
Normal file
43
vendor/github.com/bytedance/gopkg/lang/dirtmake/bytes.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2024 ByteDance Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dirtmake
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type slice struct {
|
||||
data unsafe.Pointer
|
||||
len int
|
||||
cap int
|
||||
}
|
||||
|
||||
//go:linkname mallocgc runtime.mallocgc
|
||||
func mallocgc(size uintptr, typ unsafe.Pointer, needzero bool) unsafe.Pointer
|
||||
|
||||
// Bytes allocates a byte slice but does not clean up the memory it references.
|
||||
// Throw a fatal error instead of panic if cap is greater than runtime.maxAlloc.
|
||||
// NOTE: MUST set any byte element before it's read.
|
||||
func Bytes(len, cap int) (b []byte) {
|
||||
if len < 0 || len > cap {
|
||||
panic("dirtmake.Bytes: len out of range")
|
||||
}
|
||||
p := mallocgc(uintptr(cap), nil, false)
|
||||
sh := (*slice)(unsafe.Pointer(&b))
|
||||
sh.data = p
|
||||
sh.len = len
|
||||
sh.cap = cap
|
||||
return
|
||||
}
|
||||
5
vendor/github.com/bytedance/sonic/.codespellrc
generated
vendored
Normal file
5
vendor/github.com/bytedance/sonic/.codespellrc
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
[codespell]
|
||||
# ignore test files, go project names, binary files via `skip` and special var/regex via `ignore-words`
|
||||
skip = fuzz,*_test.tmpl,testdata,*_test.go,go.mod,go.sum,*.gz
|
||||
ignore-words = .github/workflows/.ignore_words
|
||||
check-filenames = true
|
||||
55
vendor/github.com/bytedance/sonic/.gitignore
generated
vendored
Normal file
55
vendor/github.com/bytedance/sonic/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
*.o
|
||||
*.swp
|
||||
*.swm
|
||||
*.swn
|
||||
*.a
|
||||
*.so
|
||||
_obj
|
||||
_test
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
_testmain.go
|
||||
*.exe
|
||||
*.exe~
|
||||
*.test
|
||||
*.prof
|
||||
*.rar
|
||||
*.zip
|
||||
*.gz
|
||||
*.psd
|
||||
*.bmd
|
||||
*.cfg
|
||||
*.pptx
|
||||
*.log
|
||||
*nohup.out
|
||||
*settings.pyc
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.DS_Store
|
||||
/.idea/
|
||||
/.vscode/
|
||||
/output/
|
||||
/vendor/
|
||||
/Gopkg.lock
|
||||
/Gopkg.toml
|
||||
coverage.html
|
||||
coverage.out
|
||||
coverage.xml
|
||||
junit.xml
|
||||
*.profile
|
||||
*.svg
|
||||
*.out
|
||||
ast/test.out
|
||||
ast/bench.sh
|
||||
|
||||
!testdata/**/*.json.gz
|
||||
fuzz/testdata
|
||||
*__debug_bin*
|
||||
*pprof
|
||||
*coverage.txt
|
||||
tools/venv/*
|
||||
9
vendor/github.com/bytedance/sonic/.gitmodules
generated
vendored
Normal file
9
vendor/github.com/bytedance/sonic/.gitmodules
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
[submodule "cloudwego"]
|
||||
path = tools/asm2asm
|
||||
url = https://github.com/cloudwego/asm2asm.git
|
||||
[submodule "tools/simde"]
|
||||
path = tools/simde
|
||||
url = https://github.com/simd-everywhere/simde.git
|
||||
[submodule "fuzz/go-fuzz-corpus"]
|
||||
path = fuzz/go-fuzz-corpus
|
||||
url = https://github.com/dvyukov/go-fuzz-corpus.git
|
||||
24
vendor/github.com/bytedance/sonic/.licenserc.yaml
generated
vendored
Normal file
24
vendor/github.com/bytedance/sonic/.licenserc.yaml
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
header:
|
||||
license:
|
||||
spdx-id: Apache-2.0
|
||||
copyright-owner: ByteDance Inc.
|
||||
|
||||
paths:
|
||||
- '**/*.go'
|
||||
- '**/*.s'
|
||||
|
||||
paths-ignore:
|
||||
- 'ast/asm.s' # empty file
|
||||
- 'decoder/asm.s' # empty file
|
||||
- 'encoder/asm.s' # empty file
|
||||
- 'internal/caching/asm.s' # empty file
|
||||
- 'internal/jit/asm.s' # empty file
|
||||
- 'internal/native/avx/native_amd64.s' # auto-generated by asm2asm
|
||||
- 'internal/native/avx/native_subr_amd64.go' # auto-generated by asm2asm
|
||||
- 'internal/native/avx2/native_amd64.s' # auto-generated by asm2asm
|
||||
- 'internal/native/avx2/native_subr_amd64.go' # auto-generated by asm2asm
|
||||
- 'internal/resolver/asm.s' # empty file
|
||||
- 'internal/rt/asm.s' # empty file
|
||||
- 'internal/loader/asm.s' # empty file
|
||||
|
||||
comment: on-failure
|
||||
128
vendor/github.com/bytedance/sonic/CODE_OF_CONDUCT.md
generated
vendored
Normal file
128
vendor/github.com/bytedance/sonic/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
wudi.daniel@bytedance.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
63
vendor/github.com/bytedance/sonic/CONTRIBUTING.md
generated
vendored
Normal file
63
vendor/github.com/bytedance/sonic/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# How to Contribute
|
||||
|
||||
## Your First Pull Request
|
||||
We use GitHub for our codebase. You can start by reading [How To Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests).
|
||||
|
||||
## Without Semantic Versioning
|
||||
We keep the stable code in branch `main` like `golang.org/x`. Development base on branch `develop`. We promise the **Forward Compatibility** by adding new package directory with suffix `v2/v3` when code has break changes.
|
||||
|
||||
## Branch Organization
|
||||
We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) as our branch organization, as known as [FDD](https://en.wikipedia.org/wiki/Feature-driven_development)
|
||||
|
||||
|
||||
## Bugs
|
||||
### 1. How to Find Known Issues
|
||||
We are using [Github Issues](https://github.com/bytedance/sonic/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist.
|
||||
|
||||
### 2. Reporting New Issues
|
||||
Providing a reduced test code is a recommended way for reporting issues. Then can be placed in:
|
||||
- Just in issues
|
||||
- [Golang Playground](https://play.golang.org/)
|
||||
|
||||
### 3. Security Bugs
|
||||
Please do not report the safe disclosure of bugs to public issues. Contact us by [Support Email](mailto:sonic@bytedance.com)
|
||||
|
||||
## How to Get in Touch
|
||||
- [Email](mailto:wudi.daniel@bytedance.com)
|
||||
|
||||
## Submit a Pull Request
|
||||
Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
1. Search [GitHub](https://github.com/bytedance/sonic/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts.
|
||||
2. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add. Discussing the design upfront helps to ensure that we're ready to accept your work.
|
||||
3. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the bytedance/sonic repo.
|
||||
4. In your forked repository, make your changes in a new git branch:
|
||||
```
|
||||
git checkout -b bugfix/security_bug develop
|
||||
```
|
||||
5. Create your patch, including appropriate test cases.
|
||||
6. Follow our [Style Guides](#code-style-guides).
|
||||
7. Commit your changes using a descriptive commit message that follows [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit).
|
||||
Adherence to these conventions is necessary because release notes will be automatically generated from these messages.
|
||||
8. Push your branch to GitHub:
|
||||
```
|
||||
git push origin bugfix/security_bug
|
||||
```
|
||||
9. In GitHub, send a pull request to `sonic:main`
|
||||
|
||||
Note: you must use one of `optimize/feature/bugfix/doc/ci/test/refactor` following a slash(`/`) as the branch prefix.
|
||||
|
||||
Your pr title and commit message should follow https://www.conventionalcommits.org/.
|
||||
|
||||
## Contribution Prerequisites
|
||||
- Our development environment keeps up with [Go Official](https://golang.org/project/).
|
||||
- You need fully checking with lint tools before submit your pull request. [gofmt](https://golang.org/pkg/cmd/gofmt/) & [golangci-lint](https://github.com/golangci/golangci-lint)
|
||||
- You are familiar with [Github](https://github.com)
|
||||
- Maybe you need familiar with [Actions](https://github.com/features/actions)(our default workflow tool).
|
||||
|
||||
## Code Style Guides
|
||||
See [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
|
||||
Good resources:
|
||||
- [Effective Go](https://golang.org/doc/effective_go)
|
||||
- [Pingcap General advice](https://pingcap.github.io/style-guide/general.html)
|
||||
- [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)
|
||||
0
vendor/github.com/bytedance/sonic/CREDITS
generated
vendored
Normal file
0
vendor/github.com/bytedance/sonic/CREDITS
generated
vendored
Normal file
201
vendor/github.com/bytedance/sonic/LICENSE
generated
vendored
Normal file
201
vendor/github.com/bytedance/sonic/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
501
vendor/github.com/bytedance/sonic/README.md
generated
vendored
Normal file
501
vendor/github.com/bytedance/sonic/README.md
generated
vendored
Normal file
@@ -0,0 +1,501 @@
|
||||
# Sonic
|
||||
|
||||
English | [中文](README_ZH_CN.md)
|
||||
|
||||
A blazingly fast JSON serializing & deserializing library, accelerated by JIT (just-in-time compiling) and SIMD (single-instruction-multiple-data).
|
||||
|
||||
## Requirement
|
||||
|
||||
- Go: 1.18~1.25
|
||||
- Notice: Go1.24.0 is not supported due to the [issue](https://github.com/golang/go/issues/71672), please use higher go version or add build tag `--ldflags="-checklinkname=0"`
|
||||
- OS: Linux / MacOS / Windows
|
||||
- CPU: AMD64 / (ARM64, need go1.20 above)
|
||||
|
||||
## Features
|
||||
|
||||
- Runtime object binding without code generation
|
||||
- Complete APIs for JSON value manipulation
|
||||
- Fast, fast, fast!
|
||||
|
||||
## APIs
|
||||
|
||||
see [go.dev](https://pkg.go.dev/github.com/bytedance/sonic)
|
||||
|
||||
## Benchmarks
|
||||
|
||||
For **all sizes** of json and **all scenarios** of usage, **Sonic performs best**.
|
||||
|
||||
- [Medium](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19) (13KB, 300+ key, 6 layers)
|
||||
|
||||
```powershell
|
||||
goversion: 1.17.1
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
|
||||
BenchmarkEncoder_Generic_Sonic-16 32393 ns/op 402.40 MB/s 11965 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Generic_Sonic_Fast-16 21668 ns/op 601.57 MB/s 10940 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Generic_JsonIter-16 42168 ns/op 309.12 MB/s 14345 B/op 115 allocs/op
|
||||
BenchmarkEncoder_Generic_GoJson-16 65189 ns/op 199.96 MB/s 23261 B/op 16 allocs/op
|
||||
BenchmarkEncoder_Generic_StdLib-16 106322 ns/op 122.60 MB/s 49136 B/op 789 allocs/op
|
||||
BenchmarkEncoder_Binding_Sonic-16 6269 ns/op 2079.26 MB/s 14173 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Binding_Sonic_Fast-16 5281 ns/op 2468.16 MB/s 12322 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Binding_JsonIter-16 20056 ns/op 649.93 MB/s 9488 B/op 2 allocs/op
|
||||
BenchmarkEncoder_Binding_GoJson-16 8311 ns/op 1568.32 MB/s 9481 B/op 1 allocs/op
|
||||
BenchmarkEncoder_Binding_StdLib-16 16448 ns/op 792.52 MB/s 9479 B/op 1 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_Sonic-16 6681 ns/op 1950.93 MB/s 12738 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_Sonic_Fast-16 4179 ns/op 3118.99 MB/s 10757 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_JsonIter-16 9861 ns/op 1321.84 MB/s 14362 B/op 115 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_GoJson-16 18850 ns/op 691.52 MB/s 23278 B/op 16 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_StdLib-16 45902 ns/op 283.97 MB/s 49174 B/op 789 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_Sonic-16 1480 ns/op 8810.09 MB/s 13049 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_Sonic_Fast-16 1209 ns/op 10785.23 MB/s 11546 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_JsonIter-16 6170 ns/op 2112.58 MB/s 9504 B/op 2 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_GoJson-16 3321 ns/op 3925.52 MB/s 9496 B/op 1 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_StdLib-16 3739 ns/op 3486.49 MB/s 9480 B/op 1 allocs/op
|
||||
|
||||
BenchmarkDecoder_Generic_Sonic-16 66812 ns/op 195.10 MB/s 57602 B/op 723 allocs/op
|
||||
BenchmarkDecoder_Generic_Sonic_Fast-16 54523 ns/op 239.07 MB/s 49786 B/op 313 allocs/op
|
||||
BenchmarkDecoder_Generic_StdLib-16 124260 ns/op 104.90 MB/s 50869 B/op 772 allocs/op
|
||||
BenchmarkDecoder_Generic_JsonIter-16 91274 ns/op 142.81 MB/s 55782 B/op 1068 allocs/op
|
||||
BenchmarkDecoder_Generic_GoJson-16 88569 ns/op 147.17 MB/s 66367 B/op 973 allocs/op
|
||||
BenchmarkDecoder_Binding_Sonic-16 32557 ns/op 400.38 MB/s 28302 B/op 137 allocs/op
|
||||
BenchmarkDecoder_Binding_Sonic_Fast-16 28649 ns/op 455.00 MB/s 24999 B/op 34 allocs/op
|
||||
BenchmarkDecoder_Binding_StdLib-16 111437 ns/op 116.97 MB/s 10576 B/op 208 allocs/op
|
||||
BenchmarkDecoder_Binding_JsonIter-16 35090 ns/op 371.48 MB/s 14673 B/op 385 allocs/op
|
||||
BenchmarkDecoder_Binding_GoJson-16 28738 ns/op 453.59 MB/s 22039 B/op 49 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_Sonic-16 12321 ns/op 1057.91 MB/s 57233 B/op 723 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_Sonic_Fast-16 10644 ns/op 1224.64 MB/s 49362 B/op 313 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_StdLib-16 57587 ns/op 226.35 MB/s 50874 B/op 772 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_JsonIter-16 38666 ns/op 337.12 MB/s 55789 B/op 1068 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_GoJson-16 30259 ns/op 430.79 MB/s 66370 B/op 974 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_Sonic-16 5965 ns/op 2185.28 MB/s 27747 B/op 137 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_Sonic_Fast-16 5170 ns/op 2521.31 MB/s 24715 B/op 34 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_StdLib-16 27582 ns/op 472.58 MB/s 10576 B/op 208 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_JsonIter-16 13571 ns/op 960.51 MB/s 14685 B/op 385 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_GoJson-16 10031 ns/op 1299.51 MB/s 22111 B/op 49 allocs/op
|
||||
|
||||
BenchmarkGetOne_Sonic-16 3276 ns/op 3975.78 MB/s 24 B/op 1 allocs/op
|
||||
BenchmarkGetOne_Gjson-16 9431 ns/op 1380.81 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkGetOne_Jsoniter-16 51178 ns/op 254.46 MB/s 27936 B/op 647 allocs/op
|
||||
BenchmarkGetOne_Parallel_Sonic-16 216.7 ns/op 60098.95 MB/s 24 B/op 1 allocs/op
|
||||
BenchmarkGetOne_Parallel_Gjson-16 1076 ns/op 12098.62 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkGetOne_Parallel_Jsoniter-16 17741 ns/op 734.06 MB/s 27945 B/op 647 allocs/op
|
||||
BenchmarkSetOne_Sonic-16 9571 ns/op 1360.61 MB/s 1584 B/op 17 allocs/op
|
||||
BenchmarkSetOne_Sjson-16 36456 ns/op 357.22 MB/s 52180 B/op 9 allocs/op
|
||||
BenchmarkSetOne_Jsoniter-16 79475 ns/op 163.86 MB/s 45862 B/op 964 allocs/op
|
||||
BenchmarkSetOne_Parallel_Sonic-16 850.9 ns/op 15305.31 MB/s 1584 B/op 17 allocs/op
|
||||
BenchmarkSetOne_Parallel_Sjson-16 18194 ns/op 715.77 MB/s 52247 B/op 9 allocs/op
|
||||
BenchmarkSetOne_Parallel_Jsoniter-16 33560 ns/op 388.05 MB/s 45892 B/op 964 allocs/op
|
||||
BenchmarkLoadNode/LoadAll()-16 11384 ns/op 1143.93 MB/s 6307 B/op 25 allocs/op
|
||||
BenchmarkLoadNode_Parallel/LoadAll()-16 5493 ns/op 2370.68 MB/s 7145 B/op 25 allocs/op
|
||||
BenchmarkLoadNode/Interface()-16 17722 ns/op 734.85 MB/s 13323 B/op 88 allocs/op
|
||||
BenchmarkLoadNode_Parallel/Interface()-16 10330 ns/op 1260.70 MB/s 15178 B/op 88 allocs/op
|
||||
```
|
||||
|
||||
- [Small](https://github.com/bytedance/sonic/blob/main/testdata/small.go) (400B, 11 keys, 3 layers)
|
||||

|
||||
- [Large](https://github.com/bytedance/sonic/blob/main/testdata/twitter.json) (635KB, 10000+ key, 6 layers)
|
||||

|
||||
|
||||
See [bench.sh](https://github.com/bytedance/sonic/blob/main/scripts/bench.sh) for benchmark codes.
|
||||
|
||||
## How it works
|
||||
|
||||
See [INTRODUCTION.md](./docs/INTRODUCTION.md).
|
||||
|
||||
## Usage
|
||||
|
||||
### Marshal/Unmarshal
|
||||
|
||||
Default behaviors are mostly consistent with `encoding/json`, except HTML escaping form (see [Escape HTML](https://github.com/bytedance/sonic/blob/main/README.md#escape-html)) and `SortKeys` feature (optional support see [Sort Keys](https://github.com/bytedance/sonic/blob/main/README.md#sort-keys)) that is **NOT** in conformity to [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
var data YourSchema
|
||||
// Marshal
|
||||
output, err := sonic.Marshal(&data)
|
||||
// Unmarshal
|
||||
err := sonic.Unmarshal(output, &data)
|
||||
```
|
||||
|
||||
### Streaming IO
|
||||
|
||||
Sonic supports decoding json from `io.Reader` or encoding objects into `io.Writer`, aims at handling multiple values as well as reducing memory consumption.
|
||||
|
||||
- encoder
|
||||
|
||||
```go
|
||||
var o1 = map[string]interface{}{
|
||||
"a": "b",
|
||||
}
|
||||
var o2 = 1
|
||||
var w = bytes.NewBuffer(nil)
|
||||
var enc = sonic.ConfigDefault.NewEncoder(w)
|
||||
enc.Encode(o1)
|
||||
enc.Encode(o2)
|
||||
fmt.Println(w.String())
|
||||
// Output:
|
||||
// {"a":"b"}
|
||||
// 1
|
||||
```
|
||||
|
||||
- decoder
|
||||
|
||||
```go
|
||||
var o = map[string]interface{}{}
|
||||
var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
|
||||
var dec = sonic.ConfigDefault.NewDecoder(r)
|
||||
dec.Decode(&o)
|
||||
dec.Decode(&o)
|
||||
fmt.Printf("%+v", o)
|
||||
// Output:
|
||||
// map[1:2 a:b]
|
||||
```
|
||||
|
||||
### Use Number/Use Int64
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic/decoder"
|
||||
|
||||
var input = `1`
|
||||
var data interface{}
|
||||
|
||||
// default float64
|
||||
dc := decoder.NewDecoder(input)
|
||||
dc.Decode(&data) // data == float64(1)
|
||||
// use json.Number
|
||||
dc = decoder.NewDecoder(input)
|
||||
dc.UseNumber()
|
||||
dc.Decode(&data) // data == json.Number("1")
|
||||
// use int64
|
||||
dc = decoder.NewDecoder(input)
|
||||
dc.UseInt64()
|
||||
dc.Decode(&data) // data == int64(1)
|
||||
|
||||
root, err := sonic.GetFromString(input)
|
||||
// Get json.Number
|
||||
jn := root.Number()
|
||||
jm := root.InterfaceUseNumber().(json.Number) // jn == jm
|
||||
// Get float64
|
||||
fn := root.Float64()
|
||||
fm := root.Interface().(float64) // jn == jm
|
||||
```
|
||||
|
||||
### Sort Keys
|
||||
|
||||
On account of the performance loss from sorting (roughly 10%), sonic doesn't enable this feature by default. If your component depends on it to work (like [zstd](https://github.com/facebook/zstd)), Use it like this:
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
import "github.com/bytedance/sonic/encoder"
|
||||
|
||||
// Binding map only
|
||||
m := map[string]interface{}{}
|
||||
v, err := encoder.Encode(m, encoder.SortMapKeys)
|
||||
|
||||
// Or ast.Node.SortKeys() before marshal
|
||||
var root := sonic.Get(JSON)
|
||||
err := root.SortKeys()
|
||||
```
|
||||
|
||||
### Escape HTML
|
||||
|
||||
On account of the performance loss (roughly 15%), sonic doesn't enable this feature by default. You can use `encoder.EscapeHTML` option to open this feature (align with `encoding/json.HTMLEscape`).
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
v := map[string]string{"&&":"<>"}
|
||||
ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"}}`
|
||||
```
|
||||
|
||||
### Compact Format
|
||||
|
||||
Sonic encodes primitive objects (struct/map...) as compact-format JSON by default, except marshaling `json.RawMessage` or `json.Marshaler`: sonic ensures validating their output JSON but **DO NOT** compacting them for performance concerns. We provide the option `encoder.CompactMarshaler` to add compacting process.
|
||||
|
||||
### Print Error
|
||||
|
||||
If there invalid syntax in input JSON, sonic will return `decoder.SyntaxError`, which supports pretty-printing of error position
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
import "github.com/bytedance/sonic/decoder"
|
||||
|
||||
var data interface{}
|
||||
err := sonic.UnmarshalString("[[[}]]", &data)
|
||||
if err != nil {
|
||||
/* One line by default */
|
||||
println(e.Error()) // "Syntax error at index 3: invalid char\n\n\t[[[}]]\n\t...^..\n"
|
||||
/* Pretty print */
|
||||
if e, ok := err.(decoder.SyntaxError); ok {
|
||||
/*Syntax error at index 3: invalid char
|
||||
|
||||
[[[}]]
|
||||
...^..
|
||||
*/
|
||||
print(e.Description())
|
||||
} else if me, ok := err.(*decoder.MismatchTypeError); ok {
|
||||
// decoder.MismatchTypeError is new to Sonic v1.6.0
|
||||
print(me.Description())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Mismatched Types [Sonic v1.6.0]
|
||||
|
||||
If there a **mismatch-typed** value for a given key, sonic will report `decoder.MismatchTypeError` (if there are many, report the last one), but still skip wrong the value and keep decoding next JSON.
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
import "github.com/bytedance/sonic/decoder"
|
||||
|
||||
var data = struct{
|
||||
A int
|
||||
B int
|
||||
}{}
|
||||
err := UnmarshalString(`{"A":"1","B":1}`, &data)
|
||||
println(err.Error()) // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
|
||||
fmt.Printf("%+v", data) // {A:0 B:1}
|
||||
```
|
||||
|
||||
### Ast.Node
|
||||
|
||||
Sonic/ast.Node is a completely self-contained AST for JSON. It implements serialization and deserialization both and provides robust APIs for obtaining and modification of generic data.
|
||||
|
||||
#### Get/Index
|
||||
|
||||
Search partial JSON by given paths, which must be non-negative integer or string, or nil
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
input := []byte(`{"key1":[{},{"key2":{"key3":[1,2,3]}}]}`)
|
||||
|
||||
// no path, returns entire json
|
||||
root, err := sonic.Get(input)
|
||||
raw := root.Raw() // == string(input)
|
||||
|
||||
// multiple paths
|
||||
root, err := sonic.Get(input, "key1", 1, "key2")
|
||||
sub := root.Get("key3").Index(2).Int64() // == 3
|
||||
```
|
||||
|
||||
**Tip**: since `Index()` uses offset to locate data, which is much faster than scanning like `Get()`, we suggest you use it as much as possible. And sonic also provides another API `IndexOrGet()` to underlying use offset as well as ensure the key is matched.
|
||||
|
||||
#### SearchOption
|
||||
|
||||
`Searcher` provides some options for user to meet different needs:
|
||||
|
||||
```go
|
||||
opts := ast.SearchOption{ CopyReturn: true ... }
|
||||
val, err := sonic.GetWithOptions(JSON, opts, "key")
|
||||
```
|
||||
|
||||
- CopyReturn
|
||||
Indicate the searcher to copy the result JSON string instead of refer from the input. This can help to reduce memory usage if you cache the results
|
||||
- ConcurentRead
|
||||
Since `ast.Node` use `Lazy-Load` design, it doesn't support Concurrently-Read by default. If you want to read it concurrently, please specify it.
|
||||
- ValidateJSON
|
||||
Indicate the searcher to validate the entire JSON. This option is enabled by default, which slow down the search speed a little.
|
||||
|
||||
#### Set/Unset
|
||||
|
||||
Modify the json content by Set()/Unset()
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
// Set
|
||||
exist, err := root.Set("key4", NewBool(true)) // exist == false
|
||||
alias1 := root.Get("key4")
|
||||
println(alias1.Valid()) // true
|
||||
alias2 := root.Index(1)
|
||||
println(alias1 == alias2) // true
|
||||
|
||||
// Unset
|
||||
exist, err := root.UnsetByIndex(1) // exist == true
|
||||
println(root.Get("key4").Check()) // "value not exist"
|
||||
```
|
||||
|
||||
#### Serialize
|
||||
|
||||
To encode `ast.Node` as json, use `MarshalJson()` or `json.Marshal()` (MUST pass the node's pointer)
|
||||
|
||||
```go
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
buf, err := root.MarshalJson()
|
||||
println(string(buf)) // {"key1":[{},{"key2":{"key3":[1,2,3]}}]}
|
||||
exp, err := json.Marshal(&root) // WARN: use pointer
|
||||
println(string(buf) == string(exp)) // true
|
||||
```
|
||||
|
||||
#### APIs
|
||||
|
||||
- validation: `Check()`, `Error()`, `Valid()`, `Exist()`
|
||||
- searching: `Index()`, `Get()`, `IndexPair()`, `IndexOrGet()`, `GetByPath()`
|
||||
- go-type casting: `Int64()`, `Float64()`, `String()`, `Number()`, `Bool()`, `Map[UseNumber|UseNode]()`, `Array[UseNumber|UseNode]()`, `Interface[UseNumber|UseNode]()`
|
||||
- go-type packing: `NewRaw()`, `NewNumber()`, `NewNull()`, `NewBool()`, `NewString()`, `NewObject()`, `NewArray()`
|
||||
- iteration: `Values()`, `Properties()`, `ForEach()`, `SortKeys()`
|
||||
- modification: `Set()`, `SetByIndex()`, `Add()`
|
||||
|
||||
### Ast.Visitor
|
||||
|
||||
Sonic provides an advanced API for fully parsing JSON into non-standard types (neither `struct` not `map[string]interface{}`) without using any intermediate representation (`ast.Node` or `interface{}`). For example, you might have the following types which are like `interface{}` but actually not `interface{}`:
|
||||
|
||||
```go
|
||||
type UserNode interface {}
|
||||
|
||||
// the following types implement the UserNode interface.
|
||||
type (
|
||||
UserNull struct{}
|
||||
UserBool struct{ Value bool }
|
||||
UserInt64 struct{ Value int64 }
|
||||
UserFloat64 struct{ Value float64 }
|
||||
UserString struct{ Value string }
|
||||
UserObject struct{ Value map[string]UserNode }
|
||||
UserArray struct{ Value []UserNode }
|
||||
)
|
||||
```
|
||||
|
||||
Sonic provides the following API to return **the preorder traversal of a JSON AST**. The `ast.Visitor` is a SAX style interface which is used in some C++ JSON library. You should implement `ast.Visitor` by yourself and pass it to `ast.Preorder()` method. In your visitor you can make your custom types to represent JSON values. There may be an O(n) space container (such as stack) in your visitor to record the object / array hierarchy.
|
||||
|
||||
```go
|
||||
func Preorder(str string, visitor Visitor, opts *VisitorOptions) error
|
||||
|
||||
type Visitor interface {
|
||||
OnNull() error
|
||||
OnBool(v bool) error
|
||||
OnString(v string) error
|
||||
OnInt64(v int64, n json.Number) error
|
||||
OnFloat64(v float64, n json.Number) error
|
||||
OnObjectBegin(capacity int) error
|
||||
OnObjectKey(key string) error
|
||||
OnObjectEnd() error
|
||||
OnArrayBegin(capacity int) error
|
||||
OnArrayEnd() error
|
||||
}
|
||||
```
|
||||
|
||||
See [ast/visitor.go](https://github.com/bytedance/sonic/blob/main/ast/visitor.go) for detailed usage. We also implement a demo visitor for `UserNode` in [ast/visitor_test.go](https://github.com/bytedance/sonic/blob/main/ast/visitor_test.go).
|
||||
|
||||
## Compatibility
|
||||
|
||||
For developers who want to use sonic to meet different scenarios, we provide some integrated configs as `sonic.API`
|
||||
|
||||
- `ConfigDefault`: the sonic's default config (`EscapeHTML=false`,`SortKeys=false`...) to run sonic fast meanwhile ensure security.
|
||||
- `ConfigStd`: the std-compatible config (`EscapeHTML=true`,`SortKeys=true`...)
|
||||
- `ConfigFastest`: the fastest config (`NoQuoteTextMarshaler=true`) to run on sonic as fast as possible.
|
||||
Sonic **DOES NOT** ensure to support all environments, due to the difficulty of developing high-performance codes. On non-sonic-supporting environment, the implementation will fall back to `encoding/json`. Thus below configs will all equal to `ConfigStd`.
|
||||
|
||||
## Tips
|
||||
|
||||
### Pretouch
|
||||
|
||||
Since Sonic uses [golang-asm](https://github.com/twitchyliquid64/golang-asm) as a JIT assembler, which is NOT very suitable for runtime compiling, first-hit running of a huge schema may cause request-timeout or even process-OOM. For better stability, we advise **using `Pretouch()` for huge-schema or compact-memory applications** before `Marshal()/Unmarshal()`.
|
||||
|
||||
```go
|
||||
import (
|
||||
"reflect"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/bytedance/sonic/option"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var v HugeStruct
|
||||
|
||||
// For most large types (nesting depth <= option.DefaultMaxInlineDepth)
|
||||
err := sonic.Pretouch(reflect.TypeOf(v))
|
||||
|
||||
// with more CompileOption...
|
||||
err := sonic.Pretouch(reflect.TypeOf(v),
|
||||
// If the type is too deep nesting (nesting depth > option.DefaultMaxInlineDepth),
|
||||
// you can set compile recursive loops in Pretouch for better stability in JIT.
|
||||
option.WithCompileRecursiveDepth(loop),
|
||||
// For a large nested struct, try to set a smaller depth to reduce compiling time.
|
||||
option.WithCompileMaxInlineDepth(depth),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Copy string
|
||||
|
||||
When decoding **string values without any escaped characters**, sonic references them from the origin JSON buffer instead of mallocing a new buffer to copy. This helps a lot for CPU performance but may leave the whole JSON buffer in memory as long as the decoded objects are being used. In practice, we found the extra memory introduced by referring JSON buffer is usually 20% ~ 80% of decoded objects. Once an application holds these objects for a long time (for example, cache the decoded objects for reusing), its in-use memory on the server may go up. - `Config.CopyString`/`decoder.CopyString()`: We provide the option for `Decode()` / `Unmarshal()` users to choose not to reference the JSON buffer, which may cause a decline in CPU performance to some degree.
|
||||
|
||||
- `GetFromStringNoCopy()`: For memory safety, `sonic.Get()` / `sonic.GetFromString()` now copies return JSON. If users want to get json more quickly and not care about memory usage, you can use `GetFromStringNoCopy()` to return a JSON directly referenced from source.
|
||||
|
||||
### Pass string or []byte?
|
||||
|
||||
For alignment to `encoding/json`, we provide API to pass `[]byte` as an argument, but the string-to-bytes copy is conducted at the same time considering safety, which may lose performance when the origin JSON is huge. Therefore, you can use `UnmarshalString()` and `GetFromString()` to pass a string, as long as your origin data is a string or **nocopy-cast** is safe for your []byte. We also provide API `MarshalString()` for convenient **nocopy-cast** of encoded JSON []byte, which is safe since sonic's output bytes is always duplicated and unique.
|
||||
|
||||
### Accelerate `encoding.TextMarshaler`
|
||||
|
||||
To ensure data security, sonic.Encoder quotes and escapes string values from `encoding.TextMarshaler` interfaces by default, which may degrade performance much if most of your data is in form of them. We provide `encoder.NoQuoteTextMarshaler` to skip these operations, which means you **MUST** ensure their output string escaped and quoted following [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).
|
||||
|
||||
### Better performance for generic data
|
||||
|
||||
In **fully-parsed** scenario, `Unmarshal()` performs better than `Get()`+`Node.Interface()`. But if you only have a part of the schema for specific json, you can combine `Get()` and `Unmarshal()` together:
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
node, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user")
|
||||
var user User // your partial schema...
|
||||
err = sonic.UnmarshalString(node.Raw(), &user)
|
||||
```
|
||||
|
||||
Even if you don't have any schema, use `ast.Node` as the container of generic values instead of `map` or `interface`:
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
root, err := sonic.GetFromString(_TwitterJson)
|
||||
user := root.GetByPath("statuses", 3, "user") // === root.Get("status").Index(3).Get("user")
|
||||
err = user.Check()
|
||||
|
||||
// err = user.LoadAll() // only call this when you want to use 'user' concurrently...
|
||||
go someFunc(user)
|
||||
```
|
||||
|
||||
Why? Because `ast.Node` stores its children using `array`:
|
||||
|
||||
- `Array`'s performance is **much better** than `Map` when Inserting (Deserialize) and Scanning (Serialize) data;
|
||||
- **Hashing** (`map[x]`) is not as efficient as **Indexing** (`array[x]`), which `ast.Node` can conduct on **both array and object**;
|
||||
- Using `Interface()`/`Map()` means Sonic must parse all the underlying values, while `ast.Node` can parse them **on demand**.
|
||||
|
||||
**CAUTION:** `ast.Node` **DOESN'T** ensure concurrent security directly, due to its **lazy-load** design. However, you can call `Node.Load()`/`Node.LoadAll()` to achieve that, which may bring performance reduction while it still works faster than converting to `map` or `interface{}`
|
||||
|
||||
### Ast.Node or Ast.Visitor?
|
||||
|
||||
For generic data, `ast.Node` should be enough for your needs in most cases.
|
||||
|
||||
However, `ast.Node` is designed for partially processing JSON string. It has some special designs such as lazy-load which might not be suitable for directly parsing the whole JSON string like `Unmarshal()`. Although `ast.Node` is better then `map` or `interface{}`, it's also a kind of intermediate representation after all if your final types are customized and you have to convert the above types to your custom types after parsing.
|
||||
|
||||
For better performance, in previous case the `ast.Visitor` will be the better choice. It performs JSON decoding like `Unmarshal()` and you can directly use your final types to represents a JSON AST without any intermediate representations.
|
||||
|
||||
But `ast.Visitor` is not a very handy API. You might need to write a lot of code to implement your visitor and carefully maintain the tree hierarchy during decoding. Please read the comments in [ast/visitor.go](https://github.com/bytedance/sonic/blob/main/ast/visitor.go) carefully if you decide to use this API.
|
||||
|
||||
### Buffer Size
|
||||
|
||||
Sonic use memory pool in many places like `encoder.Encode`, `ast.Node.MarshalJSON` to improve performance, which may produce more memory usage (in-use) when server's load is high. See [issue 614](https://github.com/bytedance/sonic/issues/614). Therefore, we introduce some options to let user control the behavior of memory pool. See [option](https://pkg.go.dev/github.com/bytedance/sonic@v1.11.9/option#pkg-variables) package.
|
||||
|
||||
### Faster JSON Skip
|
||||
|
||||
For security, sonic use [FSM](native/skip_one.c) algorithm to validate JSON when decoding raw JSON or encoding `json.Marshaler`, which is much slower (1~10x) than [SIMD-searching-pair](native/skip_one_fast.c) algorithm. If user has many redundant JSON value and DO NOT NEED to strictly validate JSON correctness, you can enable below options:
|
||||
|
||||
- `Config.NoValidateSkipJSON`: for faster skipping JSON when decoding, such as unknown fields, json.Unmarshaler(json.RawMessage), mismatched values, and redundant array elements
|
||||
- `Config.NoValidateJSONMarshaler`: avoid validating JSON when encoding `json.Marshaler`
|
||||
- `SearchOption.ValidateJSON`: indicates if validate located JSON value when `Get`
|
||||
|
||||
## JSON-Path Support (GJSON)
|
||||
|
||||
[tidwall/gjson](https://github.com/tidwall/gjson) has provided a comprehensive and popular JSON-Path API, and
|
||||
a lot of older codes heavily relies on it. Therefore, we provides a wrapper library, which combines gjson's API with sonic's SIMD algorithm to boost up the performance. See [cloudwego/gjson](https://github.com/cloudwego/gjson).
|
||||
|
||||
## Community
|
||||
|
||||
Sonic is a subproject of [CloudWeGo](https://www.cloudwego.io/). We are committed to building a cloud native ecosystem.
|
||||
494
vendor/github.com/bytedance/sonic/README_ZH_CN.md
generated
vendored
Normal file
494
vendor/github.com/bytedance/sonic/README_ZH_CN.md
generated
vendored
Normal file
@@ -0,0 +1,494 @@
|
||||
# Sonic
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
一个速度奇快的 JSON 序列化/反序列化库,由 JIT (即时编译)和 SIMD (单指令流多数据流)加速。
|
||||
|
||||
## 依赖
|
||||
|
||||
- Go: 1.18~1.25
|
||||
- 注意:Go1.24.0 由于 [issue](https://github.com/golang/go/issues/71672) 不可用,请升级到更高 Go 版本,或添加编译选项 `--ldflags="-checklinkname=0"`
|
||||
- OS: Linux / MacOS / Windows
|
||||
- CPU: AMD64 / (ARM64, 需要 Go1.20 以上)
|
||||
|
||||
## 接口
|
||||
|
||||
详见 [go.dev](https://pkg.go.dev/github.com/bytedance/sonic)
|
||||
|
||||
## 特色
|
||||
|
||||
- 运行时对象绑定,无需代码生成
|
||||
- 完备的 JSON 操作 API
|
||||
- 快,更快,还要更快!
|
||||
|
||||
## 基准测试
|
||||
|
||||
对于**所有大小**的 json 和**所有使用场景**, **Sonic 表现均为最佳**。
|
||||
|
||||
- [中型](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19) (13kB, 300+ 键, 6 层)
|
||||
|
||||
```powershell
|
||||
goversion: 1.17.1
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
|
||||
BenchmarkEncoder_Generic_Sonic-16 32393 ns/op 402.40 MB/s 11965 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Generic_Sonic_Fast-16 21668 ns/op 601.57 MB/s 10940 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Generic_JsonIter-16 42168 ns/op 309.12 MB/s 14345 B/op 115 allocs/op
|
||||
BenchmarkEncoder_Generic_GoJson-16 65189 ns/op 199.96 MB/s 23261 B/op 16 allocs/op
|
||||
BenchmarkEncoder_Generic_StdLib-16 106322 ns/op 122.60 MB/s 49136 B/op 789 allocs/op
|
||||
BenchmarkEncoder_Binding_Sonic-16 6269 ns/op 2079.26 MB/s 14173 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Binding_Sonic_Fast-16 5281 ns/op 2468.16 MB/s 12322 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Binding_JsonIter-16 20056 ns/op 649.93 MB/s 9488 B/op 2 allocs/op
|
||||
BenchmarkEncoder_Binding_GoJson-16 8311 ns/op 1568.32 MB/s 9481 B/op 1 allocs/op
|
||||
BenchmarkEncoder_Binding_StdLib-16 16448 ns/op 792.52 MB/s 9479 B/op 1 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_Sonic-16 6681 ns/op 1950.93 MB/s 12738 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_Sonic_Fast-16 4179 ns/op 3118.99 MB/s 10757 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_JsonIter-16 9861 ns/op 1321.84 MB/s 14362 B/op 115 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_GoJson-16 18850 ns/op 691.52 MB/s 23278 B/op 16 allocs/op
|
||||
BenchmarkEncoder_Parallel_Generic_StdLib-16 45902 ns/op 283.97 MB/s 49174 B/op 789 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_Sonic-16 1480 ns/op 8810.09 MB/s 13049 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_Sonic_Fast-16 1209 ns/op 10785.23 MB/s 11546 B/op 4 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_JsonIter-16 6170 ns/op 2112.58 MB/s 9504 B/op 2 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_GoJson-16 3321 ns/op 3925.52 MB/s 9496 B/op 1 allocs/op
|
||||
BenchmarkEncoder_Parallel_Binding_StdLib-16 3739 ns/op 3486.49 MB/s 9480 B/op 1 allocs/op
|
||||
|
||||
BenchmarkDecoder_Generic_Sonic-16 66812 ns/op 195.10 MB/s 57602 B/op 723 allocs/op
|
||||
BenchmarkDecoder_Generic_Sonic_Fast-16 54523 ns/op 239.07 MB/s 49786 B/op 313 allocs/op
|
||||
BenchmarkDecoder_Generic_StdLib-16 124260 ns/op 104.90 MB/s 50869 B/op 772 allocs/op
|
||||
BenchmarkDecoder_Generic_JsonIter-16 91274 ns/op 142.81 MB/s 55782 B/op 1068 allocs/op
|
||||
BenchmarkDecoder_Generic_GoJson-16 88569 ns/op 147.17 MB/s 66367 B/op 973 allocs/op
|
||||
BenchmarkDecoder_Binding_Sonic-16 32557 ns/op 400.38 MB/s 28302 B/op 137 allocs/op
|
||||
BenchmarkDecoder_Binding_Sonic_Fast-16 28649 ns/op 455.00 MB/s 24999 B/op 34 allocs/op
|
||||
BenchmarkDecoder_Binding_StdLib-16 111437 ns/op 116.97 MB/s 10576 B/op 208 allocs/op
|
||||
BenchmarkDecoder_Binding_JsonIter-16 35090 ns/op 371.48 MB/s 14673 B/op 385 allocs/op
|
||||
BenchmarkDecoder_Binding_GoJson-16 28738 ns/op 453.59 MB/s 22039 B/op 49 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_Sonic-16 12321 ns/op 1057.91 MB/s 57233 B/op 723 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_Sonic_Fast-16 10644 ns/op 1224.64 MB/s 49362 B/op 313 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_StdLib-16 57587 ns/op 226.35 MB/s 50874 B/op 772 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_JsonIter-16 38666 ns/op 337.12 MB/s 55789 B/op 1068 allocs/op
|
||||
BenchmarkDecoder_Parallel_Generic_GoJson-16 30259 ns/op 430.79 MB/s 66370 B/op 974 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_Sonic-16 5965 ns/op 2185.28 MB/s 27747 B/op 137 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_Sonic_Fast-16 5170 ns/op 2521.31 MB/s 24715 B/op 34 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_StdLib-16 27582 ns/op 472.58 MB/s 10576 B/op 208 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_JsonIter-16 13571 ns/op 960.51 MB/s 14685 B/op 385 allocs/op
|
||||
BenchmarkDecoder_Parallel_Binding_GoJson-16 10031 ns/op 1299.51 MB/s 22111 B/op 49 allocs/op
|
||||
|
||||
BenchmarkGetOne_Sonic-16 3276 ns/op 3975.78 MB/s 24 B/op 1 allocs/op
|
||||
BenchmarkGetOne_Gjson-16 9431 ns/op 1380.81 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkGetOne_Jsoniter-16 51178 ns/op 254.46 MB/s 27936 B/op 647 allocs/op
|
||||
BenchmarkGetOne_Parallel_Sonic-16 216.7 ns/op 60098.95 MB/s 24 B/op 1 allocs/op
|
||||
BenchmarkGetOne_Parallel_Gjson-16 1076 ns/op 12098.62 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkGetOne_Parallel_Jsoniter-16 17741 ns/op 734.06 MB/s 27945 B/op 647 allocs/op
|
||||
BenchmarkSetOne_Sonic-16 9571 ns/op 1360.61 MB/s 1584 B/op 17 allocs/op
|
||||
BenchmarkSetOne_Sjson-16 36456 ns/op 357.22 MB/s 52180 B/op 9 allocs/op
|
||||
BenchmarkSetOne_Jsoniter-16 79475 ns/op 163.86 MB/s 45862 B/op 964 allocs/op
|
||||
BenchmarkSetOne_Parallel_Sonic-16 850.9 ns/op 15305.31 MB/s 1584 B/op 17 allocs/op
|
||||
BenchmarkSetOne_Parallel_Sjson-16 18194 ns/op 715.77 MB/s 52247 B/op 9 allocs/op
|
||||
BenchmarkSetOne_Parallel_Jsoniter-16 33560 ns/op 388.05 MB/s 45892 B/op 964 allocs/op
|
||||
BenchmarkLoadNode/LoadAll()-16 11384 ns/op 1143.93 MB/s 6307 B/op 25 allocs/op
|
||||
BenchmarkLoadNode_Parallel/LoadAll()-16 5493 ns/op 2370.68 MB/s 7145 B/op 25 allocs/op
|
||||
BenchmarkLoadNode/Interface()-16 17722 ns/op 734.85 MB/s 13323 B/op 88 allocs/op
|
||||
BenchmarkLoadNode_Parallel/Interface()-16 10330 ns/op 1260.70 MB/s 15178 B/op 88 allocs/op
|
||||
```
|
||||
|
||||
- [小型](https://github.com/bytedance/sonic/blob/main/testdata/small.go) (400B, 11 个键, 3 层)
|
||||

|
||||
- [大型](https://github.com/bytedance/sonic/blob/main/testdata/twitter.json) (635kB, 10000+ 个键, 6 层)
|
||||

|
||||
|
||||
要查看基准测试代码,请参阅 [bench.sh](https://github.com/bytedance/sonic/blob/main/scripts/bench.sh) 。
|
||||
|
||||
## 工作原理
|
||||
|
||||
请参阅 [INTRODUCTION_ZH_CN.md](./docs/INTRODUCTION_ZH_CN.md).
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 序列化/反序列化
|
||||
|
||||
默认的行为基本上与 `encoding/json` 相一致,除了 HTML 转义形式(参见 [Escape HTML](https://github.com/bytedance/sonic/blob/main/README.md#escape-html)) 和 `SortKeys` 功能(参见 [Sort Keys](https://github.com/bytedance/sonic/blob/main/README.md#sort-keys))**没有**遵循 [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259) 。
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
var data YourSchema
|
||||
// Marshal
|
||||
output, err := sonic.Marshal(&data)
|
||||
// Unmarshal
|
||||
err := sonic.Unmarshal(output, &data)
|
||||
```
|
||||
|
||||
### 流式输入输出
|
||||
|
||||
Sonic 支持解码 `io.Reader` 中输入的 json,或将对象编码为 json 后输出至 `io.Writer`,以处理多个值并减少内存消耗。
|
||||
|
||||
- 编码器
|
||||
|
||||
```go
|
||||
var o1 = map[string]interface{}{
|
||||
"a": "b",
|
||||
}
|
||||
var o2 = 1
|
||||
var w = bytes.NewBuffer(nil)
|
||||
var enc = sonic.ConfigDefault.NewEncoder(w)
|
||||
enc.Encode(o1)
|
||||
enc.Encode(o2)
|
||||
fmt.Println(w.String())
|
||||
// Output:
|
||||
// {"a":"b"}
|
||||
// 1
|
||||
```
|
||||
|
||||
- 解码器
|
||||
|
||||
```go
|
||||
var o = map[string]interface{}{}
|
||||
var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
|
||||
var dec = sonic.ConfigDefault.NewDecoder(r)
|
||||
dec.Decode(&o)
|
||||
dec.Decode(&o)
|
||||
fmt.Printf("%+v", o)
|
||||
// Output:
|
||||
// map[1:2 a:b]
|
||||
```
|
||||
|
||||
### 使用 `Number` / `int64`
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic/decoder"
|
||||
|
||||
var input = `1`
|
||||
var data interface{}
|
||||
|
||||
// default float64
|
||||
dc := decoder.NewDecoder(input)
|
||||
dc.Decode(&data) // data == float64(1)
|
||||
// use json.Number
|
||||
dc = decoder.NewDecoder(input)
|
||||
dc.UseNumber()
|
||||
dc.Decode(&data) // data == json.Number("1")
|
||||
// use int64
|
||||
dc = decoder.NewDecoder(input)
|
||||
dc.UseInt64()
|
||||
dc.Decode(&data) // data == int64(1)
|
||||
|
||||
root, err := sonic.GetFromString(input)
|
||||
// Get json.Number
|
||||
jn := root.Number()
|
||||
jm := root.InterfaceUseNumber().(json.Number) // jn == jm
|
||||
// Get float64
|
||||
fn := root.Float64()
|
||||
fm := root.Interface().(float64) // jn == jm
|
||||
```
|
||||
|
||||
### 对键排序
|
||||
|
||||
考虑到排序带来的性能损失(约 10% ), sonic 默认不会启用这个功能。如果你的组件依赖这个行为(如 [zstd](https://github.com/facebook/zstd)) ,可以仿照下面的例子:
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
import "github.com/bytedance/sonic/encoder"
|
||||
|
||||
// Binding map only
|
||||
m := map[string]interface{}{}
|
||||
v, err := encoder.Encode(m, encoder.SortMapKeys)
|
||||
|
||||
// Or ast.Node.SortKeys() before marshal
|
||||
var root := sonic.Get(JSON)
|
||||
err := root.SortKeys()
|
||||
```
|
||||
|
||||
### HTML 转义
|
||||
|
||||
考虑到性能损失(约15%), sonic 默认不会启用这个功能。你可以使用 `encoder.EscapeHTML` 选项来开启(与 `encoding/json.HTMLEscape` 行为一致)。
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
v := map[string]string{"&&":"<>"}
|
||||
ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"}}`
|
||||
```
|
||||
|
||||
### 紧凑格式
|
||||
|
||||
Sonic 默认将基本类型( `struct` , `map` 等)编码为紧凑格式的 JSON ,除非使用 `json.RawMessage` or `json.Marshaler` 进行编码: sonic 确保输出的 JSON 合法,但出于性能考虑,**不会**加工成紧凑格式。我们提供选项 `encoder.CompactMarshaler` 来添加此过程,
|
||||
|
||||
### 打印错误
|
||||
|
||||
如果输入的 JSON 存在无效的语法,sonic 将返回 `decoder.SyntaxError`,该错误支持错误位置的美化输出。
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
import "github.com/bytedance/sonic/decoder"
|
||||
|
||||
var data interface{}
|
||||
err := sonic.UnmarshalString("[[[}]]", &data)
|
||||
if err != nil {
|
||||
/* One line by default */
|
||||
println(e.Error()) // "Syntax error at index 3: invalid char\n\n\t[[[}]]\n\t...^..\n"
|
||||
/* Pretty print */
|
||||
if e, ok := err.(decoder.SyntaxError); ok {
|
||||
/*Syntax error at index 3: invalid char
|
||||
|
||||
[[[}]]
|
||||
...^..
|
||||
*/
|
||||
print(e.Description())
|
||||
} else if me, ok := err.(*decoder.MismatchTypeError); ok {
|
||||
// decoder.MismatchTypeError is new to Sonic v1.6.0
|
||||
print(me.Description())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 类型不匹配 [Sonic v1.6.0]
|
||||
|
||||
如果给定键中存在**类型不匹配**的值, sonic 会抛出 `decoder.MismatchTypeError` (如果有多个,只会报告最后一个),但仍会跳过错误的值并解码下一个 JSON 。
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
import "github.com/bytedance/sonic/decoder"
|
||||
|
||||
var data = struct{
|
||||
A int
|
||||
B int
|
||||
}{}
|
||||
err := UnmarshalString(`{"A":"1","B":1}`, &data)
|
||||
println(err.Error()) // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
|
||||
fmt.Printf("%+v", data) // {A:0 B:1}
|
||||
```
|
||||
|
||||
### `Ast.Node`
|
||||
|
||||
Sonic/ast.Node 是完全独立的 JSON 抽象语法树库。它实现了序列化和反序列化,并提供了获取和修改JSON数据的鲁棒的 API。
|
||||
|
||||
#### 查找/索引
|
||||
|
||||
通过给定的路径搜索 JSON 片段,路径必须为非负整数,字符串或 `nil` 。
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
input := []byte(`{"key1":[{},{"key2":{"key3":[1,2,3]}}]}`)
|
||||
|
||||
// no path, returns entire json
|
||||
root, err := sonic.Get(input)
|
||||
raw := root.Raw() // == string(input)
|
||||
|
||||
// multiple paths
|
||||
root, err := sonic.Get(input, "key1", 1, "key2")
|
||||
sub := root.Get("key3").Index(2).Int64() // == 3
|
||||
```
|
||||
|
||||
**注意**:由于 `Index()` 使用偏移量来定位数据,比使用扫描的 `Get()` 要快的多,建议尽可能的使用 `Index` 。 Sonic 也提供了另一个 API, `IndexOrGet()` ,以偏移量为基础并且也确保键的匹配。
|
||||
|
||||
#### 查找选项
|
||||
|
||||
`ast.Searcher`提供了一些选项,以满足用户的不同需求:
|
||||
|
||||
```go
|
||||
opts := ast.SearchOption{CopyReturn: true…}
|
||||
val, err := sonic.GetWithOptions(JSON, opts, "key")
|
||||
```
|
||||
|
||||
- CopyReturn
|
||||
指示搜索器复制结果JSON字符串,而不是从输入引用。如果用户缓存结果,这有助于减少内存使用
|
||||
- ConcurentRead
|
||||
因为`ast.Node`使用`Lazy-Load`设计,默认不支持并发读取。如果您想同时读取,请指定它。
|
||||
- ValidateJSON
|
||||
指示搜索器来验证整个JSON。默认情况下启用该选项, 但是对于查找速度有一定影响。
|
||||
|
||||
#### 修改
|
||||
|
||||
使用 `Set()` / `Unset()` 修改 json 的内容
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
// Set
|
||||
exist, err := root.Set("key4", NewBool(true)) // exist == false
|
||||
alias1 := root.Get("key4")
|
||||
println(alias1.Valid()) // true
|
||||
alias2 := root.Index(1)
|
||||
println(alias1 == alias2) // true
|
||||
|
||||
// Unset
|
||||
exist, err := root.UnsetByIndex(1) // exist == true
|
||||
println(root.Get("key4").Check()) // "value not exist"
|
||||
```
|
||||
|
||||
#### 序列化
|
||||
|
||||
要将 `ast.Node` 编码为 json ,使用 `MarshalJson()` 或者 `json.Marshal()` (必须传递指向节点的指针)
|
||||
|
||||
```go
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
buf, err := root.MarshalJson()
|
||||
println(string(buf)) // {"key1":[{},{"key2":{"key3":[1,2,3]}}]}
|
||||
exp, err := json.Marshal(&root) // WARN: use pointer
|
||||
println(string(buf) == string(exp)) // true
|
||||
```
|
||||
|
||||
#### APIs
|
||||
|
||||
- 合法性检查: `Check()`, `Error()`, `Valid()`, `Exist()`
|
||||
- 索引: `Index()`, `Get()`, `IndexPair()`, `IndexOrGet()`, `GetByPath()`
|
||||
- 转换至 go 内置类型: `Int64()`, `Float64()`, `String()`, `Number()`, `Bool()`, `Map[UseNumber|UseNode]()`, `Array[UseNumber|UseNode]()`, `Interface[UseNumber|UseNode]()`
|
||||
- go 类型打包: `NewRaw()`, `NewNumber()`, `NewNull()`, `NewBool()`, `NewString()`, `NewObject()`, `NewArray()`
|
||||
- 迭代: `Values()`, `Properties()`, `ForEach()`, `SortKeys()`
|
||||
- 修改: `Set()`, `SetByIndex()`, `Add()`
|
||||
|
||||
### `Ast.Visitor`
|
||||
|
||||
Sonic 提供了一个高级的 API 用于直接全量解析 JSON 到非标准容器里 (既不是 `struct` 也不是 `map[string]interface{}`) 且不需要借助任何中间表示 (`ast.Node` 或 `interface{}`)。举个例子,你可能定义了下述的类型,它们看起来像 `interface{}`,但实际上并不是:
|
||||
|
||||
```go
|
||||
type UserNode interface {}
|
||||
|
||||
// the following types implement the UserNode interface.
|
||||
type (
|
||||
UserNull struct{}
|
||||
UserBool struct{ Value bool }
|
||||
UserInt64 struct{ Value int64 }
|
||||
UserFloat64 struct{ Value float64 }
|
||||
UserString struct{ Value string }
|
||||
UserObject struct{ Value map[string]UserNode }
|
||||
UserArray struct{ Value []UserNode }
|
||||
)
|
||||
```
|
||||
|
||||
Sonic 提供了下述的 API 来返回 **“对 JSON AST 的前序遍历”**。`ast.Visitor` 是一个 SAX 风格的接口,这在某些 C++ 的 JSON 解析库中被使用到。你需要自己实现一个 `ast.Visitor`,将它传递给 `ast.Preorder()` 方法。在你的实现中你可以使用自定义的类型来表示 JSON 的值。在你的 `ast.Visitor` 中,可能需要有一个 O(n) 空间复杂度的容器(比如说栈)来记录 object / array 的层级。
|
||||
|
||||
```go
|
||||
func Preorder(str string, visitor Visitor, opts *VisitorOptions) error
|
||||
|
||||
type Visitor interface {
|
||||
OnNull() error
|
||||
OnBool(v bool) error
|
||||
OnString(v string) error
|
||||
OnInt64(v int64, n json.Number) error
|
||||
OnFloat64(v float64, n json.Number) error
|
||||
OnObjectBegin(capacity int) error
|
||||
OnObjectKey(key string) error
|
||||
OnObjectEnd() error
|
||||
OnArrayBegin(capacity int) error
|
||||
OnArrayEnd() error
|
||||
}
|
||||
```
|
||||
|
||||
详细用法参看 [ast/visitor.go](https://github.com/bytedance/sonic/blob/main/ast/visitor.go),我们还为 `UserNode` 实现了一个示例 `ast.Visitor`,你可以在 [ast/visitor_test.go](https://github.com/bytedance/sonic/blob/main/ast/visitor_test.go) 中找到它。
|
||||
|
||||
## 兼容性
|
||||
|
||||
对于想要使用sonic来满足不同场景的开发人员,我们提供了一些集成配置:
|
||||
|
||||
- `ConfigDefault`: sonic的默认配置 (`EscapeHTML=false`, `SortKeys=false`…) 保证性能同时兼顾安全性。
|
||||
- `ConfigStd`: 与 `encoding/json` 保证完全兼容的配置
|
||||
- `ConfigFastest`: 最快的配置(`NoQuoteTextMarshaler=true...`) 保证性能最优但是会缺少一些安全性检查(validate UTF8 等)
|
||||
Sonic **不**确保支持所有环境,由于开发高性能代码的困难。在不支持sonic的环境中,实现将回落到 `encoding/json`。因此上述配置将全部等于`ConfigStd`。
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 预热
|
||||
|
||||
由于 Sonic 使用 [golang-asm](https://github.com/twitchyliquid64/golang-asm) 作为 JIT 汇编器,这个库并不适用于运行时编译,第一次运行一个大型模式可能会导致请求超时甚至进程内存溢出。为了更好地稳定性,我们建议在运行大型模式或在内存有限的应用中,在使用 `Marshal()/Unmarshal()` 前运行 `Pretouch()`。
|
||||
|
||||
```go
|
||||
import (
|
||||
"reflect"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/bytedance/sonic/option"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var v HugeStruct
|
||||
|
||||
// For most large types (nesting depth <= option.DefaultMaxInlineDepth)
|
||||
err := sonic.Pretouch(reflect.TypeOf(v))
|
||||
|
||||
// with more CompileOption...
|
||||
err := sonic.Pretouch(reflect.TypeOf(v),
|
||||
// If the type is too deep nesting (nesting depth > option.DefaultMaxInlineDepth),
|
||||
// you can set compile recursive loops in Pretouch for better stability in JIT.
|
||||
option.WithCompileRecursiveDepth(loop),
|
||||
// For a large nested struct, try to set a smaller depth to reduce compiling time.
|
||||
option.WithCompileMaxInlineDepth(depth),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 拷贝字符串
|
||||
|
||||
当解码 **没有转义字符的字符串**时, sonic 会从原始的 JSON 缓冲区内引用而不是复制到新的一个缓冲区中。这对 CPU 的性能方面很有帮助,但是可能因此在解码后对象仍在使用的时候将整个 JSON 缓冲区保留在内存中。实践中我们发现,通过引用 JSON 缓冲区引入的额外内存通常是解码后对象的 20% 至 80% ,一旦应用长期保留这些对象(如缓存以备重用),服务器所使用的内存可能会增加。我们提供了选项 `decoder.CopyString()` 供用户选择,不引用 JSON 缓冲区。这可能在一定程度上降低 CPU 性能。
|
||||
|
||||
### 传递字符串还是字节数组?
|
||||
|
||||
为了和 `encoding/json` 保持一致,我们提供了传递 `[]byte` 作为参数的 API ,但考虑到安全性,字符串到字节的复制是同时进行的,这在原始 JSON 非常大时可能会导致性能损失。因此,你可以使用 `UnmarshalString()` 和 `GetFromString()` 来传递字符串,只要你的原始数据是字符串,或**零拷贝类型转换**对于你的字节数组是安全的。我们也提供了 `MarshalString()` 的 API ,以便对编码的 JSON 字节数组进行**零拷贝类型转换**,因为 sonic 输出的字节始终是重复并且唯一的,所以这样是安全的。
|
||||
|
||||
### 加速 `encoding.TextMarshaler`
|
||||
|
||||
为了保证数据安全性, `sonic.Encoder` 默认会对来自 `encoding.TextMarshaler` 接口的字符串进行引用和转义,如果大部分数据都是这种形式那可能会导致很大的性能损失。我们提供了 `encoder.NoQuoteTextMarshaler` 选项来跳过这些操作,但你**必须**保证他们的输出字符串依照 [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259) 进行了转义和引用。
|
||||
|
||||
### 泛型的性能优化
|
||||
|
||||
在 **完全解析**的场景下, `Unmarshal()` 表现得比 `Get()`+`Node.Interface()` 更好。但是如果你只有特定 JSON 的部分模式,你可以将 `Get()` 和 `Unmarshal()` 结合使用:
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
node, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user")
|
||||
var user User // your partial schema...
|
||||
err = sonic.UnmarshalString(node.Raw(), &user)
|
||||
```
|
||||
|
||||
甚至如果你没有任何模式,可以用 `ast.Node` 代替 `map` 或 `interface` 作为泛型的容器:
|
||||
|
||||
```go
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
root, err := sonic.GetFromString(_TwitterJson)
|
||||
user := root.GetByPath("statuses", 3, "user") // === root.Get("status").Index(3).Get("user")
|
||||
err = user.Check()
|
||||
|
||||
// err = user.LoadAll() // only call this when you want to use 'user' concurrently...
|
||||
go someFunc(user)
|
||||
```
|
||||
|
||||
为什么?因为 `ast.Node` 使用 `array` 来存储其子节点:
|
||||
|
||||
- 在插入(反序列化)和扫描(序列化)数据时,`Array` 的性能比 `Map` **好得多**;
|
||||
- **哈希**(`map[x]`)的效率不如**索引**(`array[x]`)高效,而 `ast.Node` 可以在数组和对象上使用索引;
|
||||
- 使用 `Interface()` / `Map()` 意味着 sonic 必须解析所有的底层值,而 `ast.Node` 可以**按需解析**它们。
|
||||
|
||||
**注意**:由于 `ast.Node` 的惰性加载设计,其**不能**直接保证并发安全性,但你可以调用 `Node.Load()` / `Node.LoadAll()` 来实现并发安全。尽管可能会带来性能损失,但仍比转换成 `map` 或 `interface{}` 更为高效。
|
||||
|
||||
### 使用 `ast.Node` 还是 `ast.Visitor`?
|
||||
|
||||
对于泛型数据的解析,`ast.Node` 在大多数场景上应该能够满足你的需求。
|
||||
|
||||
然而,`ast.Node` 是一种针对部分解析 JSON 而设计的泛型容器,它包含一些特殊设计,比如惰性加载,如果你希望像 `Unmarshal()` 那样直接解析整个 JSON,这些设计可能并不合适。尽管 `ast.Node` 相较于 `map` 或 `interface{}` 来说是更好的一种泛型容器,但它毕竟也是一种中间表示,如果你的最终类型是自定义的,你还得在解析完成后将上述类型转化成你自定义的类型。
|
||||
|
||||
在上述场景中,如果想要有更极致的性能,`ast.Visitor` 会是更好的选择。它采用和 `Unmarshal()` 类似的形式解析 JSON,并且你可以直接使用你的最终类型去表示 JSON AST,而不需要经过额外的任何中间表示。
|
||||
|
||||
但是,`ast.Visitor` 并不是一个很易用的 API。你可能需要写大量的代码去实现自己的 `ast.Visitor`,并且需要在解析过程中仔细维护树的层级。如果你决定要使用这个 API,请先仔细阅读 [ast/visitor.go](https://github.com/bytedance/sonic/blob/main/ast/visitor.go) 中的注释。
|
||||
|
||||
### 缓冲区大小
|
||||
|
||||
Sonic在许多地方使用内存池,如`encoder.Encode`, `ast.Node.MarshalJSON`等来提高性能,这可能会在服务器负载高时产生更多的内存使用(in-use)。参见[issue 614](https://github.com/bytedance/sonic/issues/614)。因此,我们引入了一些选项来让用户配置内存池的行为。参见[option](https://pkg.go.dev/github.com/bytedance/sonic@v1.11.9/option#pkg-variables)包。
|
||||
|
||||
### 更快的 JSON Skip
|
||||
|
||||
为了安全起见,在跳过原始JSON 时,sonic decoder 默认使用[FSM](native/skip_one.c)算法扫描来跳过同时校验 JSON。它相比[SIMD-searching-pair](native/skip_one_fast.c)算法跳过要慢得多(1~10倍)。如果用户有很多冗余的JSON值,并且不需要严格验证JSON的正确性,你可以启用以下选项:
|
||||
|
||||
- `Config.NoValidateSkipJSON`: 用于在解码时更快地跳过JSON,例如未知字段,`json.RawMessage`,不匹配的值和冗余的数组元素等
|
||||
- `Config.NoValidateJSONMarshaler`: 编码JSON时避免验证JSON。封送拆收器
|
||||
- `SearchOption.ValidateJSON`: 指示当`Get`时是否验证定位的JSON值
|
||||
|
||||
## 社区
|
||||
|
||||
Sonic 是 [CloudWeGo](https://www.cloudwego.io/) 下的一个子项目。我们致力于构建云原生生态系统。
|
||||
249
vendor/github.com/bytedance/sonic/api.go
generated
vendored
Normal file
249
vendor/github.com/bytedance/sonic/api.go
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sonic
|
||||
|
||||
import (
|
||||
`io`
|
||||
|
||||
`github.com/bytedance/sonic/ast`
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
)
|
||||
|
||||
const (
|
||||
// UseStdJSON indicates you are using fallback implementation (encoding/json)
|
||||
UseStdJSON = iota
|
||||
// UseSonicJSON indicates you are using real sonic implementation
|
||||
UseSonicJSON
|
||||
)
|
||||
|
||||
// APIKind is the kind of API, 0 is std json, 1 is sonic.
|
||||
const APIKind = apiKind
|
||||
|
||||
// Config is a combination of sonic/encoder.Options and sonic/decoder.Options
|
||||
type Config struct {
|
||||
// EscapeHTML indicates encoder to escape all HTML characters
|
||||
// after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape).
|
||||
// WARNING: This hurts performance A LOT, USE WITH CARE.
|
||||
EscapeHTML bool
|
||||
|
||||
// SortMapKeys indicates encoder that the keys of a map needs to be sorted
|
||||
// before serializing into JSON.
|
||||
// WARNING: This hurts performance A LOT, USE WITH CARE.
|
||||
SortMapKeys bool
|
||||
|
||||
// CompactMarshaler indicates encoder that the output JSON from json.Marshaler
|
||||
// is always compact and needs no validation
|
||||
CompactMarshaler bool
|
||||
|
||||
// NoQuoteTextMarshaler indicates encoder that the output text from encoding.TextMarshaler
|
||||
// is always escaped string and needs no quoting
|
||||
NoQuoteTextMarshaler bool
|
||||
|
||||
// NoNullSliceOrMap indicates encoder that all empty Array or Object are encoded as '[]' or '{}',
|
||||
// instead of 'null'
|
||||
NoNullSliceOrMap bool
|
||||
|
||||
// UseInt64 indicates decoder to unmarshal an integer into an interface{} as an
|
||||
// int64 instead of as a float64.
|
||||
UseInt64 bool
|
||||
|
||||
// UseNumber indicates decoder to unmarshal a number into an interface{} as a
|
||||
// json.Number instead of as a float64.
|
||||
UseNumber bool
|
||||
|
||||
// UseUnicodeErrors indicates decoder to return an error when encounter invalid
|
||||
// UTF-8 escape sequences.
|
||||
UseUnicodeErrors bool
|
||||
|
||||
// DisallowUnknownFields indicates decoder to return an error when the destination
|
||||
// is a struct and the input contains object keys which do not match any
|
||||
// non-ignored, exported fields in the destination.
|
||||
DisallowUnknownFields bool
|
||||
|
||||
// CopyString indicates decoder to decode string values by copying instead of referring.
|
||||
CopyString bool
|
||||
|
||||
// ValidateString indicates decoder and encoder to validate string values: decoder will return errors
|
||||
// when unescaped control chars(\u0000-\u001f) in the string value of JSON.
|
||||
ValidateString bool
|
||||
|
||||
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
|
||||
// after encoding the JSONMarshaler to JSON.
|
||||
NoValidateJSONMarshaler bool
|
||||
|
||||
// NoValidateJSONSkip indicates the decoder should not validate the JSON value when skipping it,
|
||||
// such as unknown-fields, mismatched-type, redundant elements..
|
||||
NoValidateJSONSkip bool
|
||||
|
||||
// NoEncoderNewline indicates that the encoder should not add a newline after every message
|
||||
NoEncoderNewline bool
|
||||
|
||||
// Encode Infinity or Nan float into `null`, instead of returning an error.
|
||||
EncodeNullForInfOrNan bool
|
||||
|
||||
// CaseSensitive indicates that the decoder should not ignore the case of object keys.
|
||||
CaseSensitive bool
|
||||
}
|
||||
|
||||
var (
|
||||
// ConfigDefault is the default config of APIs, aiming at efficiency and safety.
|
||||
ConfigDefault = Config{}.Froze()
|
||||
|
||||
// ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json.
|
||||
ConfigStd = Config{
|
||||
EscapeHTML : true,
|
||||
SortMapKeys: true,
|
||||
CompactMarshaler: true,
|
||||
CopyString : true,
|
||||
ValidateString : true,
|
||||
}.Froze()
|
||||
|
||||
// ConfigFastest is the fastest config of APIs, aiming at speed.
|
||||
ConfigFastest = Config{
|
||||
NoValidateJSONMarshaler: true,
|
||||
NoValidateJSONSkip: true,
|
||||
}.Froze()
|
||||
)
|
||||
|
||||
|
||||
// API is a binding of specific config.
|
||||
// This interface is inspired by github.com/json-iterator/go,
|
||||
// and has same behaviors under equivalent config.
|
||||
type API interface {
|
||||
// MarshalToString returns the JSON encoding string of v
|
||||
MarshalToString(v interface{}) (string, error)
|
||||
// Marshal returns the JSON encoding bytes of v.
|
||||
Marshal(v interface{}) ([]byte, error)
|
||||
// MarshalIndent returns the JSON encoding bytes with indent and prefix.
|
||||
MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
|
||||
// UnmarshalFromString parses the JSON-encoded bytes and stores the result in the value pointed to by v.
|
||||
UnmarshalFromString(str string, v interface{}) error
|
||||
// Unmarshal parses the JSON-encoded string and stores the result in the value pointed to by v.
|
||||
Unmarshal(data []byte, v interface{}) error
|
||||
// NewEncoder create a Encoder holding writer
|
||||
NewEncoder(writer io.Writer) Encoder
|
||||
// NewDecoder create a Decoder holding reader
|
||||
NewDecoder(reader io.Reader) Decoder
|
||||
// Valid validates the JSON-encoded bytes and reports if it is valid
|
||||
Valid(data []byte) bool
|
||||
}
|
||||
|
||||
// Encoder encodes JSON into io.Writer
|
||||
type Encoder interface {
|
||||
// Encode writes the JSON encoding of v to the stream, followed by a newline character.
|
||||
Encode(val interface{}) error
|
||||
// SetEscapeHTML specifies whether problematic HTML characters
|
||||
// should be escaped inside JSON quoted strings.
|
||||
// The default behavior NOT ESCAPE
|
||||
SetEscapeHTML(on bool)
|
||||
// SetIndent instructs the encoder to format each subsequent encoded value
|
||||
// as if indented by the package-level function Indent(dst, src, prefix, indent).
|
||||
// Calling SetIndent("", "") disables indentation
|
||||
SetIndent(prefix, indent string)
|
||||
}
|
||||
|
||||
// Decoder decodes JSON from io.Read
|
||||
type Decoder interface {
|
||||
// Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v.
|
||||
Decode(val interface{}) error
|
||||
// Buffered returns a reader of the data remaining in the Decoder's buffer.
|
||||
// The reader is valid until the next call to Decode.
|
||||
Buffered() io.Reader
|
||||
// DisallowUnknownFields causes the Decoder to return an error when the destination is a struct
|
||||
// and the input contains object keys which do not match any non-ignored, exported fields in the destination.
|
||||
DisallowUnknownFields()
|
||||
// More reports whether there is another element in the current array or object being parsed.
|
||||
More() bool
|
||||
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64.
|
||||
UseNumber()
|
||||
}
|
||||
|
||||
// Marshal returns the JSON encoding bytes of v.
|
||||
func Marshal(val interface{}) ([]byte, error) {
|
||||
return ConfigDefault.Marshal(val)
|
||||
}
|
||||
|
||||
// MarshalIndent is like Marshal but applies Indent to format the output.
|
||||
// Each JSON element in the output will begin on a new line beginning with prefix
|
||||
// followed by one or more copies of indent according to the indentation nesting.
|
||||
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
|
||||
return ConfigDefault.MarshalIndent(v, prefix, indent)
|
||||
}
|
||||
|
||||
// MarshalString returns the JSON encoding string of v.
|
||||
func MarshalString(val interface{}) (string, error) {
|
||||
return ConfigDefault.MarshalToString(val)
|
||||
}
|
||||
|
||||
// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||
// NOTICE: This API copies given buffer by default,
|
||||
// if you want to pass JSON more efficiently, use UnmarshalString instead.
|
||||
func Unmarshal(buf []byte, val interface{}) error {
|
||||
return ConfigDefault.Unmarshal(buf, val)
|
||||
}
|
||||
|
||||
// UnmarshalString is like Unmarshal, except buf is a string.
|
||||
func UnmarshalString(buf string, val interface{}) error {
|
||||
return ConfigDefault.UnmarshalFromString(buf, val)
|
||||
}
|
||||
|
||||
// Get searches and locates the given path from src json,
|
||||
// and returns a ast.Node representing the partially json.
|
||||
//
|
||||
// Each path arg must be integer or string:
|
||||
// - Integer is target index(>=0), means searching current node as array.
|
||||
// - String is target key, means searching current node as object.
|
||||
//
|
||||
//
|
||||
// Notice: It expects the src json is **Well-formed** and **Immutable** when calling,
|
||||
// otherwise it may return unexpected result.
|
||||
// Considering memory safety, the returned JSON is **Copied** from the input
|
||||
func Get(src []byte, path ...interface{}) (ast.Node, error) {
|
||||
return GetCopyFromString(rt.Mem2Str(src), path...)
|
||||
}
|
||||
|
||||
//GetWithOptions searches and locates the given path from src json,
|
||||
// with specific options of ast.Searcher
|
||||
func GetWithOptions(src []byte, opts ast.SearchOptions, path ...interface{}) (ast.Node, error) {
|
||||
s := ast.NewSearcher(rt.Mem2Str(src))
|
||||
s.SearchOptions = opts
|
||||
return s.GetByPath(path...)
|
||||
}
|
||||
|
||||
// GetFromString is same with Get except src is string.
|
||||
//
|
||||
// WARNING: The returned JSON is **Referenced** from the input.
|
||||
// Caching or long-time holding the returned node may cause OOM.
|
||||
// If your src is big, consider use GetFromStringCopy().
|
||||
func GetFromString(src string, path ...interface{}) (ast.Node, error) {
|
||||
return ast.NewSearcher(src).GetByPath(path...)
|
||||
}
|
||||
|
||||
// GetCopyFromString is same with Get except src is string
|
||||
func GetCopyFromString(src string, path ...interface{}) (ast.Node, error) {
|
||||
return ast.NewSearcher(src).GetByPathCopy(path...)
|
||||
}
|
||||
|
||||
// Valid reports whether data is a valid JSON encoding.
|
||||
func Valid(data []byte) bool {
|
||||
return ConfigDefault.Valid(data)
|
||||
}
|
||||
|
||||
// Valid reports whether data is a valid JSON encoding.
|
||||
func ValidString(data string) bool {
|
||||
return ConfigDefault.Valid(rt.Str2Mem(data))
|
||||
}
|
||||
94
vendor/github.com/bytedance/sonic/ast/api.go
generated
vendored
Normal file
94
vendor/github.com/bytedance/sonic/ast/api.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27)
|
||||
// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27
|
||||
|
||||
/*
|
||||
* Copyright 2022 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bytedance/sonic/encoder"
|
||||
"github.com/bytedance/sonic/internal/encoder/alg"
|
||||
"github.com/bytedance/sonic/internal/native"
|
||||
"github.com/bytedance/sonic/internal/native/types"
|
||||
"github.com/bytedance/sonic/internal/rt"
|
||||
"github.com/bytedance/sonic/utf8"
|
||||
)
|
||||
|
||||
var typeByte = rt.UnpackEface(byte(0)).Type
|
||||
|
||||
func quote(buf *[]byte, val string) {
|
||||
*buf = alg.Quote(*buf, val, false)
|
||||
}
|
||||
|
||||
func (self *Parser) decodeValue() (val types.JsonState) {
|
||||
sv := (*rt.GoString)(unsafe.Pointer(&self.s))
|
||||
flag := types.F_USE_NUMBER
|
||||
if self.dbuf != nil {
|
||||
flag = 0
|
||||
val.Dbuf = self.dbuf
|
||||
val.Dcap = types.MaxDigitNums
|
||||
}
|
||||
self.p = native.Value(sv.Ptr, sv.Len, self.p, &val, uint64(flag))
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Parser) skip() (int, types.ParsingError) {
|
||||
fsm := types.NewStateMachine()
|
||||
start := native.SkipOne(&self.s, &self.p, fsm, 0)
|
||||
types.FreeStateMachine(fsm)
|
||||
|
||||
if start < 0 {
|
||||
return self.p, types.ParsingError(-start)
|
||||
}
|
||||
return start, 0
|
||||
}
|
||||
|
||||
func (self *Node) encodeInterface(buf *[]byte) error {
|
||||
//WARN: NOT compatible with json.Encoder
|
||||
return encoder.EncodeInto(buf, self.packAny(), encoder.NoEncoderNewline)
|
||||
}
|
||||
|
||||
func (self *Parser) skipFast() (int, types.ParsingError) {
|
||||
start := native.SkipOneFast(&self.s, &self.p)
|
||||
if start < 0 {
|
||||
return self.p, types.ParsingError(-start)
|
||||
}
|
||||
return start, 0
|
||||
}
|
||||
|
||||
func (self *Parser) getByPath(validate bool, path ...interface{}) (int, types.ParsingError) {
|
||||
var fsm *types.StateMachine
|
||||
if validate {
|
||||
fsm = types.NewStateMachine()
|
||||
}
|
||||
start := native.GetByPath(&self.s, &self.p, &path, fsm)
|
||||
if validate {
|
||||
types.FreeStateMachine(fsm)
|
||||
}
|
||||
runtime.KeepAlive(path)
|
||||
if start < 0 {
|
||||
return self.p, types.ParsingError(-start)
|
||||
}
|
||||
return start, 0
|
||||
}
|
||||
|
||||
func validate_utf8(str string) bool {
|
||||
return utf8.ValidateString(str)
|
||||
}
|
||||
103
vendor/github.com/bytedance/sonic/ast/api_compat.go
generated
vendored
Normal file
103
vendor/github.com/bytedance/sonic/ast/api_compat.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20
|
||||
|
||||
/*
|
||||
* Copyright 2022 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
`encoding/json`
|
||||
`unicode/utf8`
|
||||
|
||||
`github.com/bytedance/sonic/internal/native/types`
|
||||
`github.com/bytedance/sonic/internal/compat`
|
||||
)
|
||||
|
||||
func init() {
|
||||
compat.Warn("sonic/ast")
|
||||
}
|
||||
|
||||
func quote(buf *[]byte, val string) {
|
||||
quoteString(buf, val)
|
||||
}
|
||||
|
||||
func (self *Parser) decodeValue() (val types.JsonState) {
|
||||
e, v := decodeValue(self.s, self.p, self.dbuf == nil)
|
||||
if e < 0 {
|
||||
return v
|
||||
}
|
||||
self.p = e
|
||||
return v
|
||||
}
|
||||
|
||||
func (self *Parser) skip() (int, types.ParsingError) {
|
||||
e, s := skipValue(self.s, self.p)
|
||||
if e < 0 {
|
||||
return self.p, types.ParsingError(-e)
|
||||
}
|
||||
self.p = e
|
||||
return s, 0
|
||||
}
|
||||
|
||||
func (self *Parser) skipFast() (int, types.ParsingError) {
|
||||
e, s := skipValueFast(self.s, self.p)
|
||||
if e < 0 {
|
||||
return self.p, types.ParsingError(-e)
|
||||
}
|
||||
self.p = e
|
||||
return s, 0
|
||||
}
|
||||
|
||||
func (self *Node) encodeInterface(buf *[]byte) error {
|
||||
out, err := json.Marshal(self.packAny())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*buf = append(*buf, out...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Parser) getByPath(validate bool, path ...interface{}) (int, types.ParsingError) {
|
||||
for _, p := range path {
|
||||
if idx, ok := p.(int); ok && idx >= 0 {
|
||||
if err := self.searchIndex(idx); err != 0 {
|
||||
return self.p, err
|
||||
}
|
||||
} else if key, ok := p.(string); ok {
|
||||
if err := self.searchKey(key); err != 0 {
|
||||
return self.p, err
|
||||
}
|
||||
} else {
|
||||
panic("path must be either int(>=0) or string")
|
||||
}
|
||||
}
|
||||
|
||||
var start int
|
||||
var e types.ParsingError
|
||||
if validate {
|
||||
start, e = self.skip()
|
||||
} else {
|
||||
start, e = self.skipFast()
|
||||
}
|
||||
if e != 0 {
|
||||
return self.p, e
|
||||
}
|
||||
return start, 0
|
||||
}
|
||||
|
||||
func validate_utf8(str string) bool {
|
||||
return utf8.ValidString(str)
|
||||
}
|
||||
0
vendor/github.com/bytedance/sonic/ast/asm.s
generated
vendored
Normal file
0
vendor/github.com/bytedance/sonic/ast/asm.s
generated
vendored
Normal file
470
vendor/github.com/bytedance/sonic/ast/buffer.go
generated
vendored
Normal file
470
vendor/github.com/bytedance/sonic/ast/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,470 @@
|
||||
/**
|
||||
* Copyright 2023 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bytedance/sonic/internal/caching"
|
||||
)
|
||||
|
||||
type nodeChunk [_DEFAULT_NODE_CAP]Node
|
||||
|
||||
type linkedNodes struct {
|
||||
head nodeChunk
|
||||
tail []*nodeChunk
|
||||
size int
|
||||
}
|
||||
|
||||
func (self *linkedNodes) Cap() int {
|
||||
if self == nil {
|
||||
return 0
|
||||
}
|
||||
return (len(self.tail)+1)*_DEFAULT_NODE_CAP
|
||||
}
|
||||
|
||||
func (self *linkedNodes) Len() int {
|
||||
if self == nil {
|
||||
return 0
|
||||
}
|
||||
return self.size
|
||||
}
|
||||
|
||||
func (self *linkedNodes) At(i int) (*Node) {
|
||||
if self == nil {
|
||||
return nil
|
||||
}
|
||||
if i >= 0 && i<self.size && i < _DEFAULT_NODE_CAP {
|
||||
return &self.head[i]
|
||||
} else if i >= _DEFAULT_NODE_CAP && i<self.size {
|
||||
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
|
||||
if a < len(self.tail) {
|
||||
return &self.tail[a][b]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *linkedNodes) MoveOne(source int, target int) {
|
||||
if source == target {
|
||||
return
|
||||
}
|
||||
if source < 0 || source >= self.size || target < 0 || target >= self.size {
|
||||
return
|
||||
}
|
||||
// reserve source
|
||||
n := *self.At(source)
|
||||
if source < target {
|
||||
// move every element (source,target] one step back
|
||||
for i:=source; i<target; i++ {
|
||||
*self.At(i) = *self.At(i+1)
|
||||
}
|
||||
} else {
|
||||
// move every element [target,source) one step forward
|
||||
for i:=source; i>target; i-- {
|
||||
*self.At(i) = *self.At(i-1)
|
||||
}
|
||||
}
|
||||
// set target
|
||||
*self.At(target) = n
|
||||
}
|
||||
|
||||
func (self *linkedNodes) Pop() {
|
||||
if self == nil || self.size == 0 {
|
||||
return
|
||||
}
|
||||
self.Set(self.size-1, Node{})
|
||||
self.size--
|
||||
}
|
||||
|
||||
func (self *linkedNodes) Push(v Node) {
|
||||
self.Set(self.size, v)
|
||||
}
|
||||
|
||||
|
||||
func (self *linkedNodes) Set(i int, v Node) {
|
||||
if i < _DEFAULT_NODE_CAP {
|
||||
self.head[i] = v
|
||||
if self.size <= i {
|
||||
self.size = i+1
|
||||
}
|
||||
return
|
||||
}
|
||||
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
|
||||
if a < 0 {
|
||||
self.head[b] = v
|
||||
} else {
|
||||
self.growTailLength(a+1)
|
||||
var n = &self.tail[a]
|
||||
if *n == nil {
|
||||
*n = new(nodeChunk)
|
||||
}
|
||||
(*n)[b] = v
|
||||
}
|
||||
if self.size <= i {
|
||||
self.size = i+1
|
||||
}
|
||||
}
|
||||
|
||||
func (self *linkedNodes) growTailLength(l int) {
|
||||
if l <= len(self.tail) {
|
||||
return
|
||||
}
|
||||
c := cap(self.tail)
|
||||
for c < l {
|
||||
c += 1 + c>>_APPEND_GROW_SHIFT
|
||||
}
|
||||
if c == cap(self.tail) {
|
||||
self.tail = self.tail[:l]
|
||||
return
|
||||
}
|
||||
tmp := make([]*nodeChunk, l, c)
|
||||
copy(tmp, self.tail)
|
||||
self.tail = tmp
|
||||
}
|
||||
|
||||
func (self *linkedNodes) ToSlice(con []Node) {
|
||||
if len(con) < self.size {
|
||||
return
|
||||
}
|
||||
i := (self.size-1)
|
||||
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
|
||||
if a < 0 {
|
||||
copy(con, self.head[:b+1])
|
||||
return
|
||||
} else {
|
||||
copy(con, self.head[:])
|
||||
con = con[_DEFAULT_NODE_CAP:]
|
||||
}
|
||||
|
||||
for i:=0; i<a; i++ {
|
||||
copy(con, self.tail[i][:])
|
||||
con = con[_DEFAULT_NODE_CAP:]
|
||||
}
|
||||
copy(con, self.tail[a][:b+1])
|
||||
}
|
||||
|
||||
func (self *linkedNodes) FromSlice(con []Node) {
|
||||
self.size = len(con)
|
||||
i := self.size-1
|
||||
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
|
||||
if a < 0 {
|
||||
copy(self.head[:b+1], con)
|
||||
return
|
||||
} else {
|
||||
copy(self.head[:], con)
|
||||
con = con[_DEFAULT_NODE_CAP:]
|
||||
}
|
||||
|
||||
if cap(self.tail) <= a {
|
||||
c := (a+1) + (a+1)>>_APPEND_GROW_SHIFT
|
||||
self.tail = make([]*nodeChunk, a+1, c)
|
||||
}
|
||||
self.tail = self.tail[:a+1]
|
||||
|
||||
for i:=0; i<a; i++ {
|
||||
self.tail[i] = new(nodeChunk)
|
||||
copy(self.tail[i][:], con)
|
||||
con = con[_DEFAULT_NODE_CAP:]
|
||||
}
|
||||
|
||||
self.tail[a] = new(nodeChunk)
|
||||
copy(self.tail[a][:b+1], con)
|
||||
}
|
||||
|
||||
type pairChunk [_DEFAULT_NODE_CAP]Pair
|
||||
|
||||
type linkedPairs struct {
|
||||
index map[uint64]int
|
||||
head pairChunk
|
||||
tail []*pairChunk
|
||||
size int
|
||||
}
|
||||
|
||||
func (self *linkedPairs) BuildIndex() {
|
||||
if self.index == nil {
|
||||
self.index = make(map[uint64]int, self.size)
|
||||
}
|
||||
for i:=0; i<self.size; i++ {
|
||||
p := self.At(i)
|
||||
self.index[p.hash] = i
|
||||
}
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Cap() int {
|
||||
if self == nil {
|
||||
return 0
|
||||
}
|
||||
return (len(self.tail)+1)*_DEFAULT_NODE_CAP
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Len() int {
|
||||
if self == nil {
|
||||
return 0
|
||||
}
|
||||
return self.size
|
||||
}
|
||||
|
||||
func (self *linkedPairs) At(i int) *Pair {
|
||||
if self == nil {
|
||||
return nil
|
||||
}
|
||||
if i >= 0 && i < _DEFAULT_NODE_CAP && i<self.size {
|
||||
return &self.head[i]
|
||||
} else if i >= _DEFAULT_NODE_CAP && i<self.size {
|
||||
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
|
||||
if a < len(self.tail) {
|
||||
return &self.tail[a][b]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Push(v Pair) {
|
||||
self.Set(self.size, v)
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Pop() {
|
||||
if self == nil || self.size == 0 {
|
||||
return
|
||||
}
|
||||
self.Unset(self.size-1)
|
||||
self.size--
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Unset(i int) {
|
||||
if self.index != nil {
|
||||
p := self.At(i)
|
||||
delete(self.index, p.hash)
|
||||
}
|
||||
self.set(i, Pair{})
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Set(i int, v Pair) {
|
||||
if self.index != nil {
|
||||
h := v.hash
|
||||
self.index[h] = i
|
||||
}
|
||||
self.set(i, v)
|
||||
}
|
||||
|
||||
func (self *linkedPairs) set(i int, v Pair) {
|
||||
if i < _DEFAULT_NODE_CAP {
|
||||
self.head[i] = v
|
||||
if self.size <= i {
|
||||
self.size = i+1
|
||||
}
|
||||
return
|
||||
}
|
||||
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
|
||||
if a < 0 {
|
||||
self.head[b] = v
|
||||
} else {
|
||||
self.growTailLength(a+1)
|
||||
var n = &self.tail[a]
|
||||
if *n == nil {
|
||||
*n = new(pairChunk)
|
||||
}
|
||||
(*n)[b] = v
|
||||
}
|
||||
if self.size <= i {
|
||||
self.size = i+1
|
||||
}
|
||||
}
|
||||
|
||||
func (self *linkedPairs) growTailLength(l int) {
|
||||
if l <= len(self.tail) {
|
||||
return
|
||||
}
|
||||
c := cap(self.tail)
|
||||
for c < l {
|
||||
c += 1 + c>>_APPEND_GROW_SHIFT
|
||||
}
|
||||
if c == cap(self.tail) {
|
||||
self.tail = self.tail[:l]
|
||||
return
|
||||
}
|
||||
tmp := make([]*pairChunk, l, c)
|
||||
copy(tmp, self.tail)
|
||||
self.tail = tmp
|
||||
}
|
||||
|
||||
// linear search
|
||||
func (self *linkedPairs) Get(key string) (*Pair, int) {
|
||||
if self.index != nil {
|
||||
// fast-path
|
||||
i, ok := self.index[caching.StrHash(key)]
|
||||
if ok {
|
||||
n := self.At(i)
|
||||
if n.Key == key {
|
||||
return n, i
|
||||
}
|
||||
// hash conflicts
|
||||
goto linear_search
|
||||
} else {
|
||||
return nil, -1
|
||||
}
|
||||
}
|
||||
linear_search:
|
||||
for i:=0; i<self.size; i++ {
|
||||
if n := self.At(i); n.Key == key {
|
||||
return n, i
|
||||
}
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (self *linkedPairs) ToSlice(con []Pair) {
|
||||
if len(con) < self.size {
|
||||
return
|
||||
}
|
||||
i := self.size-1
|
||||
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
|
||||
|
||||
if a < 0 {
|
||||
copy(con, self.head[:b+1])
|
||||
return
|
||||
} else {
|
||||
copy(con, self.head[:])
|
||||
con = con[_DEFAULT_NODE_CAP:]
|
||||
}
|
||||
|
||||
for i:=0; i<a; i++ {
|
||||
copy(con, self.tail[i][:])
|
||||
con = con[_DEFAULT_NODE_CAP:]
|
||||
}
|
||||
copy(con, self.tail[a][:b+1])
|
||||
}
|
||||
|
||||
func (self *linkedPairs) ToMap(con map[string]Node) {
|
||||
for i:=0; i<self.size; i++ {
|
||||
n := self.At(i)
|
||||
con[n.Key] = n.Value
|
||||
}
|
||||
}
|
||||
|
||||
func (self *linkedPairs) copyPairs(to []Pair, from []Pair, l int) {
|
||||
copy(to, from)
|
||||
if self.index != nil {
|
||||
for i:=0; i<l; i++ {
|
||||
// NOTICE: in case of user not pass hash, just cal it
|
||||
h := caching.StrHash(from[i].Key)
|
||||
from[i].hash = h
|
||||
self.index[h] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *linkedPairs) FromSlice(con []Pair) {
|
||||
self.size = len(con)
|
||||
i := self.size-1
|
||||
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
|
||||
if a < 0 {
|
||||
self.copyPairs(self.head[:b+1], con, b+1)
|
||||
return
|
||||
} else {
|
||||
self.copyPairs(self.head[:], con, len(self.head))
|
||||
con = con[_DEFAULT_NODE_CAP:]
|
||||
}
|
||||
|
||||
if cap(self.tail) <= a {
|
||||
c := (a+1) + (a+1)>>_APPEND_GROW_SHIFT
|
||||
self.tail = make([]*pairChunk, a+1, c)
|
||||
}
|
||||
self.tail = self.tail[:a+1]
|
||||
|
||||
for i:=0; i<a; i++ {
|
||||
self.tail[i] = new(pairChunk)
|
||||
self.copyPairs(self.tail[i][:], con, len(self.tail[i]))
|
||||
con = con[_DEFAULT_NODE_CAP:]
|
||||
}
|
||||
|
||||
self.tail[a] = new(pairChunk)
|
||||
self.copyPairs(self.tail[a][:b+1], con, b+1)
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Less(i, j int) bool {
|
||||
return lessFrom(self.At(i).Key, self.At(j).Key, 0)
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Swap(i, j int) {
|
||||
a, b := self.At(i), self.At(j)
|
||||
if self.index != nil {
|
||||
self.index[a.hash] = j
|
||||
self.index[b.hash] = i
|
||||
}
|
||||
*a, *b = *b, *a
|
||||
}
|
||||
|
||||
func (self *linkedPairs) Sort() {
|
||||
sort.Stable(self)
|
||||
}
|
||||
|
||||
// Compare two strings from the pos d.
|
||||
func lessFrom(a, b string, d int) bool {
|
||||
l := len(a)
|
||||
if l > len(b) {
|
||||
l = len(b)
|
||||
}
|
||||
for i := d; i < l; i++ {
|
||||
if a[i] == b[i] {
|
||||
continue
|
||||
}
|
||||
return a[i] < b[i]
|
||||
}
|
||||
return len(a) < len(b)
|
||||
}
|
||||
|
||||
type parseObjectStack struct {
|
||||
parser Parser
|
||||
v linkedPairs
|
||||
}
|
||||
|
||||
type parseArrayStack struct {
|
||||
parser Parser
|
||||
v linkedNodes
|
||||
}
|
||||
|
||||
func newLazyArray(p *Parser) Node {
|
||||
s := new(parseArrayStack)
|
||||
s.parser = *p
|
||||
return Node{
|
||||
t: _V_ARRAY_LAZY,
|
||||
p: unsafe.Pointer(s),
|
||||
}
|
||||
}
|
||||
|
||||
func newLazyObject(p *Parser) Node {
|
||||
s := new(parseObjectStack)
|
||||
s.parser = *p
|
||||
return Node{
|
||||
t: _V_OBJECT_LAZY,
|
||||
p: unsafe.Pointer(s),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Node) getParserAndArrayStack() (*Parser, *parseArrayStack) {
|
||||
stack := (*parseArrayStack)(self.p)
|
||||
return &stack.parser, stack
|
||||
}
|
||||
|
||||
func (self *Node) getParserAndObjectStack() (*Parser, *parseObjectStack) {
|
||||
stack := (*parseObjectStack)(self.p)
|
||||
return &stack.parser, stack
|
||||
}
|
||||
|
||||
557
vendor/github.com/bytedance/sonic/ast/decode.go
generated
vendored
Normal file
557
vendor/github.com/bytedance/sonic/ast/decode.go
generated
vendored
Normal file
@@ -0,0 +1,557 @@
|
||||
/*
|
||||
* Copyright 2022 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bytedance/sonic/internal/native/types"
|
||||
"github.com/bytedance/sonic/internal/rt"
|
||||
"github.com/bytedance/sonic/internal/utils"
|
||||
"github.com/bytedance/sonic/unquote"
|
||||
)
|
||||
|
||||
|
||||
var bytesNull = []byte("null")
|
||||
|
||||
const (
|
||||
strNull = "null"
|
||||
bytesTrue = "true"
|
||||
bytesFalse = "false"
|
||||
bytesObject = "{}"
|
||||
bytesArray = "[]"
|
||||
)
|
||||
|
||||
//go:nocheckptr
|
||||
func skipBlank(src string, pos int) int {
|
||||
se := uintptr(rt.IndexChar(src, len(src)))
|
||||
sp := uintptr(rt.IndexChar(src, pos))
|
||||
|
||||
for sp < se {
|
||||
if !utils.IsSpace(*(*byte)(unsafe.Pointer(sp))) {
|
||||
break
|
||||
}
|
||||
sp += 1
|
||||
}
|
||||
if sp >= se {
|
||||
return -int(types.ERR_EOF)
|
||||
}
|
||||
runtime.KeepAlive(src)
|
||||
return int(sp - uintptr(rt.IndexChar(src, 0)))
|
||||
}
|
||||
|
||||
func decodeNull(src string, pos int) (ret int) {
|
||||
ret = pos + 4
|
||||
if ret > len(src) {
|
||||
return -int(types.ERR_EOF)
|
||||
}
|
||||
if src[pos:ret] == strNull {
|
||||
return ret
|
||||
} else {
|
||||
return -int(types.ERR_INVALID_CHAR)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeTrue(src string, pos int) (ret int) {
|
||||
ret = pos + 4
|
||||
if ret > len(src) {
|
||||
return -int(types.ERR_EOF)
|
||||
}
|
||||
if src[pos:ret] == bytesTrue {
|
||||
return ret
|
||||
} else {
|
||||
return -int(types.ERR_INVALID_CHAR)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func decodeFalse(src string, pos int) (ret int) {
|
||||
ret = pos + 5
|
||||
if ret > len(src) {
|
||||
return -int(types.ERR_EOF)
|
||||
}
|
||||
if src[pos:ret] == bytesFalse {
|
||||
return ret
|
||||
}
|
||||
return -int(types.ERR_INVALID_CHAR)
|
||||
}
|
||||
|
||||
//go:nocheckptr
|
||||
func decodeString(src string, pos int) (ret int, v string) {
|
||||
ret, ep := skipString(src, pos)
|
||||
if ep == -1 {
|
||||
(*rt.GoString)(unsafe.Pointer(&v)).Ptr = rt.IndexChar(src, pos+1)
|
||||
(*rt.GoString)(unsafe.Pointer(&v)).Len = ret - pos - 2
|
||||
return ret, v
|
||||
}
|
||||
|
||||
result, err := unquote.String(src[pos:ret])
|
||||
if err != 0 {
|
||||
return -int(types.ERR_INVALID_CHAR), ""
|
||||
}
|
||||
|
||||
runtime.KeepAlive(src)
|
||||
return ret, result
|
||||
}
|
||||
|
||||
func decodeBinary(src string, pos int) (ret int, v []byte) {
|
||||
var vv string
|
||||
ret, vv = decodeString(src, pos)
|
||||
if ret < 0 {
|
||||
return ret, nil
|
||||
}
|
||||
var err error
|
||||
v, err = base64.StdEncoding.DecodeString(vv)
|
||||
if err != nil {
|
||||
return -int(types.ERR_INVALID_CHAR), nil
|
||||
}
|
||||
return ret, v
|
||||
}
|
||||
|
||||
func isDigit(c byte) bool {
|
||||
return c >= '0' && c <= '9'
|
||||
}
|
||||
|
||||
//go:nocheckptr
|
||||
func decodeInt64(src string, pos int) (ret int, v int64, err error) {
|
||||
sp := uintptr(rt.IndexChar(src, pos))
|
||||
ss := uintptr(sp)
|
||||
se := uintptr(rt.IndexChar(src, len(src)))
|
||||
if uintptr(sp) >= se {
|
||||
return -int(types.ERR_EOF), 0, nil
|
||||
}
|
||||
|
||||
if c := *(*byte)(unsafe.Pointer(sp)); c == '-' {
|
||||
sp += 1
|
||||
}
|
||||
if sp == se {
|
||||
return -int(types.ERR_EOF), 0, nil
|
||||
}
|
||||
|
||||
for ; sp < se; sp += uintptr(1) {
|
||||
if !isDigit(*(*byte)(unsafe.Pointer(sp))) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if sp < se {
|
||||
if c := *(*byte)(unsafe.Pointer(sp)); c == '.' || c == 'e' || c == 'E' {
|
||||
return -int(types.ERR_INVALID_NUMBER_FMT), 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
var vv string
|
||||
ret = int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
|
||||
(*rt.GoString)(unsafe.Pointer(&vv)).Ptr = unsafe.Pointer(ss)
|
||||
(*rt.GoString)(unsafe.Pointer(&vv)).Len = ret - pos
|
||||
|
||||
v, err = strconv.ParseInt(vv, 10, 64)
|
||||
if err != nil {
|
||||
//NOTICE: allow overflow here
|
||||
if err.(*strconv.NumError).Err == strconv.ErrRange {
|
||||
return ret, 0, err
|
||||
}
|
||||
return -int(types.ERR_INVALID_CHAR), 0, err
|
||||
}
|
||||
|
||||
runtime.KeepAlive(src)
|
||||
return ret, v, nil
|
||||
}
|
||||
|
||||
func isNumberChars(c byte) bool {
|
||||
return (c >= '0' && c <= '9') || c == '+' || c == '-' || c == 'e' || c == 'E' || c == '.'
|
||||
}
|
||||
|
||||
//go:nocheckptr
|
||||
func decodeFloat64(src string, pos int) (ret int, v float64, err error) {
|
||||
sp := uintptr(rt.IndexChar(src, pos))
|
||||
ss := uintptr(sp)
|
||||
se := uintptr(rt.IndexChar(src, len(src)))
|
||||
if uintptr(sp) >= se {
|
||||
return -int(types.ERR_EOF), 0, nil
|
||||
}
|
||||
|
||||
if c := *(*byte)(unsafe.Pointer(sp)); c == '-' {
|
||||
sp += 1
|
||||
}
|
||||
if sp == se {
|
||||
return -int(types.ERR_EOF), 0, nil
|
||||
}
|
||||
|
||||
for ; sp < se; sp += uintptr(1) {
|
||||
if !isNumberChars(*(*byte)(unsafe.Pointer(sp))) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var vv string
|
||||
ret = int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
|
||||
(*rt.GoString)(unsafe.Pointer(&vv)).Ptr = unsafe.Pointer(ss)
|
||||
(*rt.GoString)(unsafe.Pointer(&vv)).Len = ret - pos
|
||||
|
||||
v, err = strconv.ParseFloat(vv, 64)
|
||||
if err != nil {
|
||||
//NOTICE: allow overflow here
|
||||
if err.(*strconv.NumError).Err == strconv.ErrRange {
|
||||
return ret, 0, err
|
||||
}
|
||||
return -int(types.ERR_INVALID_CHAR), 0, err
|
||||
}
|
||||
|
||||
runtime.KeepAlive(src)
|
||||
return ret, v, nil
|
||||
}
|
||||
|
||||
func decodeValue(src string, pos int, skipnum bool) (ret int, v types.JsonState) {
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, types.JsonState{Vt: types.ValueType(pos)}
|
||||
}
|
||||
switch c := src[pos]; c {
|
||||
case 'n':
|
||||
ret = decodeNull(src, pos)
|
||||
if ret < 0 {
|
||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
||||
}
|
||||
return ret, types.JsonState{Vt: types.V_NULL}
|
||||
case '"':
|
||||
var ep int
|
||||
ret, ep = skipString(src, pos)
|
||||
if ret < 0 {
|
||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
||||
}
|
||||
return ret, types.JsonState{Vt: types.V_STRING, Iv: int64(pos + 1), Ep: ep}
|
||||
case '{':
|
||||
return pos + 1, types.JsonState{Vt: types.V_OBJECT}
|
||||
case '[':
|
||||
return pos + 1, types.JsonState{Vt: types.V_ARRAY}
|
||||
case 't':
|
||||
ret = decodeTrue(src, pos)
|
||||
if ret < 0 {
|
||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
||||
}
|
||||
return ret, types.JsonState{Vt: types.V_TRUE}
|
||||
case 'f':
|
||||
ret = decodeFalse(src, pos)
|
||||
if ret < 0 {
|
||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
||||
}
|
||||
return ret, types.JsonState{Vt: types.V_FALSE}
|
||||
case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
if skipnum {
|
||||
ret = skipNumber(src, pos)
|
||||
if ret >= 0 {
|
||||
return ret, types.JsonState{Vt: types.V_DOUBLE, Iv: 0, Ep: pos}
|
||||
} else {
|
||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
||||
}
|
||||
} else {
|
||||
var iv int64
|
||||
ret, iv, _ = decodeInt64(src, pos)
|
||||
if ret >= 0 {
|
||||
return ret, types.JsonState{Vt: types.V_INTEGER, Iv: iv, Ep: pos}
|
||||
} else if ret != -int(types.ERR_INVALID_NUMBER_FMT) {
|
||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
||||
}
|
||||
var fv float64
|
||||
ret, fv, _ = decodeFloat64(src, pos)
|
||||
if ret >= 0 {
|
||||
return ret, types.JsonState{Vt: types.V_DOUBLE, Dv: fv, Ep: pos}
|
||||
} else {
|
||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return -int(types.ERR_INVALID_CHAR), types.JsonState{Vt:-types.ValueType(types.ERR_INVALID_CHAR)}
|
||||
}
|
||||
}
|
||||
|
||||
//go:nocheckptr
|
||||
func skipNumber(src string, pos int) (ret int) {
|
||||
return utils.SkipNumber(src, pos)
|
||||
}
|
||||
|
||||
//go:nocheckptr
|
||||
func skipString(src string, pos int) (ret int, ep int) {
|
||||
if pos+1 >= len(src) {
|
||||
return -int(types.ERR_EOF), -1
|
||||
}
|
||||
|
||||
sp := uintptr(rt.IndexChar(src, pos))
|
||||
se := uintptr(rt.IndexChar(src, len(src)))
|
||||
|
||||
// not start with quote
|
||||
if *(*byte)(unsafe.Pointer(sp)) != '"' {
|
||||
return -int(types.ERR_INVALID_CHAR), -1
|
||||
}
|
||||
sp += 1
|
||||
|
||||
ep = -1
|
||||
for sp < se {
|
||||
c := *(*byte)(unsafe.Pointer(sp))
|
||||
if c == '\\' {
|
||||
if ep == -1 {
|
||||
ep = int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
|
||||
}
|
||||
sp += 2
|
||||
continue
|
||||
}
|
||||
sp += 1
|
||||
if c == '"' {
|
||||
return int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr)), ep
|
||||
}
|
||||
}
|
||||
|
||||
runtime.KeepAlive(src)
|
||||
// not found the closed quote until EOF
|
||||
return -int(types.ERR_EOF), -1
|
||||
}
|
||||
|
||||
//go:nocheckptr
|
||||
func skipPair(src string, pos int, lchar byte, rchar byte) (ret int) {
|
||||
if pos+1 >= len(src) {
|
||||
return -int(types.ERR_EOF)
|
||||
}
|
||||
|
||||
sp := uintptr(rt.IndexChar(src, pos))
|
||||
se := uintptr(rt.IndexChar(src, len(src)))
|
||||
|
||||
if *(*byte)(unsafe.Pointer(sp)) != lchar {
|
||||
return -int(types.ERR_INVALID_CHAR)
|
||||
}
|
||||
|
||||
sp += 1
|
||||
nbrace := 1
|
||||
inquote := false
|
||||
|
||||
for sp < se {
|
||||
c := *(*byte)(unsafe.Pointer(sp))
|
||||
if c == '\\' {
|
||||
sp += 2
|
||||
continue
|
||||
} else if c == '"' {
|
||||
inquote = !inquote
|
||||
} else if c == lchar {
|
||||
if !inquote {
|
||||
nbrace += 1
|
||||
}
|
||||
} else if c == rchar {
|
||||
if !inquote {
|
||||
nbrace -= 1
|
||||
if nbrace == 0 {
|
||||
sp += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
sp += 1
|
||||
}
|
||||
|
||||
if nbrace != 0 {
|
||||
return -int(types.ERR_INVALID_CHAR)
|
||||
}
|
||||
|
||||
runtime.KeepAlive(src)
|
||||
return int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
|
||||
}
|
||||
|
||||
func skipValueFast(src string, pos int) (ret int, start int) {
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
switch c := src[pos]; c {
|
||||
case 'n':
|
||||
ret = decodeNull(src, pos)
|
||||
case '"':
|
||||
ret, _ = skipString(src, pos)
|
||||
case '{':
|
||||
ret = skipPair(src, pos, '{', '}')
|
||||
case '[':
|
||||
ret = skipPair(src, pos, '[', ']')
|
||||
case 't':
|
||||
ret = decodeTrue(src, pos)
|
||||
case 'f':
|
||||
ret = decodeFalse(src, pos)
|
||||
case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
ret = skipNumber(src, pos)
|
||||
default:
|
||||
ret = -int(types.ERR_INVALID_CHAR)
|
||||
}
|
||||
return ret, pos
|
||||
}
|
||||
|
||||
func skipValue(src string, pos int) (ret int, start int) {
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
switch c := src[pos]; c {
|
||||
case 'n':
|
||||
ret = decodeNull(src, pos)
|
||||
case '"':
|
||||
ret, _ = skipString(src, pos)
|
||||
case '{':
|
||||
ret, _ = skipObject(src, pos)
|
||||
case '[':
|
||||
ret, _ = skipArray(src, pos)
|
||||
case 't':
|
||||
ret = decodeTrue(src, pos)
|
||||
case 'f':
|
||||
ret = decodeFalse(src, pos)
|
||||
case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
ret = skipNumber(src, pos)
|
||||
default:
|
||||
ret = -int(types.ERR_INVALID_CHAR)
|
||||
}
|
||||
return ret, pos
|
||||
}
|
||||
|
||||
func skipObject(src string, pos int) (ret int, start int) {
|
||||
start = skipBlank(src, pos)
|
||||
if start < 0 {
|
||||
return start, -1
|
||||
}
|
||||
|
||||
if src[start] != '{' {
|
||||
return -int(types.ERR_INVALID_CHAR), -1
|
||||
}
|
||||
|
||||
pos = start + 1
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
if src[pos] == '}' {
|
||||
return pos + 1, start
|
||||
}
|
||||
|
||||
for {
|
||||
pos, _ = skipString(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
if src[pos] != ':' {
|
||||
return -int(types.ERR_INVALID_CHAR), -1
|
||||
}
|
||||
|
||||
pos++
|
||||
pos, _ = skipValue(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
if src[pos] == '}' {
|
||||
return pos + 1, start
|
||||
}
|
||||
if src[pos] != ',' {
|
||||
return -int(types.ERR_INVALID_CHAR), -1
|
||||
}
|
||||
|
||||
pos++
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func skipArray(src string, pos int) (ret int, start int) {
|
||||
start = skipBlank(src, pos)
|
||||
if start < 0 {
|
||||
return start, -1
|
||||
}
|
||||
|
||||
if src[start] != '[' {
|
||||
return -int(types.ERR_INVALID_CHAR), -1
|
||||
}
|
||||
|
||||
pos = start + 1
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
if src[pos] == ']' {
|
||||
return pos + 1, start
|
||||
}
|
||||
|
||||
for {
|
||||
pos, _ = skipValue(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
|
||||
pos = skipBlank(src, pos)
|
||||
if pos < 0 {
|
||||
return pos, -1
|
||||
}
|
||||
if src[pos] == ']' {
|
||||
return pos + 1, start
|
||||
}
|
||||
if src[pos] != ',' {
|
||||
return -int(types.ERR_INVALID_CHAR), -1
|
||||
}
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeString decodes a JSON string from pos and return golang string.
|
||||
// - needEsc indicates if to unescaped escaping chars
|
||||
// - hasEsc tells if the returned string has escaping chars
|
||||
// - validStr enables validating UTF8 charset
|
||||
//
|
||||
func _DecodeString(src string, pos int, needEsc bool, validStr bool) (v string, ret int, hasEsc bool) {
|
||||
p := NewParserObj(src)
|
||||
p.p = pos
|
||||
switch val := p.decodeValue(); val.Vt {
|
||||
case types.V_STRING:
|
||||
str := p.s[val.Iv : p.p-1]
|
||||
if validStr && !validate_utf8(str) {
|
||||
return "", -int(types.ERR_INVALID_UTF8), false
|
||||
}
|
||||
/* fast path: no escape sequence */
|
||||
if val.Ep == -1 {
|
||||
return str, p.p, false
|
||||
} else if !needEsc {
|
||||
return str, p.p, true
|
||||
}
|
||||
/* unquote the string */
|
||||
out, err := unquote.String(str)
|
||||
/* check for errors */
|
||||
if err != 0 {
|
||||
return "", -int(err), true
|
||||
} else {
|
||||
return out, p.p, true
|
||||
}
|
||||
default:
|
||||
return "", -int(_ERR_UNSUPPORT_TYPE), false
|
||||
}
|
||||
}
|
||||
280
vendor/github.com/bytedance/sonic/ast/encode.go
generated
vendored
Normal file
280
vendor/github.com/bytedance/sonic/ast/encode.go
generated
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/bytedance/gopkg/lang/dirtmake"
|
||||
"github.com/bytedance/sonic/internal/rt"
|
||||
"github.com/bytedance/sonic/option"
|
||||
)
|
||||
|
||||
func quoteString(e *[]byte, s string) {
|
||||
*e = append(*e, '"')
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if rt.SafeSet[b] {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
*e = append(*e, s[start:i]...)
|
||||
}
|
||||
*e = append(*e, '\\')
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
*e = append(*e, b)
|
||||
case '\n':
|
||||
*e = append(*e, 'n')
|
||||
case '\r':
|
||||
*e = append(*e, 'r')
|
||||
case '\t':
|
||||
*e = append(*e, 't')
|
||||
default:
|
||||
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||
// If escapeHTML is set, it also escapes <, >, and &
|
||||
// because they can lead to security holes when
|
||||
// user-controlled strings are rendered into JSON
|
||||
// and served to some browsers.
|
||||
*e = append(*e, `u00`...)
|
||||
*e = append(*e, rt.Hex[b>>4])
|
||||
*e = append(*e, rt.Hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRuneInString(s[i:])
|
||||
// if c == utf8.RuneError && size == 1 {
|
||||
// if start < i {
|
||||
// e.Write(s[start:i])
|
||||
// }
|
||||
// e.WriteString(`\ufffd`)
|
||||
// i += size
|
||||
// start = i
|
||||
// continue
|
||||
// }
|
||||
if c == '\u2028' || c == '\u2029' {
|
||||
if start < i {
|
||||
*e = append(*e, s[start:i]...)
|
||||
}
|
||||
*e = append(*e, `\u202`...)
|
||||
*e = append(*e, rt.Hex[c&0xF])
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
*e = append(*e, s[start:]...)
|
||||
}
|
||||
*e = append(*e, '"')
|
||||
}
|
||||
|
||||
var bytesPool = sync.Pool{}
|
||||
|
||||
func (self *Node) MarshalJSON() ([]byte, error) {
|
||||
if self == nil {
|
||||
return bytesNull, nil
|
||||
}
|
||||
|
||||
// fast path for raw node
|
||||
if self.isRaw() {
|
||||
return rt.Str2Mem(self.toString()), nil
|
||||
}
|
||||
|
||||
buf := newBuffer()
|
||||
err := self.encode(buf)
|
||||
if err != nil {
|
||||
freeBuffer(buf)
|
||||
return nil, err
|
||||
}
|
||||
var ret []byte
|
||||
if !rt.CanSizeResue(cap(*buf)) {
|
||||
ret = *buf
|
||||
} else {
|
||||
ret = dirtmake.Bytes(len(*buf), len(*buf))
|
||||
copy(ret, *buf)
|
||||
freeBuffer(buf)
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func newBuffer() *[]byte {
|
||||
if ret := bytesPool.Get(); ret != nil {
|
||||
return ret.(*[]byte)
|
||||
} else {
|
||||
buf := make([]byte, 0, option.DefaultAstBufferSize)
|
||||
return &buf
|
||||
}
|
||||
}
|
||||
|
||||
func freeBuffer(buf *[]byte) {
|
||||
if !rt.CanSizeResue(cap(*buf)) {
|
||||
return
|
||||
}
|
||||
*buf = (*buf)[:0]
|
||||
bytesPool.Put(buf)
|
||||
}
|
||||
|
||||
func (self *Node) encode(buf *[]byte) error {
|
||||
if self.isRaw() {
|
||||
return self.encodeRaw(buf)
|
||||
}
|
||||
switch int(self.itype()) {
|
||||
case V_NONE : return ErrNotExist
|
||||
case V_ERROR : return self.Check()
|
||||
case V_NULL : return self.encodeNull(buf)
|
||||
case V_TRUE : return self.encodeTrue(buf)
|
||||
case V_FALSE : return self.encodeFalse(buf)
|
||||
case V_ARRAY : return self.encodeArray(buf)
|
||||
case V_OBJECT: return self.encodeObject(buf)
|
||||
case V_STRING: return self.encodeString(buf)
|
||||
case V_NUMBER: return self.encodeNumber(buf)
|
||||
case V_ANY : return self.encodeInterface(buf)
|
||||
default : return ErrUnsupportType
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Node) encodeRaw(buf *[]byte) error {
|
||||
lock := self.rlock()
|
||||
if !self.isRaw() {
|
||||
self.runlock()
|
||||
return self.encode(buf)
|
||||
}
|
||||
raw := self.toString()
|
||||
if lock {
|
||||
self.runlock()
|
||||
}
|
||||
*buf = append(*buf, raw...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Node) encodeNull(buf *[]byte) error {
|
||||
*buf = append(*buf, strNull...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Node) encodeTrue(buf *[]byte) error {
|
||||
*buf = append(*buf, bytesTrue...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Node) encodeFalse(buf *[]byte) error {
|
||||
*buf = append(*buf, bytesFalse...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Node) encodeNumber(buf *[]byte) error {
|
||||
str := self.toString()
|
||||
*buf = append(*buf, str...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Node) encodeString(buf *[]byte) error {
|
||||
if self.l == 0 {
|
||||
*buf = append(*buf, '"', '"')
|
||||
return nil
|
||||
}
|
||||
|
||||
quote(buf, self.toString())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Node) encodeArray(buf *[]byte) error {
|
||||
if self.isLazy() {
|
||||
if err := self.skipAllIndex(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
nb := self.len()
|
||||
if nb == 0 {
|
||||
*buf = append(*buf, bytesArray...)
|
||||
return nil
|
||||
}
|
||||
|
||||
*buf = append(*buf, '[')
|
||||
|
||||
var started bool
|
||||
for i := 0; i < nb; i++ {
|
||||
n := self.nodeAt(i)
|
||||
if !n.Exists() {
|
||||
continue
|
||||
}
|
||||
if started {
|
||||
*buf = append(*buf, ',')
|
||||
}
|
||||
started = true
|
||||
if err := n.encode(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*buf = append(*buf, ']')
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Pair) encode(buf *[]byte) error {
|
||||
if len(*buf) == 0 {
|
||||
*buf = append(*buf, '"', '"', ':')
|
||||
return self.Value.encode(buf)
|
||||
}
|
||||
|
||||
quote(buf, self.Key)
|
||||
*buf = append(*buf, ':')
|
||||
|
||||
return self.Value.encode(buf)
|
||||
}
|
||||
|
||||
func (self *Node) encodeObject(buf *[]byte) error {
|
||||
if self.isLazy() {
|
||||
if err := self.skipAllKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
nb := self.len()
|
||||
if nb == 0 {
|
||||
*buf = append(*buf, bytesObject...)
|
||||
return nil
|
||||
}
|
||||
|
||||
*buf = append(*buf, '{')
|
||||
|
||||
var started bool
|
||||
for i := 0; i < nb; i++ {
|
||||
n := self.pairAt(i)
|
||||
if n == nil || !n.Value.Exists() {
|
||||
continue
|
||||
}
|
||||
if started {
|
||||
*buf = append(*buf, ',')
|
||||
}
|
||||
started = true
|
||||
if err := n.encode(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*buf = append(*buf, '}')
|
||||
return nil
|
||||
}
|
||||
134
vendor/github.com/bytedance/sonic/ast/error.go
generated
vendored
Normal file
134
vendor/github.com/bytedance/sonic/ast/error.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`strings`
|
||||
`unsafe`
|
||||
|
||||
`github.com/bytedance/sonic/internal/native/types`
|
||||
)
|
||||
|
||||
|
||||
func newError(err types.ParsingError, msg string) *Node {
|
||||
return &Node{
|
||||
t: V_ERROR,
|
||||
l: uint(err),
|
||||
p: unsafe.Pointer(&msg),
|
||||
}
|
||||
}
|
||||
|
||||
func newErrorPair(err SyntaxError) *Pair {
|
||||
return &Pair{0, "", *newSyntaxError(err)}
|
||||
}
|
||||
|
||||
// Error returns error message if the node is invalid
|
||||
func (self Node) Error() string {
|
||||
if self.t != V_ERROR {
|
||||
return ""
|
||||
} else {
|
||||
return *(*string)(self.p)
|
||||
}
|
||||
}
|
||||
|
||||
func newSyntaxError(err SyntaxError) *Node {
|
||||
msg := err.Description()
|
||||
return &Node{
|
||||
t: V_ERROR,
|
||||
l: uint(err.Code),
|
||||
p: unsafe.Pointer(&msg),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Parser) syntaxError(err types.ParsingError) SyntaxError {
|
||||
return SyntaxError{
|
||||
Pos : self.p,
|
||||
Src : self.s,
|
||||
Code: err,
|
||||
}
|
||||
}
|
||||
|
||||
func unwrapError(err error) *Node {
|
||||
if se, ok := err.(*Node); ok {
|
||||
return se
|
||||
}else if sse, ok := err.(Node); ok {
|
||||
return &sse
|
||||
} else {
|
||||
msg := err.Error()
|
||||
return &Node{
|
||||
t: V_ERROR,
|
||||
p: unsafe.Pointer(&msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SyntaxError struct {
|
||||
Pos int
|
||||
Src string
|
||||
Code types.ParsingError
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (self SyntaxError) Error() string {
|
||||
return fmt.Sprintf("%q", self.Description())
|
||||
}
|
||||
|
||||
func (self SyntaxError) Description() string {
|
||||
return "Syntax error " + self.description()
|
||||
}
|
||||
|
||||
func (self SyntaxError) description() string {
|
||||
i := 16
|
||||
p := self.Pos - i
|
||||
q := self.Pos + i
|
||||
|
||||
/* check for empty source */
|
||||
if self.Src == "" {
|
||||
return fmt.Sprintf("no sources available, the input json is empty: %#v", self)
|
||||
}
|
||||
|
||||
/* prevent slicing before the beginning */
|
||||
if p < 0 {
|
||||
p, q, i = 0, q - p, i + p
|
||||
}
|
||||
|
||||
/* prevent slicing beyond the end */
|
||||
if n := len(self.Src); q > n {
|
||||
n = q - n
|
||||
q = len(self.Src)
|
||||
|
||||
/* move the left bound if possible */
|
||||
if p > n {
|
||||
i += n
|
||||
p -= n
|
||||
}
|
||||
}
|
||||
|
||||
/* left and right length */
|
||||
x := clamp_zero(i)
|
||||
y := clamp_zero(q - p - i - 1)
|
||||
|
||||
/* compose the error description */
|
||||
return fmt.Sprintf(
|
||||
"at index %d: %s\n\n\t%s\n\t%s^%s\n",
|
||||
self.Pos,
|
||||
self.Message(),
|
||||
self.Src[p:q],
|
||||
strings.Repeat(".", x),
|
||||
strings.Repeat(".", y),
|
||||
)
|
||||
}
|
||||
|
||||
func (self SyntaxError) Message() string {
|
||||
if self.Msg == "" {
|
||||
return self.Code.Message()
|
||||
}
|
||||
return self.Msg
|
||||
}
|
||||
|
||||
func clamp_zero(v int) int {
|
||||
if v < 0 {
|
||||
return 0
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
216
vendor/github.com/bytedance/sonic/ast/iterator.go
generated
vendored
Normal file
216
vendor/github.com/bytedance/sonic/ast/iterator.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bytedance/sonic/internal/caching"
|
||||
"github.com/bytedance/sonic/internal/native/types"
|
||||
)
|
||||
|
||||
type Pair struct {
|
||||
hash uint64
|
||||
Key string
|
||||
Value Node
|
||||
}
|
||||
|
||||
func NewPair(key string, val Node) Pair {
|
||||
return Pair{
|
||||
hash: caching.StrHash(key),
|
||||
Key: key,
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
|
||||
// Values returns iterator for array's children traversal
|
||||
func (self *Node) Values() (ListIterator, error) {
|
||||
if err := self.should(types.V_ARRAY); err != nil {
|
||||
return ListIterator{}, err
|
||||
}
|
||||
return self.values(), nil
|
||||
}
|
||||
|
||||
func (self *Node) values() ListIterator {
|
||||
return ListIterator{Iterator{p: self}}
|
||||
}
|
||||
|
||||
// Properties returns iterator for object's children traversal
|
||||
func (self *Node) Properties() (ObjectIterator, error) {
|
||||
if err := self.should(types.V_OBJECT); err != nil {
|
||||
return ObjectIterator{}, err
|
||||
}
|
||||
return self.properties(), nil
|
||||
}
|
||||
|
||||
func (self *Node) properties() ObjectIterator {
|
||||
return ObjectIterator{Iterator{p: self}}
|
||||
}
|
||||
|
||||
type Iterator struct {
|
||||
i int
|
||||
p *Node
|
||||
}
|
||||
|
||||
func (self *Iterator) Pos() int {
|
||||
return self.i
|
||||
}
|
||||
|
||||
func (self *Iterator) Len() int {
|
||||
return self.p.len()
|
||||
}
|
||||
|
||||
// HasNext reports if it is the end of iteration or has error.
|
||||
func (self *Iterator) HasNext() bool {
|
||||
if !self.p.isLazy() {
|
||||
return self.p.Valid() && self.i < self.p.len()
|
||||
} else if self.p.t == _V_ARRAY_LAZY {
|
||||
return self.p.skipNextNode().Valid()
|
||||
} else if self.p.t == _V_OBJECT_LAZY {
|
||||
pair := self.p.skipNextPair()
|
||||
if pair == nil {
|
||||
return false
|
||||
}
|
||||
return pair.Value.Valid()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ListIterator is specialized iterator for V_ARRAY
|
||||
type ListIterator struct {
|
||||
Iterator
|
||||
}
|
||||
|
||||
// ObjectIterator is specialized iterator for V_ARRAY
|
||||
type ObjectIterator struct {
|
||||
Iterator
|
||||
}
|
||||
|
||||
func (self *ListIterator) next() *Node {
|
||||
next_start:
|
||||
if !self.HasNext() {
|
||||
return nil
|
||||
} else {
|
||||
n := self.p.nodeAt(self.i)
|
||||
self.i++
|
||||
if !n.Exists() {
|
||||
goto next_start
|
||||
}
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
// Next scans through children of underlying V_ARRAY,
|
||||
// copies each child to v, and returns .HasNext().
|
||||
func (self *ListIterator) Next(v *Node) bool {
|
||||
n := self.next()
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
*v = *n
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *ObjectIterator) next() *Pair {
|
||||
next_start:
|
||||
if !self.HasNext() {
|
||||
return nil
|
||||
} else {
|
||||
n := self.p.pairAt(self.i)
|
||||
self.i++
|
||||
if n == nil || !n.Value.Exists() {
|
||||
goto next_start
|
||||
}
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
// Next scans through children of underlying V_OBJECT,
|
||||
// copies each child to v, and returns .HasNext().
|
||||
func (self *ObjectIterator) Next(p *Pair) bool {
|
||||
n := self.next()
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
*p = *n
|
||||
return true
|
||||
}
|
||||
|
||||
// Sequence represents scanning path of single-layer nodes.
|
||||
// Index indicates the value's order in both V_ARRAY and V_OBJECT json.
|
||||
// Key is the value's key (for V_OBJECT json only, otherwise it will be nil).
|
||||
type Sequence struct {
|
||||
Index int
|
||||
Key *string
|
||||
// Level int
|
||||
}
|
||||
|
||||
// String is string representation of one Sequence
|
||||
func (s Sequence) String() string {
|
||||
k := ""
|
||||
if s.Key != nil {
|
||||
k = *s.Key
|
||||
}
|
||||
return fmt.Sprintf("Sequence(%d, %q)", s.Index, k)
|
||||
}
|
||||
|
||||
type Scanner func(path Sequence, node *Node) bool
|
||||
|
||||
// ForEach scans one V_OBJECT node's children from JSON head to tail,
|
||||
// and pass the Sequence and Node of corresponding JSON value.
|
||||
//
|
||||
// Especially, if the node is not V_ARRAY or V_OBJECT,
|
||||
// the node itself will be returned and Sequence.Index == -1.
|
||||
//
|
||||
// NOTICE: An unset node WON'T trigger sc, but its index still counts into Path.Index
|
||||
func (self *Node) ForEach(sc Scanner) error {
|
||||
if err := self.checkRaw(); err != nil {
|
||||
return err
|
||||
}
|
||||
switch self.itype() {
|
||||
case types.V_ARRAY:
|
||||
iter, err := self.Values()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := iter.next()
|
||||
for v != nil {
|
||||
if !sc(Sequence{iter.i-1, nil}, v) {
|
||||
return nil
|
||||
}
|
||||
v = iter.next()
|
||||
}
|
||||
case types.V_OBJECT:
|
||||
iter, err := self.Properties()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := iter.next()
|
||||
for v != nil {
|
||||
if !sc(Sequence{iter.i-1, &v.Key}, &v.Value) {
|
||||
return nil
|
||||
}
|
||||
v = iter.next()
|
||||
}
|
||||
default:
|
||||
if self.Check() != nil {
|
||||
return self
|
||||
}
|
||||
sc(Sequence{-1, nil}, self)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1880
vendor/github.com/bytedance/sonic/ast/node.go
generated
vendored
Normal file
1880
vendor/github.com/bytedance/sonic/ast/node.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
768
vendor/github.com/bytedance/sonic/ast/parser.go
generated
vendored
Normal file
768
vendor/github.com/bytedance/sonic/ast/parser.go
generated
vendored
Normal file
@@ -0,0 +1,768 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/bytedance/sonic/internal/native/types"
|
||||
"github.com/bytedance/sonic/internal/rt"
|
||||
"github.com/bytedance/sonic/internal/utils"
|
||||
"github.com/bytedance/sonic/unquote"
|
||||
)
|
||||
|
||||
const (
|
||||
_DEFAULT_NODE_CAP int = 16
|
||||
_APPEND_GROW_SHIFT = 1
|
||||
)
|
||||
|
||||
const (
|
||||
_ERR_NOT_FOUND types.ParsingError = 33
|
||||
_ERR_UNSUPPORT_TYPE types.ParsingError = 34
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotExist means both key and value doesn't exist
|
||||
ErrNotExist error = newError(_ERR_NOT_FOUND, "value not exists")
|
||||
|
||||
// ErrUnsupportType means API on the node is unsupported
|
||||
ErrUnsupportType error = newError(_ERR_UNSUPPORT_TYPE, "unsupported type")
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
p int
|
||||
s string
|
||||
noLazy bool
|
||||
loadOnce bool
|
||||
skipValue bool
|
||||
dbuf *byte
|
||||
}
|
||||
|
||||
/** Parser Private Methods **/
|
||||
|
||||
func (self *Parser) delim() types.ParsingError {
|
||||
n := len(self.s)
|
||||
p := self.lspace(self.p)
|
||||
|
||||
/* check for EOF */
|
||||
if p >= n {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the delimiter */
|
||||
if self.s[p] != ':' {
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
|
||||
/* update the read pointer */
|
||||
self.p = p + 1
|
||||
return 0
|
||||
}
|
||||
|
||||
func (self *Parser) object() types.ParsingError {
|
||||
n := len(self.s)
|
||||
p := self.lspace(self.p)
|
||||
|
||||
/* check for EOF */
|
||||
if p >= n {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the delimiter */
|
||||
if self.s[p] != '{' {
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
|
||||
/* update the read pointer */
|
||||
self.p = p + 1
|
||||
return 0
|
||||
}
|
||||
|
||||
func (self *Parser) array() types.ParsingError {
|
||||
n := len(self.s)
|
||||
p := self.lspace(self.p)
|
||||
|
||||
/* check for EOF */
|
||||
if p >= n {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the delimiter */
|
||||
if self.s[p] != '[' {
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
|
||||
/* update the read pointer */
|
||||
self.p = p + 1
|
||||
return 0
|
||||
}
|
||||
|
||||
func (self *Parser) lspace(sp int) int {
|
||||
ns := len(self.s)
|
||||
for ; sp<ns && utils.IsSpace(self.s[sp]); sp+=1 {}
|
||||
|
||||
return sp
|
||||
}
|
||||
|
||||
func (self *Parser) backward() {
|
||||
for ; self.p >= 0 && utils.IsSpace(self.s[self.p]); self.p-=1 {}
|
||||
}
|
||||
|
||||
func (self *Parser) decodeArray(ret *linkedNodes) (Node, types.ParsingError) {
|
||||
sp := self.p
|
||||
ns := len(self.s)
|
||||
|
||||
/* check for EOF */
|
||||
if self.p = self.lspace(sp); self.p >= ns {
|
||||
return Node{}, types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for empty array */
|
||||
if self.s[self.p] == ']' {
|
||||
self.p++
|
||||
return Node{t: types.V_ARRAY}, 0
|
||||
}
|
||||
|
||||
/* allocate array space and parse every element */
|
||||
for {
|
||||
var val Node
|
||||
var err types.ParsingError
|
||||
|
||||
if self.skipValue {
|
||||
/* skip the value */
|
||||
var start int
|
||||
if start, err = self.skipFast(); err != 0 {
|
||||
return Node{}, err
|
||||
}
|
||||
if self.p > ns {
|
||||
return Node{}, types.ERR_EOF
|
||||
}
|
||||
t := switchRawType(self.s[start])
|
||||
if t == _V_NONE {
|
||||
return Node{}, types.ERR_INVALID_CHAR
|
||||
}
|
||||
val = newRawNode(self.s[start:self.p], t, false)
|
||||
}else{
|
||||
/* decode the value */
|
||||
if val, err = self.Parse(); err != 0 {
|
||||
return Node{}, err
|
||||
}
|
||||
}
|
||||
|
||||
/* add the value to result */
|
||||
ret.Push(val)
|
||||
self.p = self.lspace(self.p)
|
||||
|
||||
/* check for EOF */
|
||||
if self.p >= ns {
|
||||
return Node{}, types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the next character */
|
||||
switch self.s[self.p] {
|
||||
case ',' : self.p++
|
||||
case ']' : self.p++; return newArray(ret), 0
|
||||
default:
|
||||
// if val.isLazy() {
|
||||
// return newLazyArray(self, ret), 0
|
||||
// }
|
||||
return Node{}, types.ERR_INVALID_CHAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Parser) decodeObject(ret *linkedPairs) (Node, types.ParsingError) {
|
||||
sp := self.p
|
||||
ns := len(self.s)
|
||||
|
||||
/* check for EOF */
|
||||
if self.p = self.lspace(sp); self.p >= ns {
|
||||
return Node{}, types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for empty object */
|
||||
if self.s[self.p] == '}' {
|
||||
self.p++
|
||||
return Node{t: types.V_OBJECT}, 0
|
||||
}
|
||||
|
||||
/* decode each pair */
|
||||
for {
|
||||
var val Node
|
||||
var njs types.JsonState
|
||||
var err types.ParsingError
|
||||
|
||||
/* decode the key */
|
||||
if njs = self.decodeValue(); njs.Vt != types.V_STRING {
|
||||
return Node{}, types.ERR_INVALID_CHAR
|
||||
}
|
||||
|
||||
/* extract the key */
|
||||
idx := self.p - 1
|
||||
key := self.s[njs.Iv:idx]
|
||||
|
||||
/* check for escape sequence */
|
||||
if njs.Ep != -1 {
|
||||
if key, err = unquote.String(key); err != 0 {
|
||||
return Node{}, err
|
||||
}
|
||||
}
|
||||
|
||||
/* expect a ':' delimiter */
|
||||
if err = self.delim(); err != 0 {
|
||||
return Node{}, err
|
||||
}
|
||||
|
||||
|
||||
if self.skipValue {
|
||||
/* skip the value */
|
||||
var start int
|
||||
if start, err = self.skipFast(); err != 0 {
|
||||
return Node{}, err
|
||||
}
|
||||
if self.p > ns {
|
||||
return Node{}, types.ERR_EOF
|
||||
}
|
||||
t := switchRawType(self.s[start])
|
||||
if t == _V_NONE {
|
||||
return Node{}, types.ERR_INVALID_CHAR
|
||||
}
|
||||
val = newRawNode(self.s[start:self.p], t, false)
|
||||
} else {
|
||||
/* decode the value */
|
||||
if val, err = self.Parse(); err != 0 {
|
||||
return Node{}, err
|
||||
}
|
||||
}
|
||||
|
||||
/* add the value to result */
|
||||
// FIXME: ret's address may change here, thus previous referred node in ret may be invalid !!
|
||||
ret.Push(NewPair(key, val))
|
||||
self.p = self.lspace(self.p)
|
||||
|
||||
/* check for EOF */
|
||||
if self.p >= ns {
|
||||
return Node{}, types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the next character */
|
||||
switch self.s[self.p] {
|
||||
case ',' : self.p++
|
||||
case '}' : self.p++; return newObject(ret), 0
|
||||
default:
|
||||
// if val.isLazy() {
|
||||
// return newLazyObject(self, ret), 0
|
||||
// }
|
||||
return Node{}, types.ERR_INVALID_CHAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Parser) decodeString(iv int64, ep int) (Node, types.ParsingError) {
|
||||
p := self.p - 1
|
||||
s := self.s[iv:p]
|
||||
|
||||
/* fast path: no escape sequence */
|
||||
if ep == -1 {
|
||||
return NewString(s), 0
|
||||
}
|
||||
|
||||
/* unquote the string */
|
||||
out, err := unquote.String(s)
|
||||
|
||||
/* check for errors */
|
||||
if err != 0 {
|
||||
return Node{}, err
|
||||
} else {
|
||||
return newBytes(rt.Str2Mem(out)), 0
|
||||
}
|
||||
}
|
||||
|
||||
/** Parser Interface **/
|
||||
|
||||
func (self *Parser) Pos() int {
|
||||
return self.p
|
||||
}
|
||||
|
||||
|
||||
// Parse returns a ast.Node representing the parser's JSON.
|
||||
// NOTICE: the specific parsing lazy dependens parser's option
|
||||
// It only parse first layer and first child for Object or Array be default
|
||||
func (self *Parser) Parse() (Node, types.ParsingError) {
|
||||
switch val := self.decodeValue(); val.Vt {
|
||||
case types.V_EOF : return Node{}, types.ERR_EOF
|
||||
case types.V_NULL : return nullNode, 0
|
||||
case types.V_TRUE : return trueNode, 0
|
||||
case types.V_FALSE : return falseNode, 0
|
||||
case types.V_STRING : return self.decodeString(val.Iv, val.Ep)
|
||||
case types.V_ARRAY:
|
||||
s := self.p - 1;
|
||||
if p := skipBlank(self.s, self.p); p >= self.p && self.s[p] == ']' {
|
||||
self.p = p + 1
|
||||
return Node{t: types.V_ARRAY}, 0
|
||||
}
|
||||
if self.noLazy {
|
||||
if self.loadOnce {
|
||||
self.noLazy = false
|
||||
}
|
||||
return self.decodeArray(new(linkedNodes))
|
||||
}
|
||||
// NOTICE: loadOnce always keep raw json for object or array
|
||||
if self.loadOnce {
|
||||
self.p = s
|
||||
s, e := self.skipFast()
|
||||
if e != 0 {
|
||||
return Node{}, e
|
||||
}
|
||||
return newRawNode(self.s[s:self.p], types.V_ARRAY, true), 0
|
||||
}
|
||||
return newLazyArray(self), 0
|
||||
case types.V_OBJECT:
|
||||
s := self.p - 1;
|
||||
if p := skipBlank(self.s, self.p); p >= self.p && self.s[p] == '}' {
|
||||
self.p = p + 1
|
||||
return Node{t: types.V_OBJECT}, 0
|
||||
}
|
||||
// NOTICE: loadOnce always keep raw json for object or array
|
||||
if self.noLazy {
|
||||
if self.loadOnce {
|
||||
self.noLazy = false
|
||||
}
|
||||
return self.decodeObject(new(linkedPairs))
|
||||
}
|
||||
if self.loadOnce {
|
||||
self.p = s
|
||||
s, e := self.skipFast()
|
||||
if e != 0 {
|
||||
return Node{}, e
|
||||
}
|
||||
return newRawNode(self.s[s:self.p], types.V_OBJECT, true), 0
|
||||
}
|
||||
return newLazyObject(self), 0
|
||||
case types.V_DOUBLE : return NewNumber(self.s[val.Ep:self.p]), 0
|
||||
case types.V_INTEGER : return NewNumber(self.s[val.Ep:self.p]), 0
|
||||
default : return Node{}, types.ParsingError(-val.Vt)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Parser) searchKey(match string) types.ParsingError {
|
||||
ns := len(self.s)
|
||||
if err := self.object(); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
/* check for EOF */
|
||||
if self.p = self.lspace(self.p); self.p >= ns {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for empty object */
|
||||
if self.s[self.p] == '}' {
|
||||
self.p++
|
||||
return _ERR_NOT_FOUND
|
||||
}
|
||||
|
||||
var njs types.JsonState
|
||||
var err types.ParsingError
|
||||
/* decode each pair */
|
||||
for {
|
||||
|
||||
/* decode the key */
|
||||
if njs = self.decodeValue(); njs.Vt != types.V_STRING {
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
|
||||
/* extract the key */
|
||||
idx := self.p - 1
|
||||
key := self.s[njs.Iv:idx]
|
||||
|
||||
/* check for escape sequence */
|
||||
if njs.Ep != -1 {
|
||||
if key, err = unquote.String(key); err != 0 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
/* expect a ':' delimiter */
|
||||
if err = self.delim(); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
/* skip value */
|
||||
if key != match {
|
||||
if _, err = self.skipFast(); err != 0 {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
|
||||
/* check for EOF */
|
||||
self.p = self.lspace(self.p)
|
||||
if self.p >= ns {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the next character */
|
||||
switch self.s[self.p] {
|
||||
case ',':
|
||||
self.p++
|
||||
case '}':
|
||||
self.p++
|
||||
return _ERR_NOT_FOUND
|
||||
default:
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Parser) searchIndex(idx int) types.ParsingError {
|
||||
ns := len(self.s)
|
||||
if err := self.array(); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
/* check for EOF */
|
||||
if self.p = self.lspace(self.p); self.p >= ns {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for empty array */
|
||||
if self.s[self.p] == ']' {
|
||||
self.p++
|
||||
return _ERR_NOT_FOUND
|
||||
}
|
||||
|
||||
var err types.ParsingError
|
||||
/* allocate array space and parse every element */
|
||||
for i := 0; i < idx; i++ {
|
||||
|
||||
/* decode the value */
|
||||
if _, err = self.skipFast(); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
/* check for EOF */
|
||||
self.p = self.lspace(self.p)
|
||||
if self.p >= ns {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the next character */
|
||||
switch self.s[self.p] {
|
||||
case ',':
|
||||
self.p++
|
||||
case ']':
|
||||
self.p++
|
||||
return _ERR_NOT_FOUND
|
||||
default:
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (self *Node) skipNextNode() *Node {
|
||||
if !self.isLazy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
parser, stack := self.getParserAndArrayStack()
|
||||
ret := &stack.v
|
||||
sp := parser.p
|
||||
ns := len(parser.s)
|
||||
|
||||
/* check for EOF */
|
||||
if parser.p = parser.lspace(sp); parser.p >= ns {
|
||||
return newSyntaxError(parser.syntaxError(types.ERR_EOF))
|
||||
}
|
||||
|
||||
/* check for empty array */
|
||||
if parser.s[parser.p] == ']' {
|
||||
parser.p++
|
||||
self.setArray(ret)
|
||||
return nil
|
||||
}
|
||||
|
||||
var val Node
|
||||
/* skip the value */
|
||||
if start, err := parser.skipFast(); err != 0 {
|
||||
return newSyntaxError(parser.syntaxError(err))
|
||||
} else {
|
||||
t := switchRawType(parser.s[start])
|
||||
if t == _V_NONE {
|
||||
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
|
||||
}
|
||||
val = newRawNode(parser.s[start:parser.p], t, false)
|
||||
}
|
||||
|
||||
/* add the value to result */
|
||||
ret.Push(val)
|
||||
self.l++
|
||||
parser.p = parser.lspace(parser.p)
|
||||
|
||||
/* check for EOF */
|
||||
if parser.p >= ns {
|
||||
return newSyntaxError(parser.syntaxError(types.ERR_EOF))
|
||||
}
|
||||
|
||||
/* check for the next character */
|
||||
switch parser.s[parser.p] {
|
||||
case ',':
|
||||
parser.p++
|
||||
return ret.At(ret.Len()-1)
|
||||
case ']':
|
||||
parser.p++
|
||||
self.setArray(ret)
|
||||
return ret.At(ret.Len()-1)
|
||||
default:
|
||||
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Node) skipNextPair() (*Pair) {
|
||||
if !self.isLazy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
parser, stack := self.getParserAndObjectStack()
|
||||
ret := &stack.v
|
||||
sp := parser.p
|
||||
ns := len(parser.s)
|
||||
|
||||
/* check for EOF */
|
||||
if parser.p = parser.lspace(sp); parser.p >= ns {
|
||||
return newErrorPair(parser.syntaxError(types.ERR_EOF))
|
||||
}
|
||||
|
||||
/* check for empty object */
|
||||
if parser.s[parser.p] == '}' {
|
||||
parser.p++
|
||||
self.setObject(ret)
|
||||
return nil
|
||||
}
|
||||
|
||||
/* decode one pair */
|
||||
var val Node
|
||||
var njs types.JsonState
|
||||
var err types.ParsingError
|
||||
|
||||
/* decode the key */
|
||||
if njs = parser.decodeValue(); njs.Vt != types.V_STRING {
|
||||
return newErrorPair(parser.syntaxError(types.ERR_INVALID_CHAR))
|
||||
}
|
||||
|
||||
/* extract the key */
|
||||
idx := parser.p - 1
|
||||
key := parser.s[njs.Iv:idx]
|
||||
|
||||
/* check for escape sequence */
|
||||
if njs.Ep != -1 {
|
||||
if key, err = unquote.String(key); err != 0 {
|
||||
return newErrorPair(parser.syntaxError(err))
|
||||
}
|
||||
}
|
||||
|
||||
/* expect a ':' delimiter */
|
||||
if err = parser.delim(); err != 0 {
|
||||
return newErrorPair(parser.syntaxError(err))
|
||||
}
|
||||
|
||||
/* skip the value */
|
||||
if start, err := parser.skipFast(); err != 0 {
|
||||
return newErrorPair(parser.syntaxError(err))
|
||||
} else {
|
||||
t := switchRawType(parser.s[start])
|
||||
if t == _V_NONE {
|
||||
return newErrorPair(parser.syntaxError(types.ERR_INVALID_CHAR))
|
||||
}
|
||||
val = newRawNode(parser.s[start:parser.p], t, false)
|
||||
}
|
||||
|
||||
/* add the value to result */
|
||||
ret.Push(NewPair(key, val))
|
||||
self.l++
|
||||
parser.p = parser.lspace(parser.p)
|
||||
|
||||
/* check for EOF */
|
||||
if parser.p >= ns {
|
||||
return newErrorPair(parser.syntaxError(types.ERR_EOF))
|
||||
}
|
||||
|
||||
/* check for the next character */
|
||||
switch parser.s[parser.p] {
|
||||
case ',':
|
||||
parser.p++
|
||||
return ret.At(ret.Len()-1)
|
||||
case '}':
|
||||
parser.p++
|
||||
self.setObject(ret)
|
||||
return ret.At(ret.Len()-1)
|
||||
default:
|
||||
return newErrorPair(parser.syntaxError(types.ERR_INVALID_CHAR))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Parser Factory **/
|
||||
|
||||
// Loads parse all json into interface{}
|
||||
func Loads(src string) (int, interface{}, error) {
|
||||
ps := &Parser{s: src}
|
||||
np, err := ps.Parse()
|
||||
|
||||
/* check for errors */
|
||||
if err != 0 {
|
||||
return 0, nil, ps.ExportError(err)
|
||||
} else {
|
||||
x, err := np.Interface()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return ps.Pos(), x, nil
|
||||
}
|
||||
}
|
||||
|
||||
// LoadsUseNumber parse all json into interface{}, with numeric nodes cast to json.Number
|
||||
func LoadsUseNumber(src string) (int, interface{}, error) {
|
||||
ps := &Parser{s: src}
|
||||
np, err := ps.Parse()
|
||||
|
||||
/* check for errors */
|
||||
if err != 0 {
|
||||
return 0, nil, err
|
||||
} else {
|
||||
x, err := np.InterfaceUseNumber()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return ps.Pos(), x, nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewParser returns pointer of new allocated parser
|
||||
func NewParser(src string) *Parser {
|
||||
return &Parser{s: src}
|
||||
}
|
||||
|
||||
// NewParser returns new allocated parser
|
||||
func NewParserObj(src string) Parser {
|
||||
return Parser{s: src}
|
||||
}
|
||||
|
||||
// decodeNumber controls if parser decodes the number values instead of skip them
|
||||
// WARN: once you set decodeNumber(true), please set decodeNumber(false) before you drop the parser
|
||||
// otherwise the memory CANNOT be reused
|
||||
func (self *Parser) decodeNumber(decode bool) {
|
||||
if !decode && self.dbuf != nil {
|
||||
types.FreeDbuf(self.dbuf)
|
||||
self.dbuf = nil
|
||||
return
|
||||
}
|
||||
if decode && self.dbuf == nil {
|
||||
self.dbuf = types.NewDbuf()
|
||||
}
|
||||
}
|
||||
|
||||
// ExportError converts types.ParsingError to std Error
|
||||
func (self *Parser) ExportError(err types.ParsingError) error {
|
||||
if err == _ERR_NOT_FOUND {
|
||||
return ErrNotExist
|
||||
}
|
||||
return fmt.Errorf("%q", SyntaxError{
|
||||
Pos : self.p,
|
||||
Src : self.s,
|
||||
Code: err,
|
||||
}.Description())
|
||||
}
|
||||
|
||||
func backward(src string, i int) int {
|
||||
for ; i>=0 && utils.IsSpace(src[i]); i-- {}
|
||||
return i
|
||||
}
|
||||
|
||||
|
||||
func newRawNode(str string, typ types.ValueType, lock bool) Node {
|
||||
ret := Node{
|
||||
t: typ | _V_RAW,
|
||||
p: rt.StrPtr(str),
|
||||
l: uint(len(str)),
|
||||
}
|
||||
if lock {
|
||||
ret.m = new(sync.RWMutex)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
var typeJumpTable = [256]types.ValueType{
|
||||
'"' : types.V_STRING,
|
||||
'-' : _V_NUMBER,
|
||||
'0' : _V_NUMBER,
|
||||
'1' : _V_NUMBER,
|
||||
'2' : _V_NUMBER,
|
||||
'3' : _V_NUMBER,
|
||||
'4' : _V_NUMBER,
|
||||
'5' : _V_NUMBER,
|
||||
'6' : _V_NUMBER,
|
||||
'7' : _V_NUMBER,
|
||||
'8' : _V_NUMBER,
|
||||
'9' : _V_NUMBER,
|
||||
'[' : types.V_ARRAY,
|
||||
'f' : types.V_FALSE,
|
||||
'n' : types.V_NULL,
|
||||
't' : types.V_TRUE,
|
||||
'{' : types.V_OBJECT,
|
||||
}
|
||||
|
||||
func switchRawType(c byte) types.ValueType {
|
||||
return typeJumpTable[c]
|
||||
}
|
||||
|
||||
func (self *Node) loadt() types.ValueType {
|
||||
return (types.ValueType)(atomic.LoadInt64(&self.t))
|
||||
}
|
||||
|
||||
func (self *Node) lock() bool {
|
||||
if m := self.m; m != nil {
|
||||
m.Lock()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *Node) unlock() {
|
||||
if m := self.m; m != nil {
|
||||
m.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Node) rlock() bool {
|
||||
if m := self.m; m != nil {
|
||||
m.RLock()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *Node) runlock() {
|
||||
if m := self.m; m != nil {
|
||||
m.RUnlock()
|
||||
}
|
||||
}
|
||||
157
vendor/github.com/bytedance/sonic/ast/search.go
generated
vendored
Normal file
157
vendor/github.com/bytedance/sonic/ast/search.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
`github.com/bytedance/sonic/internal/native/types`
|
||||
)
|
||||
|
||||
// SearchOptions controls Searcher's behavior
|
||||
type SearchOptions struct {
|
||||
// ValidateJSON indicates the searcher to validate the entire JSON
|
||||
ValidateJSON bool
|
||||
|
||||
// CopyReturn indicates the searcher to copy the result JSON instead of refer from the input
|
||||
// This can help to reduce memory usage if you cache the results
|
||||
CopyReturn bool
|
||||
|
||||
// ConcurrentRead indicates the searcher to return a concurrently-READ-safe node,
|
||||
// including: GetByPath/Get/Index/GetOrIndex/Int64/Bool/Float64/String/Number/Interface/Array/Map/Raw/MarshalJSON
|
||||
ConcurrentRead bool
|
||||
}
|
||||
|
||||
type Searcher struct {
|
||||
parser Parser
|
||||
SearchOptions
|
||||
}
|
||||
|
||||
func NewSearcher(str string) *Searcher {
|
||||
return &Searcher{
|
||||
parser: Parser{
|
||||
s: str,
|
||||
noLazy: false,
|
||||
},
|
||||
SearchOptions: SearchOptions{
|
||||
ValidateJSON: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetByPathCopy search in depth from top json and returns a **Copied** json node at the path location
|
||||
func (self *Searcher) GetByPathCopy(path ...interface{}) (Node, error) {
|
||||
self.CopyReturn = true
|
||||
return self.getByPath(path...)
|
||||
}
|
||||
|
||||
// GetByPathNoCopy search in depth from top json and returns a **Referenced** json node at the path location
|
||||
//
|
||||
// WARN: this search directly refer partial json from top json, which has faster speed,
|
||||
// may consumes more memory.
|
||||
func (self *Searcher) GetByPath(path ...interface{}) (Node, error) {
|
||||
return self.getByPath(path...)
|
||||
}
|
||||
|
||||
func (self *Searcher) getByPath(path ...interface{}) (Node, error) {
|
||||
var err types.ParsingError
|
||||
var start int
|
||||
|
||||
self.parser.p = 0
|
||||
start, err = self.parser.getByPath(self.ValidateJSON, path...)
|
||||
if err != 0 {
|
||||
// for compatibility with old version
|
||||
if err == types.ERR_NOT_FOUND {
|
||||
return Node{}, ErrNotExist
|
||||
}
|
||||
if err == types.ERR_UNSUPPORT_TYPE {
|
||||
panic("path must be either int(>=0) or string")
|
||||
}
|
||||
return Node{}, self.parser.syntaxError(err)
|
||||
}
|
||||
|
||||
t := switchRawType(self.parser.s[start])
|
||||
if t == _V_NONE {
|
||||
return Node{}, self.parser.ExportError(err)
|
||||
}
|
||||
|
||||
// copy string to reducing memory usage
|
||||
var raw string
|
||||
if self.CopyReturn {
|
||||
raw = rt.Mem2Str([]byte(self.parser.s[start:self.parser.p]))
|
||||
} else {
|
||||
raw = self.parser.s[start:self.parser.p]
|
||||
}
|
||||
return newRawNode(raw, t, self.ConcurrentRead), nil
|
||||
}
|
||||
|
||||
// GetByPath searches a path and returns relaction and types of target
|
||||
func _GetByPath(src string, path ...interface{}) (start int, end int, typ int, err error) {
|
||||
p := NewParserObj(src)
|
||||
s, e := p.getByPath(false, path...)
|
||||
if e != 0 {
|
||||
// for compatibility with old version
|
||||
if e == types.ERR_NOT_FOUND {
|
||||
return -1, -1, 0, ErrNotExist
|
||||
}
|
||||
if e == types.ERR_UNSUPPORT_TYPE {
|
||||
panic("path must be either int(>=0) or string")
|
||||
}
|
||||
return -1, -1, 0, p.syntaxError(e)
|
||||
}
|
||||
|
||||
t := switchRawType(p.s[s])
|
||||
if t == _V_NONE {
|
||||
return -1, -1, 0, ErrNotExist
|
||||
}
|
||||
if t == _V_NUMBER {
|
||||
p.p = 1 + backward(p.s, p.p-1)
|
||||
}
|
||||
return s, p.p, int(t), nil
|
||||
}
|
||||
|
||||
// ValidSyntax check if a json has a valid JSON syntax,
|
||||
// while not validate UTF-8 charset
|
||||
func _ValidSyntax(json string) bool {
|
||||
p := NewParserObj(json)
|
||||
_, e := p.skip()
|
||||
if e != 0 {
|
||||
return false
|
||||
}
|
||||
if skipBlank(p.s, p.p) != -int(types.ERR_EOF) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SkipFast skip a json value in fast-skip algs,
|
||||
// while not strictly validate JSON syntax and UTF-8 charset.
|
||||
func _SkipFast(src string, i int) (int, int, error) {
|
||||
p := NewParserObj(src)
|
||||
p.p = i
|
||||
s, e := p.skipFast()
|
||||
if e != 0 {
|
||||
return -1, -1, p.ExportError(e)
|
||||
}
|
||||
t := switchRawType(p.s[s])
|
||||
if t == _V_NONE {
|
||||
return -1, -1, ErrNotExist
|
||||
}
|
||||
if t == _V_NUMBER {
|
||||
p.p = 1 + backward(p.s, p.p-1)
|
||||
}
|
||||
return s, p.p, nil
|
||||
}
|
||||
28
vendor/github.com/bytedance/sonic/ast/stubs.go
generated
vendored
Normal file
28
vendor/github.com/bytedance/sonic/ast/stubs.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/bytedance/sonic/internal/rt"
|
||||
)
|
||||
|
||||
//go:nosplit
|
||||
func mem2ptr(s []byte) unsafe.Pointer {
|
||||
return (*rt.GoSlice)(unsafe.Pointer(&s)).Ptr
|
||||
}
|
||||
333
vendor/github.com/bytedance/sonic/ast/visitor.go
generated
vendored
Normal file
333
vendor/github.com/bytedance/sonic/ast/visitor.go
generated
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
`encoding/json`
|
||||
`errors`
|
||||
|
||||
`github.com/bytedance/sonic/internal/native/types`
|
||||
`github.com/bytedance/sonic/unquote`
|
||||
)
|
||||
|
||||
// Visitor handles the callbacks during preorder traversal of a JSON AST.
|
||||
//
|
||||
// According to the JSON RFC8259, a JSON AST can be defined by
|
||||
// the following rules without separator / whitespace tokens.
|
||||
//
|
||||
// JSON-AST = value
|
||||
// value = false / null / true / object / array / number / string
|
||||
// object = begin-object [ member *( member ) ] end-object
|
||||
// member = string value
|
||||
// array = begin-array [ value *( value ) ] end-array
|
||||
//
|
||||
type Visitor interface {
|
||||
|
||||
// OnNull handles a JSON null value.
|
||||
OnNull() error
|
||||
|
||||
// OnBool handles a JSON true / false value.
|
||||
OnBool(v bool) error
|
||||
|
||||
// OnString handles a JSON string value.
|
||||
OnString(v string) error
|
||||
|
||||
// OnInt64 handles a JSON number value with int64 type.
|
||||
OnInt64(v int64, n json.Number) error
|
||||
|
||||
// OnFloat64 handles a JSON number value with float64 type.
|
||||
OnFloat64(v float64, n json.Number) error
|
||||
|
||||
// OnObjectBegin handles the beginning of a JSON object value with a
|
||||
// suggested capacity that can be used to make your custom object container.
|
||||
//
|
||||
// After this point the visitor will receive a sequence of callbacks like
|
||||
// [string, value, string, value, ......, ObjectEnd].
|
||||
//
|
||||
// Note:
|
||||
// 1. This is a recursive definition which means the value can
|
||||
// also be a JSON object / array described by a sequence of callbacks.
|
||||
// 2. The suggested capacity will be 0 if current object is empty.
|
||||
// 3. Currently sonic use a fixed capacity for non-empty object (keep in
|
||||
// sync with ast.Node) which might not be very suitable. This may be
|
||||
// improved in future version.
|
||||
OnObjectBegin(capacity int) error
|
||||
|
||||
// OnObjectKey handles a JSON object key string in member.
|
||||
OnObjectKey(key string) error
|
||||
|
||||
// OnObjectEnd handles the ending of a JSON object value.
|
||||
OnObjectEnd() error
|
||||
|
||||
// OnArrayBegin handles the beginning of a JSON array value with a
|
||||
// suggested capacity that can be used to make your custom array container.
|
||||
//
|
||||
// After this point the visitor will receive a sequence of callbacks like
|
||||
// [value, value, value, ......, ArrayEnd].
|
||||
//
|
||||
// Note:
|
||||
// 1. This is a recursive definition which means the value can
|
||||
// also be a JSON object / array described by a sequence of callbacks.
|
||||
// 2. The suggested capacity will be 0 if current array is empty.
|
||||
// 3. Currently sonic use a fixed capacity for non-empty array (keep in
|
||||
// sync with ast.Node) which might not be very suitable. This may be
|
||||
// improved in future version.
|
||||
OnArrayBegin(capacity int) error
|
||||
|
||||
// OnArrayEnd handles the ending of a JSON array value.
|
||||
OnArrayEnd() error
|
||||
}
|
||||
|
||||
// VisitorOptions contains all Visitor's options. The default value is an
|
||||
// empty VisitorOptions{}.
|
||||
type VisitorOptions struct {
|
||||
// OnlyNumber indicates parser to directly return number value without
|
||||
// conversion, then the first argument of OnInt64 / OnFloat64 will always
|
||||
// be zero.
|
||||
OnlyNumber bool
|
||||
}
|
||||
|
||||
var defaultVisitorOptions = &VisitorOptions{}
|
||||
|
||||
// Preorder decodes the whole JSON string and callbacks each AST node to visitor
|
||||
// during preorder traversal. Any visitor method with an error returned will
|
||||
// break the traversal and the given error will be directly returned. The opts
|
||||
// argument can be reused after every call.
|
||||
func Preorder(str string, visitor Visitor, opts *VisitorOptions) error {
|
||||
if opts == nil {
|
||||
opts = defaultVisitorOptions
|
||||
}
|
||||
// process VisitorOptions first to guarantee that all options will be
|
||||
// constant during decoding and make options more readable.
|
||||
var (
|
||||
optDecodeNumber = !opts.OnlyNumber
|
||||
)
|
||||
|
||||
tv := &traverser{
|
||||
parser: Parser{
|
||||
s: str,
|
||||
noLazy: true,
|
||||
skipValue: false,
|
||||
},
|
||||
visitor: visitor,
|
||||
}
|
||||
|
||||
if optDecodeNumber {
|
||||
tv.parser.decodeNumber(true)
|
||||
}
|
||||
|
||||
err := tv.decodeValue()
|
||||
|
||||
if optDecodeNumber {
|
||||
tv.parser.decodeNumber(false)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type traverser struct {
|
||||
parser Parser
|
||||
visitor Visitor
|
||||
}
|
||||
|
||||
// NOTE: keep in sync with (*Parser).Parse method.
|
||||
func (self *traverser) decodeValue() error {
|
||||
switch val := self.parser.decodeValue(); val.Vt {
|
||||
case types.V_EOF:
|
||||
return types.ERR_EOF
|
||||
case types.V_NULL:
|
||||
return self.visitor.OnNull()
|
||||
case types.V_TRUE:
|
||||
return self.visitor.OnBool(true)
|
||||
case types.V_FALSE:
|
||||
return self.visitor.OnBool(false)
|
||||
case types.V_STRING:
|
||||
return self.decodeString(val.Iv, val.Ep)
|
||||
case types.V_DOUBLE:
|
||||
return self.visitor.OnFloat64(val.Dv,
|
||||
json.Number(self.parser.s[val.Ep:self.parser.p]))
|
||||
case types.V_INTEGER:
|
||||
return self.visitor.OnInt64(val.Iv,
|
||||
json.Number(self.parser.s[val.Ep:self.parser.p]))
|
||||
case types.V_ARRAY:
|
||||
return self.decodeArray()
|
||||
case types.V_OBJECT:
|
||||
return self.decodeObject()
|
||||
default:
|
||||
return types.ParsingError(-val.Vt)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: keep in sync with (*Parser).decodeArray method.
|
||||
func (self *traverser) decodeArray() error {
|
||||
sp := self.parser.p
|
||||
ns := len(self.parser.s)
|
||||
|
||||
/* allocate array space and parse every element */
|
||||
if err := self.visitor.OnArrayBegin(_DEFAULT_NODE_CAP); err != nil {
|
||||
if err == VisitOPSkip {
|
||||
// NOTICE: for user needs to skip entry object
|
||||
self.parser.p -= 1
|
||||
if _, e := self.parser.skipFast(); e != 0 {
|
||||
return e
|
||||
}
|
||||
return self.visitor.OnArrayEnd()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/* check for EOF */
|
||||
self.parser.p = self.parser.lspace(sp)
|
||||
if self.parser.p >= ns {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for empty array */
|
||||
if self.parser.s[self.parser.p] == ']' {
|
||||
self.parser.p++
|
||||
return self.visitor.OnArrayEnd()
|
||||
}
|
||||
|
||||
for {
|
||||
/* decode the value */
|
||||
if err := self.decodeValue(); err != nil {
|
||||
return err
|
||||
}
|
||||
self.parser.p = self.parser.lspace(self.parser.p)
|
||||
|
||||
/* check for EOF */
|
||||
if self.parser.p >= ns {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the next character */
|
||||
switch self.parser.s[self.parser.p] {
|
||||
case ',':
|
||||
self.parser.p++
|
||||
case ']':
|
||||
self.parser.p++
|
||||
return self.visitor.OnArrayEnd()
|
||||
default:
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: keep in sync with (*Parser).decodeObject method.
|
||||
func (self *traverser) decodeObject() error {
|
||||
sp := self.parser.p
|
||||
ns := len(self.parser.s)
|
||||
|
||||
/* allocate object space and decode each pair */
|
||||
if err := self.visitor.OnObjectBegin(_DEFAULT_NODE_CAP); err != nil {
|
||||
if err == VisitOPSkip {
|
||||
// NOTICE: for user needs to skip entry object
|
||||
self.parser.p -= 1
|
||||
if _, e := self.parser.skipFast(); e != 0 {
|
||||
return e
|
||||
}
|
||||
return self.visitor.OnObjectEnd()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/* check for EOF */
|
||||
self.parser.p = self.parser.lspace(sp)
|
||||
if self.parser.p >= ns {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for empty object */
|
||||
if self.parser.s[self.parser.p] == '}' {
|
||||
self.parser.p++
|
||||
return self.visitor.OnObjectEnd()
|
||||
}
|
||||
|
||||
for {
|
||||
var njs types.JsonState
|
||||
var err types.ParsingError
|
||||
|
||||
/* decode the key */
|
||||
if njs = self.parser.decodeValue(); njs.Vt != types.V_STRING {
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
|
||||
/* extract the key */
|
||||
idx := self.parser.p - 1
|
||||
key := self.parser.s[njs.Iv:idx]
|
||||
|
||||
/* check for escape sequence */
|
||||
if njs.Ep != -1 {
|
||||
if key, err = unquote.String(key); err != 0 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.visitor.OnObjectKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* expect a ':' delimiter */
|
||||
if err = self.parser.delim(); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
/* decode the value */
|
||||
if err := self.decodeValue(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.parser.p = self.parser.lspace(self.parser.p)
|
||||
|
||||
/* check for EOF */
|
||||
if self.parser.p >= ns {
|
||||
return types.ERR_EOF
|
||||
}
|
||||
|
||||
/* check for the next character */
|
||||
switch self.parser.s[self.parser.p] {
|
||||
case ',':
|
||||
self.parser.p++
|
||||
case '}':
|
||||
self.parser.p++
|
||||
return self.visitor.OnObjectEnd()
|
||||
default:
|
||||
return types.ERR_INVALID_CHAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: keep in sync with (*Parser).decodeString method.
|
||||
func (self *traverser) decodeString(iv int64, ep int) error {
|
||||
p := self.parser.p - 1
|
||||
s := self.parser.s[iv:p]
|
||||
|
||||
/* fast path: no escape sequence */
|
||||
if ep == -1 {
|
||||
return self.visitor.OnString(s)
|
||||
}
|
||||
|
||||
/* unquote the string */
|
||||
out, err := unquote.String(s)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return self.visitor.OnString(out)
|
||||
}
|
||||
|
||||
// If visitor return this error on `OnObjectBegin()` or `OnArrayBegin()`,
|
||||
// the traverser will skip entry object or array
|
||||
var VisitOPSkip = errors.New("")
|
||||
143
vendor/github.com/bytedance/sonic/compat.go
generated
vendored
Normal file
143
vendor/github.com/bytedance/sonic/compat.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20
|
||||
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sonic
|
||||
|
||||
import (
|
||||
`bytes`
|
||||
`encoding/json`
|
||||
`io`
|
||||
`reflect`
|
||||
|
||||
`github.com/bytedance/sonic/option`
|
||||
)
|
||||
|
||||
const apiKind = UseStdJSON
|
||||
|
||||
type frozenConfig struct {
|
||||
Config
|
||||
}
|
||||
|
||||
// Froze convert the Config to API
|
||||
func (cfg Config) Froze() API {
|
||||
api := &frozenConfig{Config: cfg}
|
||||
return api
|
||||
}
|
||||
|
||||
func (cfg frozenConfig) marshalOptions(val interface{}, prefix, indent string) ([]byte, error) {
|
||||
w := bytes.NewBuffer([]byte{})
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetEscapeHTML(cfg.EscapeHTML)
|
||||
enc.SetIndent(prefix, indent)
|
||||
err := enc.Encode(val)
|
||||
out := w.Bytes()
|
||||
|
||||
// json.Encoder always appends '\n' after encoding,
|
||||
// which is not same with json.Marshal()
|
||||
if len(out) > 0 && out[len(out)-1] == '\n' {
|
||||
out = out[:len(out)-1]
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Marshal is implemented by sonic
|
||||
func (cfg frozenConfig) Marshal(val interface{}) ([]byte, error) {
|
||||
if !cfg.EscapeHTML {
|
||||
return cfg.marshalOptions(val, "", "")
|
||||
}
|
||||
return json.Marshal(val)
|
||||
}
|
||||
|
||||
// MarshalToString is implemented by sonic
|
||||
func (cfg frozenConfig) MarshalToString(val interface{}) (string, error) {
|
||||
out, err := cfg.Marshal(val)
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
// MarshalIndent is implemented by sonic
|
||||
func (cfg frozenConfig) MarshalIndent(val interface{}, prefix, indent string) ([]byte, error) {
|
||||
if !cfg.EscapeHTML {
|
||||
return cfg.marshalOptions(val, prefix, indent)
|
||||
}
|
||||
return json.MarshalIndent(val, prefix, indent)
|
||||
}
|
||||
|
||||
// UnmarshalFromString is implemented by sonic
|
||||
func (cfg frozenConfig) UnmarshalFromString(buf string, val interface{}) error {
|
||||
r := bytes.NewBufferString(buf)
|
||||
dec := json.NewDecoder(r)
|
||||
if cfg.UseNumber {
|
||||
dec.UseNumber()
|
||||
}
|
||||
if cfg.DisallowUnknownFields {
|
||||
dec.DisallowUnknownFields()
|
||||
}
|
||||
err := dec.Decode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check the trailing chars
|
||||
offset := dec.InputOffset()
|
||||
if t, err := dec.Token(); !(t == nil && err == io.EOF) {
|
||||
return &json.SyntaxError{ Offset: offset}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal is implemented by sonic
|
||||
func (cfg frozenConfig) Unmarshal(buf []byte, val interface{}) error {
|
||||
return cfg.UnmarshalFromString(string(buf), val)
|
||||
}
|
||||
|
||||
// NewEncoder is implemented by sonic
|
||||
func (cfg frozenConfig) NewEncoder(writer io.Writer) Encoder {
|
||||
enc := json.NewEncoder(writer)
|
||||
if !cfg.EscapeHTML {
|
||||
enc.SetEscapeHTML(cfg.EscapeHTML)
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
// NewDecoder is implemented by sonic
|
||||
func (cfg frozenConfig) NewDecoder(reader io.Reader) Decoder {
|
||||
dec := json.NewDecoder(reader)
|
||||
if cfg.UseNumber {
|
||||
dec.UseNumber()
|
||||
}
|
||||
if cfg.DisallowUnknownFields {
|
||||
dec.DisallowUnknownFields()
|
||||
}
|
||||
return dec
|
||||
}
|
||||
|
||||
// Valid is implemented by sonic
|
||||
func (cfg frozenConfig) Valid(data []byte) bool {
|
||||
return json.Valid(data)
|
||||
}
|
||||
|
||||
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
|
||||
// order to reduce the first-hit latency at **amd64** Arch.
|
||||
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
|
||||
// a compile option to set the depth of recursive compile for the nested struct type.
|
||||
// * This is the none implement for !amd64.
|
||||
// It will be useful for someone who develop with !amd64 arch,like Mac M1.
|
||||
func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
201
vendor/github.com/bytedance/sonic/decoder/decoder_compat.go
generated
vendored
Normal file
201
vendor/github.com/bytedance/sonic/decoder/decoder_compat.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
//go:build (!amd64 && !arm64) || go1.27 || !go1.17 || (arm64 && !go1.20)
|
||||
// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20
|
||||
|
||||
/*
|
||||
* Copyright 2023 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package decoder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bytedance/sonic/internal/decoder/consts"
|
||||
"github.com/bytedance/sonic/internal/native/types"
|
||||
"github.com/bytedance/sonic/option"
|
||||
"github.com/bytedance/sonic/internal/compat"
|
||||
)
|
||||
|
||||
func init() {
|
||||
compat.Warn("sonic/decoder")
|
||||
}
|
||||
|
||||
const (
|
||||
_F_use_int64 = consts.F_use_int64
|
||||
_F_disable_urc = consts.F_disable_unknown
|
||||
_F_disable_unknown = consts.F_disable_unknown
|
||||
_F_copy_string = consts.F_copy_string
|
||||
|
||||
_F_use_number = consts.F_use_number
|
||||
_F_validate_string = consts.F_validate_string
|
||||
_F_allow_control = consts.F_allow_control
|
||||
_F_no_validate_json = consts.F_no_validate_json
|
||||
_F_case_sensitive = consts.F_case_sensitive
|
||||
)
|
||||
|
||||
type Options uint64
|
||||
|
||||
const (
|
||||
OptionUseInt64 Options = 1 << _F_use_int64
|
||||
OptionUseNumber Options = 1 << _F_use_number
|
||||
OptionUseUnicodeErrors Options = 1 << _F_disable_urc
|
||||
OptionDisableUnknown Options = 1 << _F_disable_unknown
|
||||
OptionCopyString Options = 1 << _F_copy_string
|
||||
OptionValidateString Options = 1 << _F_validate_string
|
||||
OptionNoValidateJSON Options = 1 << _F_no_validate_json
|
||||
OptionCaseSensitive Options = 1 << _F_case_sensitive
|
||||
)
|
||||
|
||||
func (self *Decoder) SetOptions(opts Options) {
|
||||
if (opts & OptionUseNumber != 0) && (opts & OptionUseInt64 != 0) {
|
||||
panic("can't set OptionUseInt64 and OptionUseNumber both!")
|
||||
}
|
||||
self.f = uint64(opts)
|
||||
}
|
||||
|
||||
|
||||
// Decoder is the decoder context object
|
||||
type Decoder struct {
|
||||
i int
|
||||
f uint64
|
||||
s string
|
||||
}
|
||||
|
||||
// NewDecoder creates a new decoder instance.
|
||||
func NewDecoder(s string) *Decoder {
|
||||
return &Decoder{s: s}
|
||||
}
|
||||
|
||||
// Pos returns the current decoding position.
|
||||
func (self *Decoder) Pos() int {
|
||||
return self.i
|
||||
}
|
||||
|
||||
func (self *Decoder) Reset(s string) {
|
||||
self.s = s
|
||||
self.i = 0
|
||||
// self.f = 0
|
||||
}
|
||||
|
||||
// NOTE: api fallback do nothing
|
||||
func (self *Decoder) CheckTrailings() error {
|
||||
pos := self.i
|
||||
buf := self.s
|
||||
/* skip all the trailing spaces */
|
||||
if pos != len(buf) {
|
||||
for pos < len(buf) && (types.SPACE_MASK & (1 << buf[pos])) != 0 {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
/* then it must be at EOF */
|
||||
if pos == len(buf) {
|
||||
return nil
|
||||
}
|
||||
|
||||
/* junk after JSON value */
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Decode parses the JSON-encoded data from current position and stores the result
|
||||
// in the value pointed to by val.
|
||||
func (self *Decoder) Decode(val interface{}) error {
|
||||
r := bytes.NewBufferString(self.s)
|
||||
dec := json.NewDecoder(r)
|
||||
if (self.f & uint64(OptionUseNumber)) != 0 {
|
||||
dec.UseNumber()
|
||||
}
|
||||
if (self.f & uint64(OptionDisableUnknown)) != 0 {
|
||||
dec.DisallowUnknownFields()
|
||||
}
|
||||
return dec.Decode(val)
|
||||
}
|
||||
|
||||
// UseInt64 indicates the Decoder to unmarshal an integer into an interface{} as an
|
||||
// int64 instead of as a float64.
|
||||
func (self *Decoder) UseInt64() {
|
||||
self.f |= 1 << _F_use_int64
|
||||
self.f &^= 1 << _F_use_number
|
||||
}
|
||||
|
||||
// UseNumber indicates the Decoder to unmarshal a number into an interface{} as a
|
||||
// json.Number instead of as a float64.
|
||||
func (self *Decoder) UseNumber() {
|
||||
self.f &^= 1 << _F_use_int64
|
||||
self.f |= 1 << _F_use_number
|
||||
}
|
||||
|
||||
// UseUnicodeErrors indicates the Decoder to return an error when encounter invalid
|
||||
// UTF-8 escape sequences.
|
||||
func (self *Decoder) UseUnicodeErrors() {
|
||||
self.f |= 1 << _F_disable_urc
|
||||
}
|
||||
|
||||
// DisallowUnknownFields indicates the Decoder to return an error when the destination
|
||||
// is a struct and the input contains object keys which do not match any
|
||||
// non-ignored, exported fields in the destination.
|
||||
func (self *Decoder) DisallowUnknownFields() {
|
||||
self.f |= 1 << _F_disable_unknown
|
||||
}
|
||||
|
||||
// CopyString indicates the Decoder to decode string values by copying instead of referring.
|
||||
func (self *Decoder) CopyString() {
|
||||
self.f |= 1 << _F_copy_string
|
||||
}
|
||||
|
||||
// ValidateString causes the Decoder to validate string values when decoding string value
|
||||
// in JSON. Validation is that, returning error when unescaped control chars(0x00-0x1f) or
|
||||
// invalid UTF-8 chars in the string value of JSON.
|
||||
func (self *Decoder) ValidateString() {
|
||||
self.f |= 1 << _F_validate_string
|
||||
}
|
||||
|
||||
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
|
||||
// order to reduce the first-hit latency.
|
||||
//
|
||||
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
|
||||
// a compile option to set the depth of recursive compile for the nested struct type.
|
||||
func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type StreamDecoder = json.Decoder
|
||||
|
||||
// NewStreamDecoder adapts to encoding/json.NewDecoder API.
|
||||
//
|
||||
// NewStreamDecoder returns a new decoder that reads from r.
|
||||
func NewStreamDecoder(r io.Reader) *StreamDecoder {
|
||||
return json.NewDecoder(r)
|
||||
}
|
||||
|
||||
// SyntaxError represents json syntax error
|
||||
type SyntaxError json.SyntaxError
|
||||
|
||||
// Description
|
||||
func (s SyntaxError) Description() string {
|
||||
return (*json.SyntaxError)(unsafe.Pointer(&s)).Error()
|
||||
}
|
||||
// Error
|
||||
func (s SyntaxError) Error() string {
|
||||
return (*json.SyntaxError)(unsafe.Pointer(&s)).Error()
|
||||
}
|
||||
|
||||
// MismatchTypeError represents mismatching between json and object
|
||||
type MismatchTypeError json.UnmarshalTypeError
|
||||
72
vendor/github.com/bytedance/sonic/decoder/decoder_native.go
generated
vendored
Normal file
72
vendor/github.com/bytedance/sonic/decoder/decoder_native.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27)
|
||||
// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27
|
||||
|
||||
|
||||
/*
|
||||
* Copyright 2023 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package decoder
|
||||
|
||||
import (
|
||||
`github.com/bytedance/sonic/internal/decoder/api`
|
||||
)
|
||||
|
||||
// Decoder is the decoder context object
|
||||
type Decoder = api.Decoder
|
||||
|
||||
// SyntaxError represents json syntax error
|
||||
type SyntaxError = api.SyntaxError
|
||||
|
||||
// MismatchTypeError represents mismatching between json and object
|
||||
type MismatchTypeError = api.MismatchTypeError
|
||||
|
||||
// Options for decode.
|
||||
type Options = api.Options
|
||||
|
||||
const (
|
||||
OptionUseInt64 Options = api.OptionUseInt64
|
||||
OptionUseNumber Options = api.OptionUseNumber
|
||||
OptionUseUnicodeErrors Options = api.OptionUseUnicodeErrors
|
||||
OptionDisableUnknown Options = api.OptionDisableUnknown
|
||||
OptionCopyString Options = api.OptionCopyString
|
||||
OptionValidateString Options = api.OptionValidateString
|
||||
OptionNoValidateJSON Options = api.OptionNoValidateJSON
|
||||
OptionCaseSensitive Options = api.OptionCaseSensitive
|
||||
)
|
||||
|
||||
// StreamDecoder is the decoder context object for streaming input.
|
||||
type StreamDecoder = api.StreamDecoder
|
||||
|
||||
var (
|
||||
// NewDecoder creates a new decoder instance.
|
||||
NewDecoder = api.NewDecoder
|
||||
|
||||
// NewStreamDecoder adapts to encoding/json.NewDecoder API.
|
||||
//
|
||||
// NewStreamDecoder returns a new decoder that reads from r.
|
||||
NewStreamDecoder = api.NewStreamDecoder
|
||||
|
||||
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
|
||||
// order to reduce the first-hit latency.
|
||||
//
|
||||
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
|
||||
// a compile option to set the depth of recursive compile for the nested struct type.
|
||||
Pretouch = api.Pretouch
|
||||
|
||||
// Skip skips only one json value, and returns first non-blank character position and its ending position if it is valid.
|
||||
// Otherwise, returns negative error code using start and invalid character position using end
|
||||
Skip = api.Skip
|
||||
)
|
||||
262
vendor/github.com/bytedance/sonic/encoder/encoder_compat.go
generated
vendored
Normal file
262
vendor/github.com/bytedance/sonic/encoder/encoder_compat.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20
|
||||
|
||||
/*
|
||||
* Copyright 2023 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package encoder
|
||||
|
||||
import (
|
||||
`io`
|
||||
`bytes`
|
||||
`encoding/json`
|
||||
`reflect`
|
||||
|
||||
`github.com/bytedance/sonic/option`
|
||||
`github.com/bytedance/sonic/internal/compat`
|
||||
)
|
||||
|
||||
func init() {
|
||||
compat.Warn("sonic/encoder")
|
||||
}
|
||||
|
||||
// EnableFallback indicates if encoder use fallback
|
||||
const EnableFallback = true
|
||||
|
||||
// Options is a set of encoding options.
|
||||
type Options uint64
|
||||
|
||||
const (
|
||||
bitSortMapKeys = iota
|
||||
bitEscapeHTML
|
||||
bitCompactMarshaler
|
||||
bitNoQuoteTextMarshaler
|
||||
bitNoNullSliceOrMap
|
||||
bitValidateString
|
||||
bitNoValidateJSONMarshaler
|
||||
bitNoEncoderNewline
|
||||
|
||||
// used for recursive compile
|
||||
bitPointerValue = 63
|
||||
)
|
||||
|
||||
const (
|
||||
// SortMapKeys indicates that the keys of a map needs to be sorted
|
||||
// before serializing into JSON.
|
||||
// WARNING: This hurts performance A LOT, USE WITH CARE.
|
||||
SortMapKeys Options = 1 << bitSortMapKeys
|
||||
|
||||
// EscapeHTML indicates encoder to escape all HTML characters
|
||||
// after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape).
|
||||
// WARNING: This hurts performance A LOT, USE WITH CARE.
|
||||
EscapeHTML Options = 1 << bitEscapeHTML
|
||||
|
||||
// CompactMarshaler indicates that the output JSON from json.Marshaler
|
||||
// is always compact and needs no validation
|
||||
CompactMarshaler Options = 1 << bitCompactMarshaler
|
||||
|
||||
// NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler
|
||||
// is always escaped string and needs no quoting
|
||||
NoQuoteTextMarshaler Options = 1 << bitNoQuoteTextMarshaler
|
||||
|
||||
// NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}',
|
||||
// instead of 'null'
|
||||
NoNullSliceOrMap Options = 1 << bitNoNullSliceOrMap
|
||||
|
||||
// ValidateString indicates that encoder should validate the input string
|
||||
// before encoding it into JSON.
|
||||
ValidateString Options = 1 << bitValidateString
|
||||
|
||||
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
|
||||
// after encoding the JSONMarshaler to JSON.
|
||||
NoValidateJSONMarshaler Options = 1 << bitNoValidateJSONMarshaler
|
||||
|
||||
// NoEncoderNewline indicates that the encoder should not add a newline after every message
|
||||
NoEncoderNewline Options = 1 << bitNoEncoderNewline
|
||||
|
||||
// CompatibleWithStd is used to be compatible with std encoder.
|
||||
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
|
||||
)
|
||||
|
||||
// Encoder represents a specific set of encoder configurations.
|
||||
type Encoder struct {
|
||||
Opts Options
|
||||
prefix string
|
||||
indent string
|
||||
}
|
||||
|
||||
// Encode returns the JSON encoding of v.
|
||||
func (self *Encoder) Encode(v interface{}) ([]byte, error) {
|
||||
if self.indent != "" || self.prefix != "" {
|
||||
return EncodeIndented(v, self.prefix, self.indent, self.Opts)
|
||||
}
|
||||
return Encode(v, self.Opts)
|
||||
}
|
||||
|
||||
// SortKeys enables the SortMapKeys option.
|
||||
func (self *Encoder) SortKeys() *Encoder {
|
||||
self.Opts |= SortMapKeys
|
||||
return self
|
||||
}
|
||||
|
||||
// SetEscapeHTML specifies if option EscapeHTML opens
|
||||
func (self *Encoder) SetEscapeHTML(f bool) {
|
||||
if f {
|
||||
self.Opts |= EscapeHTML
|
||||
} else {
|
||||
self.Opts &= ^EscapeHTML
|
||||
}
|
||||
}
|
||||
|
||||
// SetValidateString specifies if option ValidateString opens
|
||||
func (self *Encoder) SetValidateString(f bool) {
|
||||
if f {
|
||||
self.Opts |= ValidateString
|
||||
} else {
|
||||
self.Opts &= ^ValidateString
|
||||
}
|
||||
}
|
||||
|
||||
// SetNoValidateJSONMarshaler specifies if option NoValidateJSONMarshaler opens
|
||||
func (self *Encoder) SetNoValidateJSONMarshaler(f bool) {
|
||||
if f {
|
||||
self.Opts |= NoValidateJSONMarshaler
|
||||
} else {
|
||||
self.Opts &= ^NoValidateJSONMarshaler
|
||||
}
|
||||
}
|
||||
|
||||
// SetNoEncoderNewline specifies if option NoEncoderNewline opens
|
||||
func (self *Encoder) SetNoEncoderNewline(f bool) {
|
||||
if f {
|
||||
self.Opts |= NoEncoderNewline
|
||||
} else {
|
||||
self.Opts &= ^NoEncoderNewline
|
||||
}
|
||||
}
|
||||
|
||||
// SetCompactMarshaler specifies if option CompactMarshaler opens
|
||||
func (self *Encoder) SetCompactMarshaler(f bool) {
|
||||
if f {
|
||||
self.Opts |= CompactMarshaler
|
||||
} else {
|
||||
self.Opts &= ^CompactMarshaler
|
||||
}
|
||||
}
|
||||
|
||||
// SetNoQuoteTextMarshaler specifies if option NoQuoteTextMarshaler opens
|
||||
func (self *Encoder) SetNoQuoteTextMarshaler(f bool) {
|
||||
if f {
|
||||
self.Opts |= NoQuoteTextMarshaler
|
||||
} else {
|
||||
self.Opts &= ^NoQuoteTextMarshaler
|
||||
}
|
||||
}
|
||||
|
||||
// SetIndent instructs the encoder to format each subsequent encoded
|
||||
// value as if indented by the package-level function EncodeIndent().
|
||||
// Calling SetIndent("", "") disables indentation.
|
||||
func (enc *Encoder) SetIndent(prefix, indent string) {
|
||||
enc.prefix = prefix
|
||||
enc.indent = indent
|
||||
}
|
||||
|
||||
// Quote returns the JSON-quoted version of s.
|
||||
func Quote(s string) string {
|
||||
/* check for empty string */
|
||||
if s == "" {
|
||||
return `""`
|
||||
}
|
||||
|
||||
out, _ := json.Marshal(s)
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// Encode returns the JSON encoding of val, encoded with opts.
|
||||
func Encode(val interface{}, opts Options) ([]byte, error) {
|
||||
return json.Marshal(val)
|
||||
}
|
||||
|
||||
// EncodeInto is like Encode but uses a user-supplied buffer instead of allocating
|
||||
// a new one.
|
||||
func EncodeInto(buf *[]byte, val interface{}, opts Options) error {
|
||||
if buf == nil {
|
||||
panic("user-supplied buffer buf is nil")
|
||||
}
|
||||
w := bytes.NewBuffer(*buf)
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetEscapeHTML((opts & EscapeHTML) != 0)
|
||||
err := enc.Encode(val)
|
||||
*buf = w.Bytes()
|
||||
l := len(*buf)
|
||||
if l > 0 && (opts & NoEncoderNewline != 0) && (*buf)[l-1] == '\n' {
|
||||
*buf = (*buf)[:l-1]
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
|
||||
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
|
||||
// so that the JSON will be safe to embed inside HTML <script> tags.
|
||||
// For historical reasons, web browsers don't honor standard HTML
|
||||
// escaping within <script> tags, so an alternative JSON encoding must
|
||||
// be used.
|
||||
func HTMLEscape(dst []byte, src []byte) []byte {
|
||||
d := bytes.NewBuffer(dst)
|
||||
json.HTMLEscape(d, src)
|
||||
return d.Bytes()
|
||||
}
|
||||
|
||||
// EncodeIndented is like Encode but applies Indent to format the output.
|
||||
// Each JSON element in the output will begin on a new line beginning with prefix
|
||||
// followed by one or more copies of indent according to the indentation nesting.
|
||||
func EncodeIndented(val interface{}, prefix string, indent string, opts Options) ([]byte, error) {
|
||||
w := bytes.NewBuffer([]byte{})
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetEscapeHTML((opts & EscapeHTML) != 0)
|
||||
enc.SetIndent(prefix, indent)
|
||||
err := enc.Encode(val)
|
||||
out := w.Bytes()
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
|
||||
// order to reduce the first-hit latency.
|
||||
//
|
||||
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
|
||||
// a compile option to set the depth of recursive compile for the nested struct type.
|
||||
func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Valid validates json and returns first non-blank character position,
|
||||
// if it is only one valid json value.
|
||||
// Otherwise returns invalid character position using start.
|
||||
//
|
||||
// Note: it does not check for the invalid UTF-8 characters.
|
||||
func Valid(data []byte) (ok bool, start int) {
|
||||
return json.Valid(data), 0
|
||||
}
|
||||
|
||||
// StreamEncoder uses io.Writer as
|
||||
type StreamEncoder = json.Encoder
|
||||
|
||||
// NewStreamEncoder adapts to encoding/json.NewDecoder API.
|
||||
//
|
||||
// NewStreamEncoder returns a new encoder that write to w.
|
||||
func NewStreamEncoder(w io.Writer) *StreamEncoder {
|
||||
return json.NewEncoder(w)
|
||||
}
|
||||
|
||||
121
vendor/github.com/bytedance/sonic/encoder/encoder_native.go
generated
vendored
Normal file
121
vendor/github.com/bytedance/sonic/encoder/encoder_native.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27
|
||||
|
||||
/*
|
||||
* Copyright 2023 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package encoder
|
||||
|
||||
import (
|
||||
`github.com/bytedance/sonic/internal/encoder`
|
||||
)
|
||||
|
||||
// EnableFallback indicates if encoder use fallback
|
||||
const EnableFallback = false
|
||||
|
||||
// Encoder represents a specific set of encoder configurations.
|
||||
type Encoder = encoder.Encoder
|
||||
|
||||
// StreamEncoder uses io.Writer as input.
|
||||
type StreamEncoder = encoder.StreamEncoder
|
||||
|
||||
// Options is a set of encoding options.
|
||||
type Options = encoder.Options
|
||||
|
||||
const (
|
||||
// SortMapKeys indicates that the keys of a map needs to be sorted
|
||||
// before serializing into JSON.
|
||||
// WARNING: This hurts performance A LOT, USE WITH CARE.
|
||||
SortMapKeys Options = encoder.SortMapKeys
|
||||
|
||||
// EscapeHTML indicates encoder to escape all HTML characters
|
||||
// after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape).
|
||||
// WARNING: This hurts performance A LOT, USE WITH CARE.
|
||||
EscapeHTML Options = encoder.EscapeHTML
|
||||
|
||||
// CompactMarshaler indicates that the output JSON from json.Marshaler
|
||||
// is always compact and needs no validation
|
||||
CompactMarshaler Options = encoder.CompactMarshaler
|
||||
|
||||
// NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler
|
||||
// is always escaped string and needs no quoting
|
||||
NoQuoteTextMarshaler Options = encoder.NoQuoteTextMarshaler
|
||||
|
||||
// NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}',
|
||||
// instead of 'null'
|
||||
NoNullSliceOrMap Options = encoder.NoNullSliceOrMap
|
||||
|
||||
// ValidateString indicates that encoder should validate the input string
|
||||
// before encoding it into JSON.
|
||||
ValidateString Options = encoder.ValidateString
|
||||
|
||||
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
|
||||
// after encoding the JSONMarshaler to JSON.
|
||||
NoValidateJSONMarshaler Options = encoder.NoValidateJSONMarshaler
|
||||
|
||||
// NoEncoderNewline indicates that the encoder should not add a newline after every message
|
||||
NoEncoderNewline Options = encoder.NoEncoderNewline
|
||||
|
||||
// CompatibleWithStd is used to be compatible with std encoder.
|
||||
CompatibleWithStd Options = encoder.CompatibleWithStd
|
||||
|
||||
// EncodeNullForInfOrNan encodes Infinity or NaN float values as 'null'
|
||||
// instead of returning an error.
|
||||
EncodeNullForInfOrNan Options = encoder.EncodeNullForInfOrNan
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
// Encode returns the JSON encoding of val, encoded with opts.
|
||||
Encode = encoder.Encode
|
||||
|
||||
// EncodeIndented is like Encode but applies Indent to format the output.
|
||||
// Each JSON element in the output will begin on a new line beginning with prefix
|
||||
// followed by one or more copies of indent according to the indentation nesting.
|
||||
EncodeIndented = encoder.EncodeIndented
|
||||
|
||||
// EncodeInto is like Encode but uses a user-supplied buffer instead of allocating a new one.
|
||||
EncodeInto = encoder.EncodeInto
|
||||
|
||||
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
|
||||
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
|
||||
// so that the JSON will be safe to embed inside HTML <script> tags.
|
||||
// For historical reasons, web browsers don't honor standard HTML
|
||||
// escaping within <script> tags, so an alternative JSON encoding must
|
||||
// be used.
|
||||
HTMLEscape = encoder.HTMLEscape
|
||||
|
||||
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
|
||||
// order to reduce the first-hit latency.
|
||||
//
|
||||
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
|
||||
// a compile option to set the depth of recursive compile for the nested struct type.
|
||||
Pretouch = encoder.Pretouch
|
||||
|
||||
// Quote returns the JSON-quoted version of s.
|
||||
Quote = encoder.Quote
|
||||
|
||||
// Valid validates json and returns first non-blank character position,
|
||||
// if it is only one valid json value.
|
||||
// Otherwise returns invalid character position using start.
|
||||
//
|
||||
// Note: it does not check for the invalid UTF-8 characters.
|
||||
Valid = encoder.Valid
|
||||
|
||||
// NewStreamEncoder adapts to encoding/json.NewDecoder API.
|
||||
//
|
||||
// NewStreamEncoder returns a new encoder that write to w.
|
||||
NewStreamEncoder = encoder.NewStreamEncoder
|
||||
)
|
||||
10
vendor/github.com/bytedance/sonic/go.work
generated
vendored
Normal file
10
vendor/github.com/bytedance/sonic/go.work
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
go 1.18
|
||||
|
||||
use (
|
||||
.
|
||||
./external_jsonlib_test
|
||||
./fuzz
|
||||
./generic_test
|
||||
./issue_test
|
||||
./loader
|
||||
)
|
||||
10
vendor/github.com/bytedance/sonic/go.work.sum
generated
vendored
Normal file
10
vendor/github.com/bytedance/sonic/go.work.sum
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
0
vendor/github.com/bytedance/sonic/internal/caching/asm.s
generated
vendored
Normal file
0
vendor/github.com/bytedance/sonic/internal/caching/asm.s
generated
vendored
Normal file
115
vendor/github.com/bytedance/sonic/internal/caching/fcache.go
generated
vendored
Normal file
115
vendor/github.com/bytedance/sonic/internal/caching/fcache.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package caching
|
||||
|
||||
import (
|
||||
`strings`
|
||||
`unsafe`
|
||||
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
)
|
||||
|
||||
type FieldMap struct {
|
||||
N uint64
|
||||
b unsafe.Pointer
|
||||
m map[string]int
|
||||
}
|
||||
|
||||
type FieldEntry struct {
|
||||
ID int
|
||||
Name string
|
||||
Hash uint64
|
||||
}
|
||||
|
||||
const (
|
||||
FieldMap_N = int64(unsafe.Offsetof(FieldMap{}.N))
|
||||
FieldMap_b = int64(unsafe.Offsetof(FieldMap{}.b))
|
||||
FieldEntrySize = int64(unsafe.Sizeof(FieldEntry{}))
|
||||
)
|
||||
|
||||
func newBucket(n int) unsafe.Pointer {
|
||||
v := make([]FieldEntry, n)
|
||||
return (*rt.GoSlice)(unsafe.Pointer(&v)).Ptr
|
||||
}
|
||||
|
||||
func CreateFieldMap(n int) *FieldMap {
|
||||
return &FieldMap {
|
||||
N: uint64(n * 2),
|
||||
b: newBucket(n * 2), // LoadFactor = 0.5
|
||||
m: make(map[string]int, n * 2),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FieldMap) At(p uint64) *FieldEntry {
|
||||
off := uintptr(p) * uintptr(FieldEntrySize)
|
||||
return (*FieldEntry)(unsafe.Pointer(uintptr(self.b) + off))
|
||||
}
|
||||
|
||||
// Get searches FieldMap by name. JIT generated assembly does NOT call this
|
||||
// function, rather it implements its own version directly in assembly. So
|
||||
// we must ensure this function stays in sync with the JIT generated one.
|
||||
func (self *FieldMap) Get(name string) int {
|
||||
h := StrHash(name)
|
||||
p := h % self.N
|
||||
s := self.At(p)
|
||||
|
||||
/* find the element;
|
||||
* the hash map is never full, so the loop will always terminate */
|
||||
for s.Hash != 0 {
|
||||
if s.Hash == h && s.Name == name {
|
||||
return s.ID
|
||||
} else {
|
||||
p = (p + 1) % self.N
|
||||
s = self.At(p)
|
||||
}
|
||||
}
|
||||
|
||||
/* not found */
|
||||
return -1
|
||||
}
|
||||
|
||||
func (self *FieldMap) Set(name string, i int) {
|
||||
h := StrHash(name)
|
||||
p := h % self.N
|
||||
s := self.At(p)
|
||||
|
||||
/* searching for an empty slot;
|
||||
* the hash map is never full, so the loop will always terminate */
|
||||
for s.Hash != 0 {
|
||||
p = (p + 1) % self.N
|
||||
s = self.At(p)
|
||||
}
|
||||
|
||||
/* set the value */
|
||||
s.ID = i
|
||||
s.Hash = h
|
||||
s.Name = name
|
||||
|
||||
/* add the case-insensitive version, prefer the one with smaller field ID */
|
||||
key := strings.ToLower(name)
|
||||
if v, ok := self.m[key]; !ok || i < v {
|
||||
self.m[key] = i
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FieldMap) GetCaseInsensitive(name string) int {
|
||||
if i, ok := self.m[strings.ToLower(name)]; ok {
|
||||
return i
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/bytedance/sonic/internal/caching/hashing.go
generated
vendored
Normal file
36
vendor/github.com/bytedance/sonic/internal/caching/hashing.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package caching
|
||||
|
||||
import (
|
||||
`unsafe`
|
||||
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
)
|
||||
|
||||
var (
|
||||
V_strhash = rt.UnpackEface(rt.Strhash)
|
||||
S_strhash = *(*uintptr)(V_strhash.Value)
|
||||
)
|
||||
|
||||
func StrHash(s string) uint64 {
|
||||
if v := rt.Strhash(unsafe.Pointer(&s), 0); v == 0 {
|
||||
return 1
|
||||
} else {
|
||||
return uint64(v)
|
||||
}
|
||||
}
|
||||
179
vendor/github.com/bytedance/sonic/internal/caching/pcache.go
generated
vendored
Normal file
179
vendor/github.com/bytedance/sonic/internal/caching/pcache.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package caching
|
||||
|
||||
import (
|
||||
`sync`
|
||||
`sync/atomic`
|
||||
`unsafe`
|
||||
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
)
|
||||
|
||||
/** Program Map **/
|
||||
|
||||
const (
|
||||
_LoadFactor = 0.5
|
||||
_InitCapacity = 4096 // must be a power of 2
|
||||
)
|
||||
|
||||
type _ProgramMap struct {
|
||||
n uint64
|
||||
m uint32
|
||||
b []_ProgramEntry
|
||||
}
|
||||
|
||||
type _ProgramEntry struct {
|
||||
vt *rt.GoType
|
||||
fn interface{}
|
||||
}
|
||||
|
||||
func newProgramMap() *_ProgramMap {
|
||||
return &_ProgramMap {
|
||||
n: 0,
|
||||
m: _InitCapacity - 1,
|
||||
b: make([]_ProgramEntry, _InitCapacity),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_ProgramMap) copy() *_ProgramMap {
|
||||
fork := &_ProgramMap{
|
||||
n: self.n,
|
||||
m: self.m,
|
||||
b: make([]_ProgramEntry, len(self.b)),
|
||||
}
|
||||
for i, f := range self.b {
|
||||
fork.b[i] = f
|
||||
}
|
||||
return fork
|
||||
}
|
||||
|
||||
func (self *_ProgramMap) get(vt *rt.GoType) interface{} {
|
||||
i := self.m + 1
|
||||
p := vt.Hash & self.m
|
||||
|
||||
/* linear probing */
|
||||
for ; i > 0; i-- {
|
||||
if b := self.b[p]; b.vt == vt {
|
||||
return b.fn
|
||||
} else if b.vt == nil {
|
||||
break
|
||||
} else {
|
||||
p = (p + 1) & self.m
|
||||
}
|
||||
}
|
||||
|
||||
/* not found */
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *_ProgramMap) add(vt *rt.GoType, fn interface{}) *_ProgramMap {
|
||||
p := self.copy()
|
||||
f := float64(atomic.LoadUint64(&p.n) + 1) / float64(p.m + 1)
|
||||
|
||||
/* check for load factor */
|
||||
if f > _LoadFactor {
|
||||
p = p.rehash()
|
||||
}
|
||||
|
||||
/* insert the value */
|
||||
p.insert(vt, fn)
|
||||
return p
|
||||
}
|
||||
|
||||
func (self *_ProgramMap) rehash() *_ProgramMap {
|
||||
c := (self.m + 1) << 1
|
||||
r := &_ProgramMap{m: c - 1, b: make([]_ProgramEntry, int(c))}
|
||||
|
||||
/* rehash every entry */
|
||||
for i := uint32(0); i <= self.m; i++ {
|
||||
if b := self.b[i]; b.vt != nil {
|
||||
r.insert(b.vt, b.fn)
|
||||
}
|
||||
}
|
||||
|
||||
/* rebuild successful */
|
||||
return r
|
||||
}
|
||||
|
||||
func (self *_ProgramMap) insert(vt *rt.GoType, fn interface{}) {
|
||||
h := vt.Hash
|
||||
p := h & self.m
|
||||
|
||||
/* linear probing */
|
||||
for i := uint32(0); i <= self.m; i++ {
|
||||
if b := &self.b[p]; b.vt != nil {
|
||||
p += 1
|
||||
p &= self.m
|
||||
} else {
|
||||
b.vt = vt
|
||||
b.fn = fn
|
||||
atomic.AddUint64(&self.n, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/* should never happens */
|
||||
panic("no available slots")
|
||||
}
|
||||
|
||||
/** RCU Program Cache **/
|
||||
|
||||
type ProgramCache struct {
|
||||
m sync.Mutex
|
||||
p unsafe.Pointer
|
||||
}
|
||||
|
||||
func CreateProgramCache() *ProgramCache {
|
||||
return &ProgramCache {
|
||||
m: sync.Mutex{},
|
||||
p: unsafe.Pointer(newProgramMap()),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ProgramCache) Reset() {
|
||||
self.m.Lock()
|
||||
defer self.m.Unlock()
|
||||
self.p = unsafe.Pointer(newProgramMap())
|
||||
}
|
||||
|
||||
func (self *ProgramCache) Get(vt *rt.GoType) interface{} {
|
||||
return (*_ProgramMap)(atomic.LoadPointer(&self.p)).get(vt)
|
||||
}
|
||||
|
||||
func (self *ProgramCache) Compute(vt *rt.GoType, compute func(*rt.GoType, ... interface{}) (interface{}, error), ex ...interface{}) (interface{}, error) {
|
||||
var err error
|
||||
var val interface{}
|
||||
|
||||
/* use defer to prevent inlining of this function */
|
||||
self.m.Lock()
|
||||
defer self.m.Unlock()
|
||||
|
||||
/* double check with write lock held */
|
||||
if val = self.Get(vt); val != nil {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
/* compute the value */
|
||||
if val, err = compute(vt, ex...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/* update the RCU cache */
|
||||
atomic.StorePointer(&self.p, unsafe.Pointer((*_ProgramMap)(atomic.LoadPointer(&self.p)).add(vt, val)))
|
||||
return val, nil
|
||||
}
|
||||
12
vendor/github.com/bytedance/sonic/internal/compat/warn.go
generated
vendored
Normal file
12
vendor/github.com/bytedance/sonic/internal/compat/warn.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20
|
||||
|
||||
package compat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Warn(prefix string) {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: %s only supports (go1.17~1.26 and amd64 CPU) or (go1.20~1.26 and arm64 CPU), but your environment is not suitable and will fallback to encoding/json\n", prefix)
|
||||
}
|
||||
40
vendor/github.com/bytedance/sonic/internal/cpu/features.go
generated
vendored
Normal file
40
vendor/github.com/bytedance/sonic/internal/cpu/features.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`os`
|
||||
|
||||
`github.com/klauspost/cpuid/v2`
|
||||
)
|
||||
|
||||
var (
|
||||
HasAVX2 = cpuid.CPU.Has(cpuid.AVX2)
|
||||
HasSSE = cpuid.CPU.Has(cpuid.SSE)
|
||||
)
|
||||
|
||||
func init() {
|
||||
switch v := os.Getenv("SONIC_MODE"); v {
|
||||
case "" : break
|
||||
case "auto" : break
|
||||
case "noavx" : HasAVX2 = false
|
||||
// will also disable avx, act as `noavx`, we remain it to make sure forward compatibility
|
||||
case "noavx2" : HasAVX2 = false
|
||||
default : panic(fmt.Sprintf("invalid mode: '%s', should be one of 'auto', 'noavx', 'noavx2'", v))
|
||||
}
|
||||
}
|
||||
175
vendor/github.com/bytedance/sonic/internal/decoder/api/decoder.go
generated
vendored
Normal file
175
vendor/github.com/bytedance/sonic/internal/decoder/api/decoder.go
generated
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
`reflect`
|
||||
|
||||
`github.com/bytedance/sonic/internal/native`
|
||||
`github.com/bytedance/sonic/internal/native/types`
|
||||
`github.com/bytedance/sonic/internal/decoder/consts`
|
||||
`github.com/bytedance/sonic/internal/decoder/errors`
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
`github.com/bytedance/sonic/option`
|
||||
)
|
||||
|
||||
const (
|
||||
_F_allow_control = consts.F_allow_control
|
||||
_F_copy_string = consts.F_copy_string
|
||||
_F_disable_unknown = consts.F_disable_unknown
|
||||
_F_disable_urc = consts.F_disable_urc
|
||||
_F_use_int64 = consts.F_use_int64
|
||||
_F_use_number = consts.F_use_number
|
||||
_F_validate_string = consts.F_validate_string
|
||||
_F_case_sensitive = consts.F_case_sensitive
|
||||
|
||||
_MaxStack = consts.MaxStack
|
||||
|
||||
OptionUseInt64 = consts.OptionUseInt64
|
||||
OptionUseNumber = consts.OptionUseNumber
|
||||
OptionUseUnicodeErrors = consts.OptionUseUnicodeErrors
|
||||
OptionDisableUnknown = consts.OptionDisableUnknown
|
||||
OptionCopyString = consts.OptionCopyString
|
||||
OptionValidateString = consts.OptionValidateString
|
||||
OptionNoValidateJSON = consts.OptionNoValidateJSON
|
||||
OptionCaseSensitive = consts.OptionCaseSensitive
|
||||
)
|
||||
|
||||
type (
|
||||
Options = consts.Options
|
||||
MismatchTypeError = errors.MismatchTypeError
|
||||
SyntaxError = errors.SyntaxError
|
||||
)
|
||||
|
||||
func (self *Decoder) SetOptions(opts Options) {
|
||||
if (opts & consts.OptionUseNumber != 0) && (opts & consts.OptionUseInt64 != 0) {
|
||||
panic("can't set OptionUseInt64 and OptionUseNumber both!")
|
||||
}
|
||||
self.f = uint64(opts)
|
||||
}
|
||||
|
||||
// Decoder is the decoder context object
|
||||
type Decoder struct {
|
||||
i int
|
||||
f uint64
|
||||
s string
|
||||
}
|
||||
|
||||
// NewDecoder creates a new decoder instance.
|
||||
func NewDecoder(s string) *Decoder {
|
||||
return &Decoder{s: s}
|
||||
}
|
||||
|
||||
// Pos returns the current decoding position.
|
||||
func (self *Decoder) Pos() int {
|
||||
return self.i
|
||||
}
|
||||
|
||||
func (self *Decoder) Reset(s string) {
|
||||
self.s = s
|
||||
self.i = 0
|
||||
// self.f = 0
|
||||
}
|
||||
|
||||
func (self *Decoder) CheckTrailings() error {
|
||||
pos := self.i
|
||||
buf := self.s
|
||||
/* skip all the trailing spaces */
|
||||
if pos != len(buf) {
|
||||
for pos < len(buf) && (types.SPACE_MASK & (1 << buf[pos])) != 0 {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
/* then it must be at EOF */
|
||||
if pos == len(buf) {
|
||||
return nil
|
||||
}
|
||||
|
||||
/* junk after JSON value */
|
||||
return SyntaxError {
|
||||
Src : buf,
|
||||
Pos : pos,
|
||||
Code : types.ERR_INVALID_CHAR,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Decode parses the JSON-encoded data from current position and stores the result
|
||||
// in the value pointed to by val.
|
||||
func (self *Decoder) Decode(val interface{}) error {
|
||||
return decodeImpl(&self.s, &self.i, self.f, val)
|
||||
}
|
||||
|
||||
// UseInt64 indicates the Decoder to unmarshal an integer into an interface{} as an
|
||||
// int64 instead of as a float64.
|
||||
func (self *Decoder) UseInt64() {
|
||||
self.f |= 1 << _F_use_int64
|
||||
self.f &^= 1 << _F_use_number
|
||||
}
|
||||
|
||||
// UseNumber indicates the Decoder to unmarshal a number into an interface{} as a
|
||||
// json.Number instead of as a float64.
|
||||
func (self *Decoder) UseNumber() {
|
||||
self.f &^= 1 << _F_use_int64
|
||||
self.f |= 1 << _F_use_number
|
||||
}
|
||||
|
||||
// UseUnicodeErrors indicates the Decoder to return an error when encounter invalid
|
||||
// UTF-8 escape sequences.
|
||||
func (self *Decoder) UseUnicodeErrors() {
|
||||
self.f |= 1 << _F_disable_urc
|
||||
}
|
||||
|
||||
// DisallowUnknownFields indicates the Decoder to return an error when the destination
|
||||
// is a struct and the input contains object keys which do not match any
|
||||
// non-ignored, exported fields in the destination.
|
||||
func (self *Decoder) DisallowUnknownFields() {
|
||||
self.f |= 1 << _F_disable_unknown
|
||||
}
|
||||
|
||||
// CopyString indicates the Decoder to decode string values by copying instead of referring.
|
||||
func (self *Decoder) CopyString() {
|
||||
self.f |= 1 << _F_copy_string
|
||||
}
|
||||
|
||||
// ValidateString causes the Decoder to validate string values when decoding string value
|
||||
// in JSON. Validation is that, returning error when unescaped control chars(0x00-0x1f) or
|
||||
// invalid UTF-8 chars in the string value of JSON.
|
||||
func (self *Decoder) ValidateString() {
|
||||
self.f |= 1 << _F_validate_string
|
||||
}
|
||||
|
||||
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
|
||||
// order to reduce the first-hit latency.
|
||||
//
|
||||
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
|
||||
// a compile option to set the depth of recursive compile for the nested struct type.
|
||||
func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
|
||||
return pretouchImpl(vt, opts...)
|
||||
}
|
||||
|
||||
// Skip skips only one json value, and returns first non-blank character position and its ending position if it is valid.
|
||||
// Otherwise, returns negative error code using start and invalid character position using end
|
||||
func Skip(data []byte) (start int, end int) {
|
||||
s := rt.Mem2Str(data)
|
||||
p := 0
|
||||
m := types.NewStateMachine()
|
||||
ret := native.SkipOne(&s, &p, m, uint64(0))
|
||||
types.FreeStateMachine(m)
|
||||
return ret, p
|
||||
}
|
||||
38
vendor/github.com/bytedance/sonic/internal/decoder/api/decoder_amd64.go
generated
vendored
Normal file
38
vendor/github.com/bytedance/sonic/internal/decoder/api/decoder_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build go1.17 && !go1.27
|
||||
// +build go1.17,!go1.27
|
||||
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/bytedance/sonic/internal/envs"
|
||||
"github.com/bytedance/sonic/internal/decoder/jitdec"
|
||||
"github.com/bytedance/sonic/internal/decoder/optdec"
|
||||
)
|
||||
|
||||
var (
|
||||
pretouchImpl = jitdec.Pretouch
|
||||
decodeImpl = jitdec.Decode
|
||||
)
|
||||
|
||||
func init() {
|
||||
if envs.UseOptDec {
|
||||
pretouchImpl = optdec.Pretouch
|
||||
decodeImpl = optdec.Decode
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/bytedance/sonic/internal/decoder/api/decoder_arm64.go
generated
vendored
Normal file
38
vendor/github.com/bytedance/sonic/internal/decoder/api/decoder_arm64.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// +build go1.17,!go1.27
|
||||
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
`github.com/bytedance/sonic/internal/decoder/optdec`
|
||||
`github.com/bytedance/sonic/internal/envs`
|
||||
)
|
||||
|
||||
var (
|
||||
pretouchImpl = optdec.Pretouch
|
||||
decodeImpl = optdec.Decode
|
||||
)
|
||||
|
||||
|
||||
func init() {
|
||||
// when in aarch64, we enable all optimization
|
||||
envs.EnableOptDec()
|
||||
envs.EnableFastMap()
|
||||
}
|
||||
|
||||
|
||||
247
vendor/github.com/bytedance/sonic/internal/decoder/api/stream.go
generated
vendored
Normal file
247
vendor/github.com/bytedance/sonic/internal/decoder/api/stream.go
generated
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/bytedance/sonic/internal/native"
|
||||
"github.com/bytedance/sonic/internal/native/types"
|
||||
"github.com/bytedance/sonic/internal/rt"
|
||||
"github.com/bytedance/sonic/internal/utils"
|
||||
"github.com/bytedance/sonic/option"
|
||||
)
|
||||
|
||||
var (
|
||||
minLeftBufferShift uint = 1
|
||||
)
|
||||
|
||||
// StreamDecoder is the decoder context object for streaming input.
|
||||
type StreamDecoder struct {
|
||||
r io.Reader
|
||||
buf []byte
|
||||
scanp int
|
||||
scanned int64
|
||||
err error
|
||||
Decoder
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func () interface{} {
|
||||
return make([]byte, 0, option.DefaultDecoderBufferSize)
|
||||
},
|
||||
}
|
||||
|
||||
func freeBytes(buf []byte) {
|
||||
if rt.CanSizeResue(cap(buf)) {
|
||||
bufPool.Put(buf[:0])
|
||||
}
|
||||
}
|
||||
|
||||
// NewStreamDecoder adapts to encoding/json.NewDecoder API.
|
||||
//
|
||||
// NewStreamDecoder returns a new decoder that reads from r.
|
||||
func NewStreamDecoder(r io.Reader) *StreamDecoder {
|
||||
return &StreamDecoder{r : r}
|
||||
}
|
||||
|
||||
// Decode decodes input stream into val with corresponding data.
|
||||
// Redundantly bytes may be read and left in its buffer, and can be used at next call.
|
||||
// Either io error from underlying io.Reader (except io.EOF)
|
||||
// or syntax error from data will be recorded and stop subsequently decoding.
|
||||
func (self *StreamDecoder) Decode(val interface{}) (err error) {
|
||||
// read more data into buf
|
||||
if self.More() {
|
||||
var s = self.scanp
|
||||
try_skip:
|
||||
var e = len(self.buf)
|
||||
var src = rt.Mem2Str(self.buf[s:e])
|
||||
// try skip
|
||||
var x = 0;
|
||||
if y := native.SkipOneFast(&src, &x); y < 0 {
|
||||
if self.readMore() {
|
||||
goto try_skip
|
||||
}
|
||||
if self.err == nil {
|
||||
self.err = SyntaxError{e, self.s, types.ParsingError(-s), ""}
|
||||
self.setErr(self.err)
|
||||
}
|
||||
return self.err
|
||||
} else {
|
||||
s = y + s
|
||||
e = x + s
|
||||
}
|
||||
|
||||
// must copy string here for safety
|
||||
self.Decoder.Reset(string(self.buf[s:e]))
|
||||
err = self.Decoder.Decode(val)
|
||||
if err != nil {
|
||||
self.setErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
self.scanp = e
|
||||
_, empty := self.scan()
|
||||
if empty {
|
||||
// no remain valid bytes, thus we just recycle buffer
|
||||
mem := self.buf
|
||||
self.buf = nil
|
||||
freeBytes(mem)
|
||||
} else {
|
||||
// remain undecoded bytes, move them onto head
|
||||
n := copy(self.buf, self.buf[self.scanp:])
|
||||
self.buf = self.buf[:n]
|
||||
}
|
||||
|
||||
self.scanned += int64(self.scanp)
|
||||
self.scanp = 0
|
||||
}
|
||||
|
||||
return self.err
|
||||
}
|
||||
|
||||
// InputOffset returns the input stream byte offset of the current decoder position.
|
||||
// The offset gives the location of the end of the most recently returned token and the beginning of the next token.
|
||||
func (self *StreamDecoder) InputOffset() int64 {
|
||||
return self.scanned + int64(self.scanp)
|
||||
}
|
||||
|
||||
// Buffered returns a reader of the data remaining in the Decoder's buffer.
|
||||
// The reader is valid until the next call to Decode.
|
||||
func (self *StreamDecoder) Buffered() io.Reader {
|
||||
return bytes.NewReader(self.buf[self.scanp:])
|
||||
}
|
||||
|
||||
// More reports whether there is another element in the
|
||||
// current array or object being parsed.
|
||||
func (self *StreamDecoder) More() bool {
|
||||
if self.err != nil {
|
||||
return false
|
||||
}
|
||||
c, err := self.peek()
|
||||
return err == nil && c != ']' && c != '}'
|
||||
}
|
||||
|
||||
// More reports whether there is another element in the
|
||||
// current array or object being parsed.
|
||||
func (self *StreamDecoder) readMore() bool {
|
||||
if self.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var err error
|
||||
var n int
|
||||
for {
|
||||
// Grow buffer if not large enough.
|
||||
l := len(self.buf)
|
||||
realloc(&self.buf)
|
||||
|
||||
n, err = self.r.Read(self.buf[l:cap(self.buf)])
|
||||
self.buf = self.buf[: l+n]
|
||||
|
||||
self.scanp = l
|
||||
_, empty := self.scan()
|
||||
if !empty {
|
||||
return true
|
||||
}
|
||||
|
||||
// buffer has been scanned, now report any error
|
||||
if err != nil {
|
||||
self.setErr(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StreamDecoder) setErr(err error) {
|
||||
self.err = err
|
||||
mem := self.buf[:0]
|
||||
self.buf = nil
|
||||
freeBytes(mem)
|
||||
}
|
||||
|
||||
func (self *StreamDecoder) peek() (byte, error) {
|
||||
var err error
|
||||
for {
|
||||
c, empty := self.scan()
|
||||
if !empty {
|
||||
return byte(c), nil
|
||||
}
|
||||
// buffer has been scanned, now report any error
|
||||
if err != nil {
|
||||
self.setErr(err)
|
||||
return 0, err
|
||||
}
|
||||
err = self.refill()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StreamDecoder) scan() (byte, bool) {
|
||||
for i := self.scanp; i < len(self.buf); i++ {
|
||||
c := self.buf[i]
|
||||
if utils.IsSpace(c) {
|
||||
continue
|
||||
}
|
||||
self.scanp = i
|
||||
return c, false
|
||||
}
|
||||
return 0, true
|
||||
}
|
||||
|
||||
|
||||
func (self *StreamDecoder) refill() error {
|
||||
// Make room to read more into the buffer.
|
||||
// First slide down data already consumed.
|
||||
if self.scanp > 0 {
|
||||
self.scanned += int64(self.scanp)
|
||||
n := copy(self.buf, self.buf[self.scanp:])
|
||||
self.buf = self.buf[:n]
|
||||
self.scanp = 0
|
||||
}
|
||||
|
||||
// Grow buffer if not large enough.
|
||||
realloc(&self.buf)
|
||||
|
||||
// Read. Delay error for next iteration (after scan).
|
||||
n, err := self.r.Read(self.buf[len(self.buf):cap(self.buf)])
|
||||
self.buf = self.buf[0 : len(self.buf)+n]
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func realloc(buf *[]byte) bool {
|
||||
l := uint(len(*buf))
|
||||
c := uint(cap(*buf))
|
||||
if c == 0 {
|
||||
*buf = bufPool.Get().([]byte)
|
||||
return true
|
||||
}
|
||||
if c - l <= c >> minLeftBufferShift {
|
||||
e := l+(l>>minLeftBufferShift)
|
||||
if e <= c {
|
||||
e = c*2
|
||||
}
|
||||
tmp := make([]byte, l, e)
|
||||
copy(tmp, *buf)
|
||||
*buf = tmp
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
37
vendor/github.com/bytedance/sonic/internal/decoder/consts/option.go
generated
vendored
Normal file
37
vendor/github.com/bytedance/sonic/internal/decoder/consts/option.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
package consts
|
||||
|
||||
import (
|
||||
`github.com/bytedance/sonic/internal/native/types`
|
||||
)
|
||||
|
||||
|
||||
const (
|
||||
F_use_int64 = 0
|
||||
F_disable_urc = 2
|
||||
F_disable_unknown = 3
|
||||
F_copy_string = 4
|
||||
|
||||
F_use_number = types.B_USE_NUMBER
|
||||
F_validate_string = types.B_VALIDATE_STRING
|
||||
F_allow_control = types.B_ALLOW_CONTROL
|
||||
F_no_validate_json = types.B_NO_VALIDATE_JSON
|
||||
F_case_sensitive = 7
|
||||
)
|
||||
|
||||
type Options uint64
|
||||
|
||||
const (
|
||||
OptionUseInt64 Options = 1 << F_use_int64
|
||||
OptionUseNumber Options = 1 << F_use_number
|
||||
OptionUseUnicodeErrors Options = 1 << F_disable_urc
|
||||
OptionDisableUnknown Options = 1 << F_disable_unknown
|
||||
OptionCopyString Options = 1 << F_copy_string
|
||||
OptionValidateString Options = 1 << F_validate_string
|
||||
OptionNoValidateJSON Options = 1 << F_no_validate_json
|
||||
OptionCaseSensitive Options = 1 << F_case_sensitive
|
||||
)
|
||||
|
||||
const (
|
||||
MaxStack = 4096
|
||||
)
|
||||
191
vendor/github.com/bytedance/sonic/internal/decoder/errors/errors.go
generated
vendored
Normal file
191
vendor/github.com/bytedance/sonic/internal/decoder/errors/errors.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
`encoding/json`
|
||||
`errors`
|
||||
`fmt`
|
||||
`reflect`
|
||||
`strconv`
|
||||
`strings`
|
||||
|
||||
`github.com/bytedance/sonic/internal/native/types`
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
)
|
||||
|
||||
type SyntaxError struct {
|
||||
Pos int
|
||||
Src string
|
||||
Code types.ParsingError
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (self SyntaxError) Error() string {
|
||||
return fmt.Sprintf("%q", self.Description())
|
||||
}
|
||||
|
||||
func (self SyntaxError) Description() string {
|
||||
return "Syntax error " + self.description()
|
||||
}
|
||||
|
||||
func (self SyntaxError) description() string {
|
||||
/* check for empty source */
|
||||
if self.Src == "" {
|
||||
return fmt.Sprintf("no sources available, the input json is empty: %#v", self)
|
||||
}
|
||||
|
||||
p, x, q, y := calcBounds(len(self.Src), self.Pos)
|
||||
|
||||
/* compose the error description */
|
||||
return fmt.Sprintf(
|
||||
"at index %d: %s\n\n\t%s\n\t%s^%s\n",
|
||||
self.Pos,
|
||||
self.Message(),
|
||||
self.Src[p:q],
|
||||
strings.Repeat(".", x),
|
||||
strings.Repeat(".", y),
|
||||
)
|
||||
}
|
||||
|
||||
func calcBounds(size int, pos int) (lbound int, lwidth int, rbound int, rwidth int) {
|
||||
if pos >= size || pos < 0 {
|
||||
return 0, 0, size, 0
|
||||
}
|
||||
|
||||
i := 16
|
||||
lbound = pos - i
|
||||
rbound = pos + i
|
||||
|
||||
/* prevent slicing before the beginning */
|
||||
if lbound < 0 {
|
||||
lbound, rbound, i = 0, rbound - lbound, i + lbound
|
||||
}
|
||||
|
||||
/* prevent slicing beyond the end */
|
||||
if n := size; rbound > n {
|
||||
n = rbound - n
|
||||
rbound = size
|
||||
|
||||
/* move the left bound if possible */
|
||||
if lbound > n {
|
||||
i += n
|
||||
lbound -= n
|
||||
}
|
||||
}
|
||||
|
||||
/* left and right length */
|
||||
lwidth = clamp_zero(i)
|
||||
rwidth = clamp_zero(rbound - lbound - i - 1)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self SyntaxError) Message() string {
|
||||
if self.Msg == "" {
|
||||
return self.Code.Message()
|
||||
}
|
||||
return self.Msg
|
||||
}
|
||||
|
||||
func clamp_zero(v int) int {
|
||||
if v < 0 {
|
||||
return 0
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
/** JIT Error Helpers **/
|
||||
|
||||
var StackOverflow = &json.UnsupportedValueError {
|
||||
Str : "Value nesting too deep",
|
||||
Value : reflect.ValueOf("..."),
|
||||
}
|
||||
|
||||
func ErrorWrap(src string, pos int, code types.ParsingError) error {
|
||||
return *error_wrap_heap(src, pos, code)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func error_wrap_heap(src string, pos int, code types.ParsingError) *SyntaxError {
|
||||
return &SyntaxError {
|
||||
Pos : pos,
|
||||
Src : src,
|
||||
Code : code,
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorType(vt *rt.GoType) error {
|
||||
return &json.UnmarshalTypeError{Type: vt.Pack()}
|
||||
}
|
||||
|
||||
type MismatchTypeError struct {
|
||||
Pos int
|
||||
Src string
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func swithchJSONType (src string, pos int) string {
|
||||
var val string
|
||||
switch src[pos] {
|
||||
case 'f': fallthrough
|
||||
case 't': val = "bool"
|
||||
case '"': val = "string"
|
||||
case '{': val = "object"
|
||||
case '[': val = "array"
|
||||
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': val = "number"
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (self MismatchTypeError) Error() string {
|
||||
se := SyntaxError {
|
||||
Pos : self.Pos,
|
||||
Src : self.Src,
|
||||
Code : types.ERR_MISMATCH,
|
||||
}
|
||||
return fmt.Sprintf("Mismatch type %s with value %s %q", self.Type.String(), swithchJSONType(self.Src, self.Pos), se.description())
|
||||
}
|
||||
|
||||
func (self MismatchTypeError) Description() string {
|
||||
se := SyntaxError {
|
||||
Pos : self.Pos,
|
||||
Src : self.Src,
|
||||
Code : types.ERR_MISMATCH,
|
||||
}
|
||||
return fmt.Sprintf("Mismatch type %s with value %s %s", self.Type.String(), swithchJSONType(self.Src, self.Pos), se.description())
|
||||
}
|
||||
|
||||
func ErrorMismatch(src string, pos int, vt *rt.GoType) error {
|
||||
return &MismatchTypeError {
|
||||
Pos : pos,
|
||||
Src : src,
|
||||
Type : vt.Pack(),
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorField(name string) error {
|
||||
return errors.New("json: unknown field " + strconv.Quote(name))
|
||||
}
|
||||
|
||||
func ErrorValue(value string, vtype reflect.Type) error {
|
||||
return &json.UnmarshalTypeError {
|
||||
Type : vtype,
|
||||
Value : value,
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user