phone
into V20220630
@@ -22,14 +22,21 @@ require ( | |||
github.com/PuerkitoBio/goquery v1.5.0 | |||
github.com/RichardKnop/machinery v1.6.9 | |||
github.com/RoaringBitmap/roaring v0.4.23 // indirect | |||
github.com/alibabacloud-go/darabonba-openapi v0.1.18 | |||
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9 | |||
github.com/alibabacloud-go/tea v1.1.17 | |||
github.com/alibabacloud-go/tea-utils v1.4.3 | |||
github.com/alibabacloud-go/tea-xml v1.1.2 // indirect | |||
github.com/bgentry/speakeasy v0.1.0 // indirect | |||
github.com/blevesearch/bleve v1.0.7 | |||
github.com/clbanning/mxj/v2 v2.5.5 // indirect | |||
github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 // indirect | |||
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect | |||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect | |||
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect | |||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc | |||
github.com/dgrijalva/jwt-go v3.2.0+incompatible | |||
github.com/disintegration/imaging v1.6.2 | |||
github.com/dustin/go-humanize v1.0.0 | |||
github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | |||
github.com/elliotchance/orderedmap v1.4.0 | |||
@@ -56,6 +63,7 @@ require ( | |||
github.com/golang/protobuf v1.4.1 // indirect | |||
github.com/gomodule/redigo v2.0.0+incompatible | |||
github.com/google/go-github/v24 v24.0.1 | |||
github.com/google/uuid v1.1.1 | |||
github.com/gorilla/context v1.1.1 | |||
github.com/gorilla/websocket v1.4.0 | |||
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect | |||
@@ -112,9 +120,9 @@ require ( | |||
github.com/urfave/cli v1.22.1 | |||
github.com/xanzy/go-gitlab v0.31.0 | |||
github.com/yohcop/openid-go v1.0.0 | |||
github.com/yuin/goldmark v1.1.27 | |||
github.com/yuin/goldmark v1.1.30 | |||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | |||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 | |||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 | |||
golang.org/x/mod v0.3.0 // indirect | |||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120 | |||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | |||
@@ -126,7 +134,7 @@ require ( | |||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect | |||
gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect | |||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df | |||
gopkg.in/ini.v1 v1.52.0 | |||
gopkg.in/ini.v1 v1.56.0 | |||
gopkg.in/ldap.v3 v3.0.2 | |||
gopkg.in/macaron.v1 v1.3.9 // indirect | |||
gopkg.in/testfixtures.v2 v2.5.0 | |||
@@ -78,6 +78,35 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBb | |||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= | |||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | |||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | |||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= | |||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= | |||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= | |||
github.com/alibabacloud-go/darabonba-openapi v0.1.14/go.mod h1:w4CosR7O/kapCtEEMBm3JsQqWBU/CnZ2o0pHorsTWDI= | |||
github.com/alibabacloud-go/darabonba-openapi v0.1.18 h1:3eUVmAr7WCJp7fgIvmCd9ZUyuwtJYbtUqJIed5eXCmk= | |||
github.com/alibabacloud-go/darabonba-openapi v0.1.18/go.mod h1:PB4HffMhJVmAgNKNq3wYbTUlFvPgxJpTzd1F5pTuUsc= | |||
github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= | |||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50= | |||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= | |||
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9 h1:z+OU7LbWtQitWJ8SAn55hEQkJPCsEPJc97TvGCZV+4s= | |||
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9/go.mod h1:AT91gCNJPsemf4lHLNgWTf/RsgmpdOprWvQ3FYvtwGk= | |||
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= | |||
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= | |||
github.com/alibabacloud-go/openapi-util v0.0.10/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= | |||
github.com/alibabacloud-go/openapi-util v0.0.11 h1:iYnqOPR5hyEEnNZmebGyRMkkEJRWUEjDiiaOHZ5aNhA= | |||
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= | |||
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= | |||
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= | |||
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= | |||
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= | |||
github.com/alibabacloud-go/tea v1.1.17 h1:05R5DnaJXe9sCNIe8KUgWHC/z6w/VZIwczgUwzRnul8= | |||
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= | |||
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= | |||
github.com/alibabacloud-go/tea-utils v1.4.3 h1:8SzwmmRrOnQ09Hf5a9GyfJc0d7Sjv6fmsZoF4UDbFjo= | |||
github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= | |||
github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M= | |||
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= | |||
github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY= | |||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= | |||
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= | |||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= | |||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= | |||
@@ -125,6 +154,8 @@ github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQ | |||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= | |||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU= | |||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE= | |||
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= | |||
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= | |||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | |||
github.com/corbym/gocrest v1.0.3 h1:gwEdq6RkTmq+09CTuM29DfKOCtZ7G7bcyxs3IZ6EVdU= | |||
github.com/corbym/gocrest v1.0.3/go.mod h1:maVFL5lbdS2PgfOQgGRWDYTeunSWQeiEgoNdTABShCs= | |||
@@ -170,6 +201,8 @@ github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xb | |||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | |||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | |||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= | |||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= | |||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= | |||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | |||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | |||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= | |||
@@ -369,6 +402,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORR | |||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | |||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= | |||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | |||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | |||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= | |||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= | |||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= | |||
@@ -432,6 +466,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV | |||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | |||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= | |||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | |||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= | |||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | |||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | |||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | |||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | |||
@@ -673,11 +709,13 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 | |||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | |||
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= | |||
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= | |||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= | |||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= | |||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= | |||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | |||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= | |||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | |||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | |||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= | |||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= | |||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= | |||
@@ -707,6 +745,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf | |||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | |||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= | |||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | |||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | |||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | |||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= | |||
@@ -722,6 +761,8 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV | |||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= | |||
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= | |||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= | |||
github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= | |||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= | |||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= | |||
github.com/toqueteos/trie v1.0.0 h1:8i6pXxNUXNRAqP246iibb7w/pSFquNTQ+uNfriG7vlk= | |||
github.com/toqueteos/trie v1.0.0/go.mod h1:Ywk48QhEqhU1+DwhMkJ2x7eeGxDHiGkAdc9+0DYcbsM= | |||
@@ -761,6 +802,8 @@ github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec | |||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
github.com/yuin/goldmark v1.1.27 h1:nqDD4MMMQA0lmWq03Z2/myGPYLQoXtmi0rGVs95ntbo= | |||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
github.com/yuin/goldmark v1.1.30 h1:j4d4Lw3zqZelDhBksEo3BnWg9xhXRQGJPPSL6OApZjI= | |||
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo= | |||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0= | |||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= | |||
@@ -800,14 +843,19 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U | |||
golang.org/x/crypto v0.0.0-20190907121410-71b5226ff739/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |||
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88= | |||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= | |||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | |||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | |||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | |||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0= | |||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | |||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= | |||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | |||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | |||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | |||
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | |||
@@ -848,6 +896,7 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL | |||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | |||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= | |||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | |||
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | |||
@@ -868,6 +917,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ | |||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= | |||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= | |||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
@@ -933,6 +984,7 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn | |||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |||
golang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | |||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= | |||
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | |||
golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 h1:vmsb6v0zUdmUlXfwKaYrHPPRCV0lHq/IwNIf0ASGjyQ= | |||
golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | |||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
@@ -1012,6 +1064,8 @@ gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= | |||
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4= | |||
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y= | |||
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
gopkg.in/ldap.v3 v3.0.2 h1:R6RBtabK6e1GO0eQKtkyOFbAHO73QesLzI2w2DZ6b9w= | |||
gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw= | |||
gopkg.in/macaron.v1 v1.3.9 h1:Dw+DDRYdXgQyEsPlfAfKz+UA5qVUrH3KPD7JhmZ9MFc= | |||
@@ -766,6 +766,14 @@ func UserSignIn(username, password string) (*User, error) { | |||
if err != nil { | |||
return nil, err | |||
} | |||
//email和用户名方式没找到,用手机号查找 | |||
if !hasUser { | |||
user = &User{PhoneNumber: strings.TrimSpace(username)} | |||
hasUser, err = x.Get(user) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
if hasUser { | |||
switch user.LoginType { | |||
@@ -184,6 +184,8 @@ type User struct { | |||
WechatOpenId string `xorm:"INDEX"` | |||
WechatBindUnix timeutil.TimeStamp | |||
//Mobile phone | |||
PhoneNumber string `xorm:"UNIQUE"` | |||
} | |||
// SearchOrganizationsOptions options to filter organizations | |||
@@ -1442,6 +1444,31 @@ func getUserByName(e Engine, name string) (*User, error) { | |||
return u, nil | |||
} | |||
func GetUserByPhoneNumber(phoneNumber string) (*User, error) { | |||
return getUserByPhoneNumber(x, phoneNumber) | |||
} | |||
func getUserByPhoneNumber(e Engine, phoneNumber string) (*User, error) { | |||
u := &User{PhoneNumber: phoneNumber} | |||
has, err := e.Get(u) | |||
if err != nil { | |||
return nil, err | |||
} else if !has { | |||
return nil, ErrUserNotExist{0, "", 0} | |||
} | |||
return u, nil | |||
} | |||
func IsUserByPhoneNumberExist(phoneNumber string) (bool, error) { | |||
return isUserByPhoneNumberExist(x, phoneNumber) | |||
} | |||
func isUserByPhoneNumberExist(e Engine, phoneNumber string) (bool, error) { | |||
return e.Where("phone_number = ?", phoneNumber).Exist(&User{}) | |||
} | |||
// GetUserEmailsByNames returns a list of e-mails corresponds to names of users | |||
// that have their email notifications set to enabled or onmention. | |||
func GetUserEmailsByNames(names []string) []string { | |||
@@ -1610,6 +1637,26 @@ func GetUserByEmail(email string) (*User, error) { | |||
return GetUserByEmailContext(DefaultDBContext(), email) | |||
} | |||
func GetUserByMainEmail(email string) (*User, error) { | |||
if len(email) == 0 { | |||
return nil, ErrUserNotExist{0, email, 0} | |||
} | |||
email = strings.ToLower(email) | |||
// First try to find the user by primary email | |||
user := &User{Email: email} | |||
has, err := DefaultDBContext().e.Get(user) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if has { | |||
return user, nil | |||
} else { | |||
return nil, ErrUserNotExist{0, email, 0} | |||
} | |||
} | |||
// GetUserByEmailContext returns the user object by given e-mail if exists with db context | |||
func GetUserByEmailContext(ctx DBContext, email string) (*User, error) { | |||
if len(email) == 0 { | |||
@@ -81,6 +81,8 @@ type RegisterForm struct { | |||
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"` | |||
Email string `binding:"Required;Email;MaxSize(254)"` | |||
Password string `binding:"MaxSize(255)"` | |||
PhoneNumber string `binding:"MaxSize(20)"` | |||
VerifyCode string `binding:"MaxSize(10)"` | |||
Retype string | |||
GRecaptchaResponse string `form:"g-recaptcha-response"` | |||
} | |||
@@ -209,6 +211,8 @@ type UpdateProfileForm struct { | |||
Location string `binding:"MaxSize(50)"` | |||
Language string `binding:"Size(5)"` | |||
Description string `binding:"MaxSize(255)"` | |||
PhoneNumber string `binding:"MaxSize(20)"` | |||
VerifyCode string `binding:"MaxSize(10)"` | |||
} | |||
// Validate validates the fields | |||
@@ -364,3 +368,43 @@ type U2FDeleteForm struct { | |||
func (f *U2FDeleteForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
return validate(errs, ctx.Data, f, ctx.Locale) | |||
} | |||
type PhoneNumberForm struct { | |||
PhoneNumber string `binding:"Required;MaxSize(20)"` | |||
Mode int `binding:"Required"` | |||
SlideID string `binding:"Required;MaxSize(100)"` | |||
} | |||
func (f *PhoneNumberForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
return validate(errs, ctx.Data, f, ctx.Locale) | |||
} | |||
type PhoneNumberCodeForm struct { | |||
PhoneNumber string `binding:"Required;MaxSize(20)"` | |||
VerifyCode string `binding:"Required;MaxSize(10)"` | |||
Remember bool | |||
} | |||
func (f *PhoneNumberCodeForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
return validate(errs, ctx.Data, f, ctx.Locale) | |||
} | |||
type ResetPassWordByPhoneForm struct { | |||
PhoneNumber string `binding:"Required;MaxSize(20)"` | |||
VerifyCode string `binding:"Required;MaxSize(10)"` | |||
Password string `binding:"MaxSize(255)"` | |||
Remember bool | |||
} | |||
func (f *ResetPassWordByPhoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
return validate(errs, ctx.Data, f, ctx.Locale) | |||
} | |||
type SlideImageForm struct { | |||
SlideID string `binding:"Required"` | |||
X int `binding:"Required"` | |||
} | |||
func (f *SlideImageForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
return validate(errs, ctx.Data, f, ctx.Locale) | |||
} |
@@ -53,6 +53,10 @@ func Toggle(options *ToggleOptions) macaron.Handler { | |||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") | |||
ctx.HTML(200, "user/auth/prohibit_login") | |||
return | |||
} else if setting.PhoneService.Enabled && ctx.User.IsActive && ctx.User.PhoneNumber == "" && ctx.Req.URL.Path != "/bindPhone" { | |||
ctx.Data["Title"] = ctx.Tr("phone.bind_phone") | |||
ctx.HTML(200, "user/auth/bind_phone") | |||
return | |||
} | |||
if ctx.User.MustChangePassword { | |||
@@ -0,0 +1,61 @@ | |||
package phone | |||
import ( | |||
"math" | |||
"math/rand" | |||
"regexp" | |||
"strconv" | |||
"time" | |||
"code.gitea.io/gitea/modules/setting" | |||
openapi "github.com/alibabacloud-go/darabonba-openapi/client" | |||
dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v2/client" | |||
util "github.com/alibabacloud-go/tea-utils/service" | |||
"github.com/alibabacloud-go/tea/tea" | |||
) | |||
func IsValidPhoneNumber(phoneNumber string) bool { | |||
pattern := "^1[3-9]\\d{9}$" | |||
match, _ := regexp.MatchString(pattern, phoneNumber) | |||
return match | |||
} | |||
func GenerateVerifyCode(n int) string { | |||
min := int(math.Pow10(n - 1)) | |||
max := int(math.Pow10(n)) | |||
rand.Seed(time.Now().UnixNano()) | |||
return strconv.Itoa(rand.Intn(max-min) + min) | |||
} | |||
func createClient(accessKeyId *string, accessKeySecret *string) (_result *dysmsapi20170525.Client, _err error) { | |||
config := &openapi.Config{ | |||
// 您的AccessKey ID | |||
AccessKeyId: accessKeyId, | |||
// 您的AccessKey Secret | |||
AccessKeySecret: accessKeySecret, | |||
} | |||
// 访问的域名 | |||
config.Endpoint = tea.String("dysmsapi.aliyuncs.com") | |||
_result = &dysmsapi20170525.Client{} | |||
_result, _err = dysmsapi20170525.NewClient(config) | |||
return _result, _err | |||
} | |||
func SendVerifyCode(phoneNumber string, verifyCode string) error { | |||
client, _err := createClient(&setting.PhoneService.AccessKeyId, &setting.PhoneService.AccessKeySecret) | |||
if _err != nil { | |||
return _err | |||
} | |||
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{ | |||
SignName: tea.String(setting.PhoneService.SignName), | |||
TemplateCode: tea.String(setting.PhoneService.TemplateCode), | |||
PhoneNumbers: tea.String(phoneNumber), | |||
TemplateParam: tea.String("{\"code\":\"" + verifyCode + "\"}"), | |||
} | |||
runtime := &util.RuntimeOptions{} | |||
_, _err = client.SendSmsWithOptions(sendSmsRequest, runtime) | |||
return _err | |||
} |
@@ -7,10 +7,53 @@ | |||
package public | |||
import ( | |||
"fmt" | |||
"io/ioutil" | |||
"os" | |||
"path" | |||
"code.gitea.io/gitea/modules/setting" | |||
"gitea.com/macaron/macaron" | |||
"github.com/unknwon/com" | |||
) | |||
// Static implements the macaron static handler for serving assets. | |||
func Static(opts *Options) macaron.Handler { | |||
return opts.staticHandler(opts.Directory) | |||
} | |||
func Dir(name string) ([]string, error) { | |||
var ( | |||
result []string | |||
) | |||
staticDir := path.Join(setting.StaticRootPath, "public", name) | |||
if com.IsDir(staticDir) { | |||
files, err := com.StatDir(staticDir, true) | |||
if err != nil { | |||
return []string{}, fmt.Errorf("Failed to read img directory. %v", err) | |||
} | |||
result = append(result, files...) | |||
} | |||
return result, nil | |||
} | |||
func Asset(name string) ([]byte, error) { | |||
staticPath := path.Join(setting.StaticRootPath, "public", name) | |||
if com.IsFile(staticPath) { | |||
f, err := os.Open(staticPath) | |||
defer f.Close() | |||
if err == nil { | |||
return ioutil.ReadAll(f) | |||
} | |||
} | |||
return nil, fmt.Errorf("Asset file does not exist: %s", name) | |||
} |
@@ -7,6 +7,7 @@ | |||
package public | |||
import ( | |||
"fmt" | |||
"io/ioutil" | |||
"gitea.com/macaron/macaron" | |||
@@ -20,6 +21,16 @@ func Static(opts *Options) macaron.Handler { | |||
return opts.staticHandler("") | |||
} | |||
func Dir(name string) ([]string, error) { | |||
files, err := AssetDir(name) | |||
if err != nil { | |||
return []string{}, fmt.Errorf("Failed to read embedded directory. %v", err) | |||
} | |||
return files, nil | |||
} | |||
func Asset(name string) ([]byte, error) { | |||
f, err := Assets.Open("/" + name) | |||
if err != nil { | |||
@@ -29,6 +40,24 @@ func Asset(name string) ([]byte, error) { | |||
return ioutil.ReadAll(f) | |||
} | |||
func AssetDir(dirName string) ([]string, error) { | |||
d, err := Assets.Open(dirName) | |||
if err != nil { | |||
return nil, err | |||
} | |||
defer d.Close() | |||
files, err := d.Readdir(-1) | |||
if err != nil { | |||
return nil, err | |||
} | |||
var results = make([]string, 0, len(files)) | |||
for _, file := range files { | |||
results = append(results, file.Name()) | |||
} | |||
return results, nil | |||
} | |||
func AssetNames() []string { | |||
realFS := Assets.(vfsgen۰FS) | |||
var results = make([]string, 0, len(realFS)) | |||
@@ -41,6 +41,66 @@ func Setnx(key, value string, timeout time.Duration) (bool, error) { | |||
} | |||
func SETNX(conn redis.Conn, key, value string, seconds int) (bool, error) { | |||
reply, err := conn.Do("SET", key, value, "NX", "EX", seconds) | |||
return redis.Bool(reply, err) | |||
} | |||
func SET(conn redis.Conn, key, value string, seconds int) (bool, error) { | |||
reply, err := conn.Do("SETEX", key, seconds, value) | |||
return redis.Bool(reply, err) | |||
} | |||
func HSETNX(conn redis.Conn, key, subKey string, value interface{}) error { | |||
_, err := conn.Do("HSETNX", key, subKey, value) | |||
return err | |||
} | |||
func HGET(conn redis.Conn, key, subKey string) (interface{}, error) { | |||
return conn.Do("HGET", key, subKey) | |||
} | |||
func EXISTS(conn redis.Conn, key string) (bool, error) { | |||
reply, err := conn.Do("EXISTS", key) | |||
return redis.Bool(reply, err) | |||
} | |||
func HEXISTS(conn redis.Conn, key string, subKey string) (bool, error) { | |||
reply, err := conn.Do("HEXISTS", key, subKey) | |||
return redis.Bool(reply, err) | |||
} | |||
func Expire(conn redis.Conn, key string, seconds int) error { | |||
_, err := conn.Do("EXPIRE", key, seconds) | |||
return err | |||
} | |||
func HINCRBY(conn redis.Conn, key, subKey string, value int) error { | |||
_, err := conn.Do("HINCRBY", key, subKey, value) | |||
return err | |||
} | |||
func GET(conn redis.Conn, key string) (interface{}, error) { | |||
return conn.Do("GET", key) | |||
} | |||
func Ttl(conn redis.Conn, key string) (int, error) { | |||
reply, err := conn.Do("TTL", key) | |||
if err != nil { | |||
return 0, err | |||
} | |||
n, _ := strconv.Atoi(fmt.Sprint(reply)) | |||
return n, nil | |||
} | |||
func Get(key string) (string, error) { | |||
redisClient := labelmsg.Get() | |||
defer redisClient.Close() | |||
@@ -0,0 +1,47 @@ | |||
package setting | |||
import ( | |||
"code.gitea.io/gitea/modules/log" | |||
) | |||
type Phone struct { | |||
Enabled bool | |||
VerifyCodeLength int | |||
AccessKeyId string | |||
AccessKeySecret string | |||
SignName string | |||
TemplateCode string | |||
CodeTimeout int | |||
RetryInterval int | |||
MaxRetryTimes int | |||
} | |||
var ( | |||
// Phone verify info | |||
PhoneService *Phone | |||
) | |||
func newPhoneService() { | |||
sec := Cfg.Section("phone") | |||
// Check phone setting. | |||
if !sec.Key("ENABLED").MustBool() { | |||
PhoneService = &Phone{ | |||
Enabled: sec.Key("ENABLED").MustBool(), | |||
} | |||
return | |||
} | |||
PhoneService = &Phone{ | |||
Enabled: sec.Key("ENABLED").MustBool(), | |||
VerifyCodeLength: sec.Key("VERIFY_CODE_LEN").MustInt(6), | |||
AccessKeyId: sec.Key("AccessKeyId").String(), | |||
AccessKeySecret: sec.Key("AccessKeySecret").String(), | |||
SignName: sec.Key("SignName").String(), | |||
TemplateCode: sec.Key("TemplateCode").String(), | |||
CodeTimeout: sec.Key("CODE_TIMEOUT").MustInt(60 * 5), | |||
RetryInterval: sec.Key("RETRY_INTERVAL").MustInt(60 * 2), | |||
MaxRetryTimes: sec.Key("MAX_RETRY").MustInt(5), | |||
} | |||
log.Info("Phone Service Enabled") | |||
} |
@@ -1519,4 +1519,5 @@ func NewServices() { | |||
newIndexerService() | |||
newTaskService() | |||
NewQueueService() | |||
newPhoneService() | |||
} |
@@ -0,0 +1,12 @@ | |||
package setting | |||
import ( | |||
"image" | |||
) | |||
var ( | |||
// the original images for generate slide image | |||
SlideImagesBg []*image.Image | |||
SlideMaskImage *image.Image | |||
SlideImagesCount int | |||
) |
@@ -0,0 +1,308 @@ | |||
package slideimage | |||
import ( | |||
"bytes" | |||
"image" | |||
"image/png" | |||
"math" | |||
"math/rand" | |||
"path" | |||
"strconv" | |||
"strings" | |||
"time" | |||
"code.gitea.io/gitea/modules/labelmsg" | |||
"github.com/gomodule/redigo/redis" | |||
"code.gitea.io/gitea/modules/public" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/redis/redis_client" | |||
"gitea.com/macaron/macaron" | |||
"github.com/disintegration/imaging" | |||
"github.com/google/uuid" | |||
) | |||
type SlideImage struct { | |||
SubURL string | |||
URLPrefix string | |||
SampleImages int | |||
StdWidth int | |||
StdHeight int | |||
MaskSize int | |||
ImageY int | |||
ImageXStart int | |||
MinImageX int | |||
Expiration int | |||
Tolerance int | |||
CachePrefix string | |||
CacheManualPrefix string | |||
} | |||
type Options struct { | |||
// Suburl path. Default is empty. | |||
SubURL string | |||
// URL prefix of getting captcha pictures. Default is "/slideimage/". | |||
URLPrefix string | |||
//Default is 4 | |||
SampleImages int | |||
//Image width default 391 | |||
StdWidth int | |||
//Image Height default 196 | |||
StdHeight int | |||
// default 51 | |||
MaskSize int | |||
// default 125 | |||
ImageY int | |||
//default 0 | |||
ImageXStart int | |||
//容忍的误差 default 2px | |||
Tolerance int | |||
// default 150 | |||
MinImageX int | |||
// default 600 seconds | |||
Expiration int | |||
//default slide: | |||
CachePrefix string | |||
//default mslide: 验证通过,在缓存中记录已进行过人工操作,然后在发送验证码之前再进行校验是否进行了人工操作。 | |||
CacheManualPrefix string | |||
} | |||
func NewSlideImage(opt Options) *SlideImage { | |||
return &SlideImage{ | |||
SubURL: opt.SubURL, | |||
URLPrefix: opt.URLPrefix, | |||
SampleImages: opt.SampleImages, | |||
StdWidth: opt.StdWidth, | |||
StdHeight: opt.StdHeight, | |||
MaskSize: opt.MaskSize, | |||
ImageY: opt.ImageY, | |||
ImageXStart: opt.ImageXStart, | |||
Tolerance: opt.Tolerance, | |||
MinImageX: opt.MinImageX, | |||
Expiration: opt.Expiration, | |||
CachePrefix: opt.CachePrefix, | |||
} | |||
} | |||
func prepareOptions(options []Options) Options { | |||
var opt Options | |||
if len(options) > 0 { | |||
opt = options[0] | |||
} | |||
opt.SubURL = strings.TrimSuffix(opt.SubURL, "/") | |||
// Defaults. | |||
if len(opt.URLPrefix) == 0 { | |||
opt.URLPrefix = "/slideimage/" | |||
} else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' { | |||
opt.URLPrefix += "/" | |||
} | |||
if opt.SampleImages == 0 { | |||
opt.SampleImages = 4 | |||
} | |||
if opt.StdWidth == 0 { | |||
opt.StdWidth = 391 | |||
} | |||
if opt.StdHeight == 0 { | |||
opt.StdHeight = 196 | |||
} | |||
if opt.MaskSize == 0 { | |||
opt.MaskSize = 51 | |||
} | |||
if opt.ImageY == 0 { | |||
opt.ImageY = 75 | |||
} | |||
if opt.ImageXStart == 0 { | |||
opt.ImageXStart = 2 | |||
} | |||
if opt.Tolerance == 0 { | |||
opt.Tolerance = 2 | |||
} | |||
if opt.MinImageX == 0 { | |||
opt.MinImageX = 150 | |||
} | |||
if opt.Expiration == 0 { | |||
opt.Expiration = 600 | |||
} | |||
if len(opt.CachePrefix) == 0 { | |||
opt.CachePrefix = "slide:" | |||
} | |||
if len(opt.CacheManualPrefix) == 0 { | |||
opt.CacheManualPrefix = "mslide:" | |||
} | |||
return opt | |||
} | |||
func (s *SlideImage) key(id string) string { | |||
return s.CachePrefix + id | |||
} | |||
func (s *SlideImage) mkey(id string) string { | |||
return s.CacheManualPrefix + id | |||
} | |||
func (s *SlideImage) VerifyManual(id string) (bool, error) { | |||
redisConn := labelmsg.Get() | |||
defer redisConn.Close() | |||
return redis_client.EXISTS(redisConn, s.mkey(id)) | |||
} | |||
func (s *SlideImage) Verify(id string, x int) bool { | |||
redisConn := labelmsg.Get() | |||
defer redisConn.Close() | |||
v, err := redis_client.GET(redisConn, s.key(id)) | |||
v1, err := redis.String(v, err) | |||
if err != nil { | |||
log.Warn("redis err", err) | |||
return false | |||
} | |||
if v1 == "" { | |||
return false | |||
} | |||
values := strings.Split(v1, "-") | |||
imageRandX, _ := strconv.Atoi(values[1]) | |||
if int(math.Abs(float64(imageRandX-x))) <= s.Tolerance { | |||
redis_client.SETNX(redisConn, s.mkey(id), "1", s.Expiration) | |||
return true | |||
} | |||
return false | |||
} | |||
func (s *SlideImage) CreateCode() (string, int, int) { | |||
nums := rand.Intn(s.SampleImages) | |||
imageId := uuid.New().String() | |||
//获取随机x坐标 | |||
imageRandX := rand.Intn(s.StdWidth - s.MaskSize - 3) | |||
if imageRandX < s.MinImageX { | |||
imageRandX += s.MinImageX | |||
} | |||
redis_client.Setex(s.key(imageId), strconv.Itoa(nums)+"-"+strconv.Itoa(imageRandX), time.Second*time.Duration(s.Expiration)) | |||
return imageId, nums, imageRandX | |||
} | |||
func SlideImager(options ...Options) macaron.Handler { | |||
return func(ctx *macaron.Context) { | |||
slideImage := NewSlideImage(prepareOptions(options)) | |||
if strings.HasPrefix(ctx.Req.URL.Path, slideImage.URLPrefix) { | |||
id := path.Base(ctx.Req.URL.Path) | |||
if i := strings.Index(id, "."); i > -1 { | |||
id = id[:i] | |||
} | |||
isScreenshot := strings.HasSuffix(id, "screenshot") | |||
if isScreenshot { | |||
id = strings.TrimSuffix(id, "screenshot") | |||
} | |||
key := slideImage.key(id) | |||
v, err := redis_client.Get(key) | |||
if err != nil || v == "" { | |||
ctx.Status(404) | |||
ctx.Write([]byte("not found")) | |||
//png.Encode(ctx.Resp, *setting.SlideImagesBg[0]) | |||
return | |||
} | |||
values := strings.Split(v, "-") | |||
imageIndex, _ := strconv.Atoi(values[0]) | |||
imageRandX, _ := strconv.Atoi(values[1]) | |||
imageBg := setting.SlideImagesBg[imageIndex] | |||
maxPotion := image.Point{ | |||
X: imageRandX + slideImage.MaskSize, | |||
Y: slideImage.ImageY + slideImage.MaskSize, | |||
} | |||
minPotion := image.Point{ | |||
X: imageRandX, | |||
Y: slideImage.ImageY, | |||
} | |||
subimg := image.Rectangle{ | |||
Max: maxPotion, | |||
Min: minPotion, | |||
} | |||
if isScreenshot { | |||
data := imaging.Crop(*imageBg, subimg) | |||
png.Encode(ctx.Resp, data) | |||
} else { | |||
data := imaging.Overlay(*imageBg, *setting.SlideMaskImage, minPotion, 1.0) | |||
png.Encode(ctx.Resp, data) | |||
} | |||
ctx.Status(200) | |||
return | |||
} | |||
ctx.Data["SlideImageInfo"] = slideImage | |||
ctx.Data["EnablePhone"] = setting.PhoneService.Enabled | |||
ctx.Map(slideImage) | |||
} | |||
} | |||
func InitSlideImage() { | |||
if setting.PhoneService.Enabled { | |||
filenames, err := public.Dir(path.Join("img", "slide", "bg")) | |||
if err != nil { | |||
panic("Slide Image Service init failed") | |||
} | |||
maskFileName, err := public.Dir(path.Join("img", "slide", "mask")) | |||
if err != nil { | |||
panic("Slide Image Service init failed") | |||
} | |||
for _, filename := range filenames { | |||
if strings.HasSuffix(filename, ".png") { | |||
content, err := public.Asset(path.Join("img", "slide", "bg", filename)) | |||
if err != nil { | |||
log.Warn("can not open "+filename, err) | |||
continue | |||
} | |||
m, err := png.Decode(bytes.NewReader(content)) | |||
if err != nil { | |||
log.Warn("can not decode "+filename, err) | |||
continue | |||
} | |||
setting.SlideImagesBg = append(setting.SlideImagesBg, &m) | |||
} | |||
} | |||
setting.SlideImagesCount = len(setting.SlideImagesBg) | |||
if setting.SlideImagesCount == 0 { | |||
panic("Slide Image Service init failed") | |||
} | |||
maskContent, err := public.Asset(path.Join("img", "slide", "mask", maskFileName[0])) | |||
if err != nil { | |||
panic("Slide Image Service init failed") | |||
} | |||
MaskImage, err := png.Decode(bytes.NewReader(maskContent)) | |||
if err != nil { | |||
panic("Slide Image Service init failed") | |||
} | |||
setting.SlideMaskImage = &MaskImage | |||
log.Info("Slide Image Service Enabled") | |||
} | |||
} |
@@ -197,6 +197,7 @@ no_reply_address_helper = Domain name for users with a hidden email address. For | |||
[home] | |||
uname_holder = Username or Email Address | |||
login_uname_holder=Username/Email/Phone number | |||
uname_holder_cloud_brain = cloudbrain username | |||
password_holder = Password | |||
switch_dashboard_context = Switch Dashboard Context | |||
@@ -335,11 +336,16 @@ resent_limit_prompt = You have already requested an activation email recently. P | |||
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below. | |||
resend_mail = Click here to resend your activation email | |||
email_not_associate = The email address is not associated with any account. | |||
email_not_main=The email address is wrong, please input your primary email address. | |||
email_not_right=The email address is not associated with any account, please input the right email address. | |||
send_reset_mail = Send Account Recovery Email | |||
please_enter_main_email=Please enter your primary email | |||
please_enter_main_email_tips=Tips: Only the primary email can receive the recovery email. | |||
reset_password = Account Recovery | |||
invalid_code = Your confirmation code is invalid or has expired. | |||
reset_password_helper = Recover Account | |||
reset_password_wrong_user = You are signed in as %s, but the account recovery link is for %s | |||
reset_password_wrong_user_phone=You are signed in, but the phone number is used by other user. | |||
password_too_short = Password length cannot be less than %d characters. | |||
non_local_account = Non-local users can not update their password through the openi web interface. | |||
verify = Verify | |||
@@ -374,6 +380,37 @@ authorization_failed = Authorization failed | |||
authorization_failed_desc = The authorization failed because we detected an invalid request. Please contact the maintainer of the app you've tried to authorize. | |||
disable_forgot_password_mail = Account recovery is disabled. Please contact your site administrator. | |||
sspi_auth_failed = SSPI authentication failed | |||
[phone] | |||
format_err=The format of phone number is wrong. | |||
query_err=Fail to query phone number, please try again later. | |||
already_register=The phone number is already used. | |||
not_register=The phone number is wrong. | |||
not_modify=The phone number is not updated. | |||
max_times=One phone number can not send verification code more than %s times a day. | |||
too_fast=Send too frequently, please try again later. | |||
manual_first=Please slide to finish the jigsaw first. | |||
verify_code_fail=Please input right verification code. | |||
bind_phone=Please Bind Your Phone. | |||
bind_phone_fail=Fail to bind phone number, please try again later. | |||
phone_number=Phone number | |||
drag_the_slider_to_fill_the_puzzle=Drag the slider to the right to fill the puzzle | |||
mobile_phone_verification_code=Phone verification code | |||
please_enter_SMS_verification_code=Please enter SMS verification code | |||
get_verification_code=Get verification code | |||
new_login_password=New login password | |||
please_enter_new_password=Please enter new password | |||
second_resend=S resend | |||
please_bind_your_mobile_number=Please Bind Your Phone Number | |||
submit=Submit | |||
please_enter_the_correct_mobile_number=Please enter the correct phone number | |||
please_enter_the_correct_mobile_phone_verification_code=Please enter the correct phone verification code | |||
email_retrieve_password=Email retrieve password | |||
mobile_number_retrieve_password=Phone number retrieve password | |||
mobile_login=Mobile login | |||
account_password_login=Account password login | |||
cloud_brain_user_login=Cloud brain user login | |||
modify_phone_number=Modify phone number | |||
[mail] | |||
activate_account = Please activate your account | |||
@@ -2495,6 +2532,7 @@ users.new_account = Create User Account | |||
users.name = Username | |||
users.full_name = Full Name | |||
users.activated = Activated | |||
users.bind_phone = Bind Phone | |||
users.admin = Admin | |||
users.restricted = Restricted | |||
users.repos = Repos | |||
@@ -198,6 +198,7 @@ no_reply_address_helper=具有隐藏电子邮件地址的用户的域名。例 | |||
[home] | |||
uname_holder=登录名或电子邮箱地址 | |||
login_uname_holder=用户名/邮箱/手机号 | |||
uname_holder_cloud_brain=云脑登录名 | |||
password_holder=密码 | |||
switch_dashboard_context=切换控制面板用户 | |||
@@ -339,11 +340,16 @@ resent_limit_prompt=您请求发送激活邮件过于频繁,请等待 3 分钟 | |||
has_unconfirmed_mail=%s 您好,系统检测到您有一封发送至 <b>%s</b> 但未被确认的邮件。如果您未收到激活邮件,或需要重新发送,请单击下方的按钮。 | |||
resend_mail=单击此处重新发送确认邮件 | |||
email_not_associate=您输入的邮箱地址未被关联到任何帐号! | |||
send_reset_mail=发送账户恢复邮件 | |||
email_not_main=电子邮箱地址不正确,请输入您设置的主要邮箱地址。 | |||
email_not_right=您输入了不存在的邮箱地址,请输入正确的邮箱地址。 | |||
send_reset_mail=发送密码找回邮件 | |||
please_enter_main_email=请输入接收通知提醒的主要邮箱地址 | |||
please_enter_main_email_tips=说明:如果您设置了多个邮箱地址,只有主要邮箱可以收到密码找回邮件,其他邮箱无法收到。 | |||
reset_password=账户恢复 | |||
invalid_code=此确认密钥无效或已过期。 | |||
reset_password_helper=恢复账户 | |||
reset_password_wrong_user=您已作为 %s 登录,无法使用链接恢复 %s 的账户。 | |||
reset_password_wrong_user_phone=您已登录,不能用别的账号的手机恢复。 | |||
password_too_short=密码长度不能少于 %d 位。 | |||
non_local_account=非本地帐户不能通过 openi 的 web 界面更改密码。 | |||
verify=验证 | |||
@@ -378,6 +384,37 @@ authorization_failed=授权失败 | |||
authorization_failed_desc=授权失败,这是一个无效的请求。请联系尝试授权应用的管理员。 | |||
disable_forgot_password_mail = Account recovery is disabled. Please contact your site administrator. | |||
sspi_auth_failed=SSPI 认证失败 | |||
[phone] | |||
format_err=手机号格式错误。 | |||
query_err=查询手机号失败,请稍后再试。 | |||
already_register=手机号已被使用。 | |||
not_register=手机号输入错误。 | |||
not_modify=手机号未修改。 | |||
max_times=一个手机号每天发送验证码次数不能超过%s次。 | |||
too_fast=验证码发送太频繁,请稍后再试。 | |||
manual_first=请先拖动滑块填充拼图。 | |||
verify_code_fail=请输入正确的短信验证码。 | |||
bind_phone=请绑定手机号。 | |||
bind_phone_fail=绑定手机号失败,请稍后再试。 | |||
phone_number=手机号码 | |||
drag_the_slider_to_fill_the_puzzle=向右拖动滑块填充拼图 | |||
mobile_phone_verification_code=手机验证码 | |||
please_enter_SMS_verification_code=请输入短信验证码 | |||
get_verification_code=获取验证码 | |||
new_login_password=新的登录密码 | |||
please_enter_new_password=请输入新的密码 | |||
second_resend=S后重发 | |||
please_bind_your_mobile_number=请绑定手机号 | |||
submit=提交 | |||
please_enter_the_correct_mobile_number=请输入正确的手机号 | |||
please_enter_the_correct_mobile_phone_verification_code=请输入正确格式的手机验证码 | |||
email_retrieve_password=邮箱找回密码 | |||
mobile_number_retrieve_password=手机号找回密码 | |||
mobile_login=手机登录 | |||
account_password_login=账号密码登录 | |||
cloud_brain_user_login=云脑1用户登录 | |||
modify_phone_number=修改手机号 | |||
[mail] | |||
activate_account=请激活您的帐户 | |||
@@ -2510,6 +2547,7 @@ users.new_account=创建新帐户 | |||
users.name=用户名 | |||
users.full_name=全名 | |||
users.activated=已激活 | |||
users.bind_phone=手机验证 | |||
users.admin=管理员 | |||
users.restricted=受限 | |||
users.repos=项目数 | |||
@@ -117,6 +117,10 @@ func Dashboard(ctx *context.Context) { | |||
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password" | |||
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL) | |||
ctx.Redirect(setting.AppSubURL + "/user/settings/change_password") | |||
} else if setting.PhoneService.Enabled && ctx.User.IsActive && ctx.User.PhoneNumber == "" { | |||
ctx.Data["Title"] = ctx.Tr("phone.bind_phone") | |||
ctx.HTML(200, "user/auth/bind_phone") | |||
return | |||
} else { | |||
user.Dashboard(ctx) | |||
} | |||
@@ -10,6 +10,8 @@ import ( | |||
"strings" | |||
"time" | |||
"code.gitea.io/gitea/modules/slideimage" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/migrations" | |||
"code.gitea.io/gitea/modules/auth/sso" | |||
@@ -57,6 +59,7 @@ func checkRunMode() { | |||
// NewServices init new services | |||
func NewServices() { | |||
setting.NewServices() | |||
slideimage.InitSlideImage() | |||
if err := storage.Init(); err != nil { | |||
log.Fatal("storage init failed: %v", err) | |||
} | |||
@@ -12,6 +12,8 @@ import ( | |||
"text/template" | |||
"time" | |||
"code.gitea.io/gitea/modules/slideimage" | |||
"code.gitea.io/gitea/routers/image" | |||
"code.gitea.io/gitea/routers/authentication" | |||
@@ -233,6 +235,10 @@ func NewMacaron() *macaron.Macaron { | |||
m.Use(captcha.Captchaer(captcha.Options{ | |||
SubURL: setting.AppSubURL, | |||
})) | |||
m.Use(slideimage.SlideImager(slideimage.Options{ | |||
SubURL: setting.AppSubURL, | |||
SampleImages: setting.SlideImagesCount, | |||
})) | |||
m.Use(session.Sessioner(session.Options{ | |||
Provider: setting.SessionConfig.Provider, | |||
ProviderConfig: setting.SessionConfig.ProviderConfig, | |||
@@ -368,6 +374,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/login/cloud_brain", user.SignInCloudBrain) | |||
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) | |||
m.Get("/login/phone", user.SignInPhone) | |||
m.Post("/login/phone", bindIgnErr(auth.PhoneNumberCodeForm{}), user.SignInPhonePost) | |||
m.Group("", func() { | |||
m.Combo("/login/openid"). | |||
Get(user.SignInOpenID). | |||
@@ -407,6 +416,10 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
}, reqSignOut) | |||
m.Any("/user/events", reqSignIn, events.Events) | |||
m.Get("/slideImage", user.CreateSlideImageInfo) | |||
m.Post("/verifySlideImage", bindIgnErr(auth.SlideImageForm{}), user.VerifySlideImage) | |||
m.Post("/sendVerifyCode", bindIgnErr(auth.PhoneNumberForm{}), user.SendVerifyCode) | |||
m.Post("/bindPhone", reqSignIn, bindIgnErr(auth.PhoneNumberCodeForm{}), user.BindPhone) | |||
m.Group("/login/oauth", func() { | |||
m.Get("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth) | |||
@@ -486,6 +499,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/email2user", user.Email2User) | |||
m.Get("/recover_account", user.ResetPasswd) | |||
m.Post("/recover_account", user.ResetPasswdPost) | |||
m.Post("/recover_account_by_phone",bindIgnErr(auth.ResetPassWordByPhoneForm{}), user.ResetPasswdByPhonePost) | |||
m.Get("/forgot_password", user.ForgotPasswd) | |||
m.Post("/forgot_password", user.ForgotPasswdPost) | |||
m.Post("/logout", user.SignOut) | |||
@@ -8,9 +8,19 @@ package user | |||
import ( | |||
"errors" | |||
"fmt" | |||
"github.com/gomodule/redigo/redis" | |||
"net/http" | |||
"strconv" | |||
"strings" | |||
"code.gitea.io/gitea/modules/slideimage" | |||
phoneService "code.gitea.io/gitea/services/phone" | |||
"code.gitea.io/gitea/modules/labelmsg" | |||
"code.gitea.io/gitea/modules/phone" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/auth" | |||
"code.gitea.io/gitea/modules/auth/oauth2" | |||
@@ -38,11 +48,13 @@ const ( | |||
tplSignIn base.TplName = "user/auth/signin" | |||
// tplSignIn template for sign in page | |||
tplSignInCloudBrain base.TplName = "user/auth/signin_cloud_brain" | |||
tplSignInPhone base.TplName = "user/auth/signin_phone" | |||
// tplSignUp template path for sign up page | |||
tplSignUp base.TplName = "user/auth/signup" | |||
// TplActivate template path for activate user | |||
TplActivate base.TplName = "user/auth/activate" | |||
tplForgotPassword base.TplName = "user/auth/forgot_passwd" | |||
tplForgotPasswordPhone base.TplName = "user/auth/forgot_passwd_phone" | |||
tplResetPassword base.TplName = "user/auth/reset_passwd" | |||
tplTwofa base.TplName = "user/auth/twofa" | |||
tplTwofaScratch base.TplName = "user/auth/twofa_scratch" | |||
@@ -176,6 +188,49 @@ func SignInCloudBrain(ctx *context.Context) { | |||
ctx.HTML(200, tplSignInCloudBrain) | |||
} | |||
func SignInPhone(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
// Check auto-login. | |||
if checkAutoLogin(ctx) { | |||
return | |||
} | |||
ctx.Data["PageIsPhoneLogin"] = true | |||
ctx.HTML(200, tplSignInPhone) | |||
} | |||
func SignInPhonePost(ctx *context.Context, form auth.PhoneNumberCodeForm) { | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
ctx.Data["PageIsPhoneLogin"] = true | |||
ctx.Data["IsCourse"] = ctx.QueryBool("course") | |||
if ctx.HasError() { | |||
ctx.HTML(200, tplSignInPhone) | |||
return | |||
} | |||
if !phoneService.IsVerifyCodeRight(strings.TrimSpace(form.PhoneNumber), strings.TrimSpace(form.VerifyCode)) { | |||
ctx.RenderWithErr(ctx.Tr("phone.verify_code_fail"), tplSignInPhone, &form) | |||
return | |||
} | |||
u, err := models.GetUserByPhoneNumber(strings.TrimSpace(form.PhoneNumber)) | |||
if err != nil { | |||
if models.IsErrUserNotExist(err) { | |||
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignInPhone, &form) | |||
log.Info("Failed authentication attempt for %s from %s", form.PhoneNumber, ctx.RemoteAddr()) | |||
} else { | |||
ctx.ServerError("UserSignIn", err) | |||
} | |||
return | |||
} | |||
models.SaveLoginInfoToDb(ctx.Req.Request, u) | |||
handleSignIn(ctx, u, form.Remember) | |||
} | |||
func SignInPostAPI(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
UserName := ctx.Query("UserName") | |||
@@ -1252,10 +1307,20 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo | |||
return | |||
} | |||
if setting.PhoneService.Enabled { | |||
phoneNumber := strings.TrimSpace(form.PhoneNumber) | |||
verifyCode := strings.TrimSpace(form.VerifyCode) | |||
if !phoneService.IsVerifyCodeRight(phoneNumber, verifyCode) { | |||
ctx.RenderWithErr(ctx.Tr("phone.verify_code_fail"), tplSignUp, &form) | |||
return | |||
} | |||
} | |||
u := &models.User{ | |||
Name: form.UserName, | |||
Email: form.Email, | |||
Passwd: form.Password, | |||
PhoneNumber: strings.TrimSpace(form.PhoneNumber), | |||
IsActive: !setting.Service.RegisterEmailConfirm, | |||
} | |||
if err := models.CreateUser(u); err != nil { | |||
@@ -1427,6 +1492,17 @@ func ActivateEmail(ctx *context.Context) { | |||
// ForgotPasswd render the forget pasword page | |||
func ForgotPasswd(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title") | |||
forgetType := ctx.Query("type") | |||
if forgetType == "phone" { | |||
if !setting.PhoneService.Enabled { | |||
ctx.Data["IsResetDisable"] = true | |||
ctx.HTML(200, tplForgotPasswordPhone) | |||
return | |||
} | |||
ctx.Data["IsResetRequest"] = true | |||
ctx.HTML(200, tplForgotPasswordPhone) | |||
} else { | |||
if setting.MailService == nil { | |||
ctx.Data["IsResetDisable"] = true | |||
@@ -1439,6 +1515,7 @@ func ForgotPasswd(ctx *context.Context) { | |||
ctx.Data["IsResetRequest"] = true | |||
ctx.HTML(200, tplForgotPassword) | |||
} | |||
} | |||
// ForgotPasswdPost response for forget password request | |||
@@ -1454,12 +1531,16 @@ func ForgotPasswdPost(ctx *context.Context) { | |||
email := ctx.Query("email") | |||
ctx.Data["Email"] = email | |||
u, err := models.GetUserByEmail(email) | |||
u, err := models.GetUserByMainEmail(email) | |||
if err != nil { | |||
if models.IsErrUserNotExist(err) { | |||
ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language()) | |||
ctx.Data["IsResetSent"] = true | |||
ctx.HTML(200, tplForgotPassword) | |||
ctx.Data["IsResetSent"] = false | |||
if used, _ := models.IsEmailUsed(email); used { | |||
ctx.RenderWithErr(ctx.Tr("auth.email_not_main"), tplForgotPassword, nil) | |||
} else { | |||
ctx.RenderWithErr(ctx.Tr("auth.email_not_right"), tplForgotPassword, nil) | |||
} | |||
return | |||
} | |||
@@ -1647,6 +1728,55 @@ func ResetPasswdPost(ctx *context.Context) { | |||
handleSignInFull(ctx, u, remember, true) | |||
} | |||
func ResetPasswdByPhonePost(ctx *context.Context, form auth.ResetPassWordByPhoneForm) { | |||
phoneNumber := strings.TrimSpace(form.PhoneNumber) | |||
verifyCode := strings.TrimSpace(form.VerifyCode) | |||
isRight := phoneService.IsVerifyCodeRight(phoneNumber, verifyCode) | |||
if !isRight { | |||
ctx.RenderWithErr(ctx.Tr("phone.verify_code_fail"), tplForgotPasswordPhone, form) | |||
return | |||
} | |||
passwd := strings.TrimSpace(form.Password) | |||
if len(passwd) < setting.MinPasswordLength { | |||
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplForgotPasswordPhone, form) | |||
return | |||
} else if !password.IsComplexEnough(passwd) { | |||
ctx.RenderWithErr(password.BuildComplexityError(ctx), tplForgotPasswordPhone, form) | |||
return | |||
} | |||
u, err := models.GetUserByPhoneNumber(phoneNumber) | |||
if err != nil { | |||
log.Error("fail to query by phone number", err) | |||
ctx.RenderWithErr(ctx.Tr("phone.query_err", setting.MinPasswordLength), tplForgotPasswordPhone, form) | |||
return | |||
} | |||
if nil != ctx.User && u.ID != ctx.User.ID { | |||
ctx.RenderWithErr(ctx.Tr("auth.reset_password_wrong_user", ctx.User.Email, u.Email), tplForgotPasswordPhone, form) | |||
return | |||
} | |||
if u.Rands, err = models.GetUserSalt(); err != nil { | |||
ctx.ServerError("UpdateUser", err) | |||
return | |||
} | |||
if u.Salt, err = models.GetUserSalt(); err != nil { | |||
ctx.ServerError("UpdateUser", err) | |||
return | |||
} | |||
u.HashPassword(passwd) | |||
u.MustChangePassword = false | |||
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil { | |||
ctx.ServerError("UpdateUser", err) | |||
return | |||
} | |||
handleSignInFull(ctx, u, form.Remember, true) | |||
} | |||
// MustChangePassword renders the page to change a user's password | |||
func MustChangePassword(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("auth.must_change_password") | |||
@@ -1709,3 +1839,135 @@ func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form aut | |||
ctx.Redirect(setting.AppSubURL + "/") | |||
} | |||
func CreateSlideImageInfo(ctx *context.Context, slideImage *slideimage.SlideImage) { | |||
id, _, _ := slideImage.CreateCode() | |||
ctx.JSON(http.StatusOK, models.BaseMessage{0, id}) | |||
} | |||
func VerifySlideImage(ctx *context.Context, slideImage *slideimage.SlideImage, form auth.SlideImageForm) { | |||
if slideImage.Verify(form.SlideID, form.X) { | |||
ctx.JSON(http.StatusOK, models.BaseOKMessage) | |||
} else { | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage("")) | |||
} | |||
} | |||
func BindPhone(ctx *context.Context, form auth.PhoneNumberCodeForm) { | |||
if strings.TrimSpace(form.PhoneNumber) != "" && strings.TrimSpace(form.VerifyCode) != "" && phoneService.IsVerifyCodeRight(strings.TrimSpace(form.PhoneNumber), strings.TrimSpace(form.VerifyCode)) { | |||
ctx.User.PhoneNumber = strings.TrimSpace(form.PhoneNumber) | |||
if err := models.UpdateUserSetting(ctx.User); err != nil { | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.bind_phone_fail"))) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, models.BaseOKMessage) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.verify_code_fail"))) | |||
} | |||
func SendVerifyCode(ctx *context.Context, slideImage *slideimage.SlideImage, form auth.PhoneNumberForm) { | |||
phoneNumber := strings.TrimSpace(form.PhoneNumber) | |||
if !phone.IsValidPhoneNumber(phoneNumber) { | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.format_err"))) | |||
return | |||
} | |||
hasManual, err := slideImage.VerifyManual(form.SlideID) | |||
if err != nil { | |||
log.Warn("redis err", err) | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) | |||
return | |||
} | |||
if !hasManual { | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) | |||
return | |||
} | |||
if form.Mode != 2 { | |||
has, err := models.IsUserByPhoneNumberExist(phoneNumber) | |||
if err != nil { | |||
log.Warn("sql err", err) | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) | |||
return | |||
} | |||
if form.Mode==0 { //注册 | |||
if has { | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) | |||
return | |||
} | |||
} else { //手机号验证码登录 mode=1 忘记密码 mode=3 | |||
if !has { | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.not_register"))) | |||
return | |||
} | |||
} | |||
} else { | |||
//修改手机号 mode=2 绑定手机 | |||
u, err := models.GetUserByPhoneNumber(phoneNumber) | |||
if err != nil && !models.IsErrUserNotExist(err) { | |||
log.Warn("sql err", err) | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) | |||
return | |||
} | |||
if u != nil { | |||
if u.ID == ctx.User.ID { //没有修改手机号 | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.not_modify"))) | |||
return | |||
} else { //修改的手机已经被别的用户注册 | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) | |||
return | |||
} | |||
} | |||
} | |||
redisConn := labelmsg.Get() | |||
defer redisConn.Close() | |||
sendTimes, err := phoneService.GetPhoneNumberSendTimes(redisConn, phoneNumber) | |||
if err != nil && err!=redis.ErrNil { | |||
log.Warn("redis err", err) | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) | |||
return | |||
} | |||
if sendTimes >= setting.PhoneService.MaxRetryTimes { | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.max_times", strconv.Itoa(setting.PhoneService.MaxRetryTimes)))) | |||
return | |||
} | |||
ttl, err := phoneService.GetPhoneCodeTTL(redisConn, phoneNumber) | |||
if err != nil { | |||
log.Warn("redis err", err) | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) | |||
return | |||
} | |||
if setting.PhoneService.CodeTimeout-ttl < setting.PhoneService.RetryInterval { | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.too_fast"))) | |||
return | |||
} | |||
err = phoneService.SendVerifyCode(redisConn, phoneNumber) | |||
if err != nil { | |||
log.Warn("send code or redis err", err) | |||
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, models.BaseOKMessage) | |||
} |
@@ -11,6 +11,8 @@ import ( | |||
"io/ioutil" | |||
"strings" | |||
phoneService "code.gitea.io/gitea/services/phone" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/auth" | |||
"code.gitea.io/gitea/modules/base" | |||
@@ -89,6 +91,18 @@ func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) { | |||
return | |||
} | |||
if setting.PhoneService.Enabled && strings.TrimSpace(form.PhoneNumber) != "" { | |||
if strings.TrimSpace(form.PhoneNumber) != ctx.User.PhoneNumber { | |||
if phoneService.IsVerifyCodeRight(strings.TrimSpace(form.PhoneNumber), strings.TrimSpace(form.VerifyCode)) { | |||
ctx.User.PhoneNumber = strings.TrimSpace(form.PhoneNumber) | |||
} else { | |||
ctx.Flash.Error(ctx.Tr("phone.verify_code_fail")) | |||
ctx.Redirect(setting.AppSubURL + "/user/settings") | |||
return | |||
} | |||
} | |||
} | |||
ctx.User.FullName = form.FullName | |||
ctx.User.KeepEmailPrivate = form.KeepEmailPrivate | |||
@@ -0,0 +1,93 @@ | |||
package phone | |||
import ( | |||
"fmt" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/phone" | |||
"code.gitea.io/gitea/modules/redis/redis_client" | |||
"code.gitea.io/gitea/modules/setting" | |||
"github.com/gomodule/redigo/redis" | |||
) | |||
//验证码存储前缀 使用时%s用手机号替代 | |||
const CODE_PREFIX = "P_C:%s" | |||
//手机号发送验证码次数Hkey,%s对应日期, 存储在hset中,值是hashet,记录手机号和发送次数 | |||
const TIMES_PREFIX = "P_T:%s" | |||
func GetPhoneNumberSendTimes(conn redis.Conn, phoneNumber string) (int, error) { | |||
i, err := redis_client.HGET(conn, GetPhoneTimesHKey(), phoneNumber) | |||
return redis.Int(i, err) | |||
} | |||
func GetPhoneCodeTTL(conn redis.Conn, phoneNumber string) (int, error) { | |||
return redis_client.Ttl(conn, GetPhoneCodeKey(phoneNumber)) | |||
} | |||
func SendVerifyCode(conn redis.Conn, phoneNumber string) error { | |||
timesKey := GetPhoneTimesHKey() | |||
exists, err := redis_client.EXISTS(conn, timesKey) | |||
if err != nil { | |||
return err | |||
} | |||
code := phone.GenerateVerifyCode(setting.PhoneService.VerifyCodeLength) | |||
err = phone.SendVerifyCode(phoneNumber, code) | |||
if err != nil { | |||
return err | |||
} | |||
redis_client.SET(conn, GetPhoneCodeKey(phoneNumber), code, setting.PhoneService.CodeTimeout) | |||
if !exists { | |||
err = redis_client.HSETNX(conn, timesKey, phoneNumber, 1) | |||
if err != nil { | |||
return err | |||
} | |||
err = redis_client.Expire(conn, timesKey, getRemainSecondOfDay(time.Now())) | |||
if err != nil { | |||
return err | |||
} | |||
} else { | |||
err = redis_client.HINCRBY(conn, timesKey, phoneNumber, 1) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
func IsVerifyCodeRight(phoneNumer string, verifyCode string) bool { | |||
if phoneNumer == "" { | |||
return false | |||
} | |||
value, err := redis_client.Get(GetPhoneCodeKey(phoneNumer)) | |||
if err != nil { | |||
log.Warn("redis err", err) | |||
return false | |||
} else { | |||
if value == "" { | |||
return false | |||
} | |||
return value == verifyCode | |||
} | |||
} | |||
func GetPhoneCodeKey(phoneNumber string) string { | |||
return fmt.Sprintf(CODE_PREFIX, phoneNumber) | |||
} | |||
func GetPhoneTimesHKey() string { | |||
today := time.Now().Format("2006-01-02") | |||
return fmt.Sprintf(TIMES_PREFIX, today) | |||
} | |||
func getRemainSecondOfDay(t time.Time) int { | |||
return 86400 - 60*60*t.Hour() - 60*t.Minute() - t.Second() | |||
} |
@@ -20,6 +20,7 @@ | |||
<th>{{.i18n.Tr "admin.users.name"}}</th> | |||
<th>{{.i18n.Tr "email"}}</th> | |||
<th>{{.i18n.Tr "admin.users.activated"}}</th> | |||
<th>{{.i18n.Tr "admin.users.bind_phone"}}</th> | |||
<th>{{.i18n.Tr "admin.users.admin"}}</th> | |||
<th>{{.i18n.Tr "admin.users.restricted"}}</th> | |||
<th>{{.i18n.Tr "admin.users.repos"}}</th> | |||
@@ -35,6 +36,7 @@ | |||
<td><a href="{{AppSubUrl}}/{{.Name}}">{{.Name}}</a></td> | |||
<td><span class="text truncate email">{{.Email}}</span></td> | |||
<td><i class="fa fa{{if .IsActive}}-check{{end}}-square-o"></i></td> | |||
<td><i class="fa fa{{if .PhoneNumber}}-check{{end}}-square-o"></i></td> | |||
<td><i class="fa fa{{if .IsAdmin}}-check{{end}}-square-o"></i></td> | |||
<td><i class="fa fa{{if .IsRestricted}}-check{{end}}-square-o"></i></td> | |||
<td>{{.NumRepos}}</td> | |||
@@ -0,0 +1,79 @@ | |||
{{template "base/head" .}} | |||
<div class="user bindphone forgot password" style="margin-top: 20px;"> | |||
<div class="ui middle very relaxed page grid"> | |||
<div class="column"> | |||
<form class="ui form ignore-dirty" action="" method=""> | |||
{{.CsrfTokenHtml}} | |||
<h2 class="ui top attached header"> | |||
{{.i18n.Tr "phone.please_bind_your_mobile_number"}} | |||
</h2> | |||
<div class="ui attached segment"> | |||
{{template "base/alert" .}} | |||
<div style="display:none;" class="ui negative message"> | |||
<p></p> | |||
</div> | |||
{{if .EnablePhone }} | |||
<div style="display:flex;justify-content:center;"> | |||
<div class="use-type" usetype="2" autofocus="true" style="width:491px;" showlabel="true" > | |||
{{template "user/auth/phone_verify" .}} | |||
</div> | |||
</div> | |||
<style> | |||
.use-type ._label-c { | |||
display: flex; | |||
} | |||
</style> | |||
{{end}} | |||
<div class="ui divider"></div> | |||
<div class="inline field"> | |||
<label></label> | |||
<button class="ui blue button">{{.i18n.Tr "phone.submit"}}</button> | |||
</div> | |||
</div> | |||
</form> | |||
<script> | |||
(function() { | |||
window.addEventListener('load', function () { | |||
var bindPhoneEl = $('.bindphone'); | |||
bindPhoneEl.find('button.button').off('click').on('click', function(e) { | |||
var phoneNumber = bindPhoneEl.find('input.phoneNumber').val(); | |||
var verifyCode = bindPhoneEl.find('input.verifyCode').val(); | |||
if (phoneNumber && verifyCode) { | |||
e.preventDefault(); | |||
if (!/^1[3578]\d{9}$/.test(phoneNumber)) { | |||
bindPhoneEl.find('.ui.negative.message').show().find('p').text({{.i18n.Tr "phone.please_enter_the_correct_mobile_number"}}); | |||
return; | |||
} | |||
if (!/^\d{6}$/.test(verifyCode)) { | |||
bindPhoneEl.find('.ui.negative.message').show().find('p').text({{.i18n.Tr "phone.please_enter_the_correct_mobile_phone_verification_code"}}); | |||
return; | |||
} | |||
$.ajax({ | |||
url: '/bindPhone', | |||
type: 'post', | |||
dataType: 'json', | |||
data: { | |||
_csrf: bindPhoneEl.find('input[name="_csrf"]').val(), | |||
phone_number: phoneNumber, | |||
verify_code: verifyCode | |||
}, | |||
success: function(res) { | |||
if (res && res.Code === 0) { | |||
window.location.href = '/dashboard'; | |||
} else { | |||
bindPhoneEl.find('.ui.negative.message').show().find('p').text(res.Message); | |||
} | |||
}, | |||
error: function(err) { | |||
console.log(err); | |||
} | |||
}); | |||
} | |||
}); | |||
}); | |||
})(); | |||
</script> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -1,4 +1,14 @@ | |||
{{template "base/head" .}} | |||
<div class="ui secondary pointing tabular top attached borderless menu new-menu navbar"> | |||
<a class="active item" rel="nofollow" href="{{AppSubUrl}}/user/forgot_password"> | |||
{{.i18n.Tr "phone.email_retrieve_password"}} | |||
</a> | |||
{{if .EnablePhone }} | |||
<a class="item" rel="nofollow" href="{{AppSubUrl}}/user/forgot_password?type=phone"> | |||
{{.i18n.Tr "phone.mobile_number_retrieve_password"}} | |||
</a> | |||
{{end}} | |||
</div> | |||
<div class="user forgot password"> | |||
<div class="ui middle very relaxed page grid"> | |||
<div class="column"> | |||
@@ -14,8 +24,9 @@ | |||
{{else if .IsResetRequest}} | |||
<div class="required inline field {{if .Err_Email}}error{{end}}"> | |||
<label for="email">{{.i18n.Tr "email"}}</label> | |||
<input id="email" name="email" type="email" value="{{.Email}}" autofocus required> | |||
<input id="email" name="email" type="email" value="{{.Email}}" autofocus required placeholder="{{.i18n.Tr "auth.please_enter_main_email"}}"> | |||
</div> | |||
<div style="text-align:center;">{{.i18n.Tr "auth.please_enter_main_email_tips"}}</div> | |||
<div class="ui divider"></div> | |||
<div class="inline field"> | |||
<label></label> | |||
@@ -0,0 +1,45 @@ | |||
{{template "base/head" .}} | |||
<div class="ui secondary pointing tabular top attached borderless menu new-menu navbar"> | |||
<a class="item" rel="nofollow" href="{{AppSubUrl}}/user/forgot_password"> | |||
{{.i18n.Tr "phone.email_retrieve_password"}} | |||
</a> | |||
<a class="active item" rel="nofollow" href="{{AppSubUrl}}/user/forgot_password?type=phone"> | |||
{{.i18n.Tr "phone.mobile_number_retrieve_password"}} | |||
</a> | |||
</div> | |||
<div class="user forgot password"> | |||
<div class="ui middle very relaxed page grid"> | |||
<div class="column"> | |||
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/user/recover_account_by_phone" method="post"> | |||
{{.CsrfTokenHtml}} | |||
<h2 class="ui top attached header"> | |||
{{.i18n.Tr "auth.forgot_password_title"}} | |||
</h2> | |||
<div class="ui attached segment"> | |||
{{template "base/alert" .}} | |||
<div style="display:none;" class="ui negative message"> | |||
<p></p> | |||
</div> | |||
{{if .EnablePhone }} | |||
<div style="display:flex;justify-content:center;"> | |||
<div class="use-type" usetype="3" style="width:491px;" showlabel="true" shownewpwd="true" autofocus="true"> | |||
{{template "user/auth/phone_verify" .}} | |||
</div> | |||
</div> | |||
<style> | |||
.use-type ._label-c, .use-type .new-pass-word-wrap { | |||
display: flex; | |||
} | |||
</style> | |||
{{end}} | |||
<div class="ui divider"></div> | |||
<div class="inline field"> | |||
<label></label> | |||
<button class="ui blue button">{{.i18n.Tr "phone.submit"}}</button> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -0,0 +1,75 @@ | |||
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/_phoneverify.css?v={{MD5 AppVer}}" /> | |||
<div class="__phone-verify-code"> | |||
<div class="phone-c"> | |||
<div class="phone-label _label-c required"> | |||
<span class="_label">{{.i18n.Tr "phone.phone_number"}}</span> | |||
</div> | |||
<div class="phone-area-c"> | |||
<select value="+86"> | |||
<option value="+86">+86</option> | |||
</select> | |||
</div> | |||
<div class="field phone-num-c"> | |||
<input class="phoneNumber" style="width:100% !important" name="phone_number" value="{{.phone_number}}" placeholder="{{.i18n.Tr "phone.phone_number"}}" required autocomplete="off" /> | |||
<div class="modify-phone-number"><div style="display:flex;align-items:center;height:100%;"><a>{{.i18n.Tr "phone.modify_phone_number"}}</a></div></div> | |||
</div> | |||
</div> | |||
<div class="slide-bar-wrap"> | |||
<div class="slide-bar-label _label-c required" style=""></div> | |||
<div class="slide-bar-c" style="flex:1;"> | |||
<div class="slide-bar-bg"> | |||
<div class="slide-txt">{{.i18n.Tr "phone.drag_the_slider_to_fill_the_puzzle"}}</div> | |||
<div class="slide-bar"></div> | |||
<div class="slide-trigger"> | |||
<i class="arrow right icon"></i> | |||
<i class="check icon" style="display:none;"></i> | |||
<i class="close icon" style="display:none;"></i> | |||
</div> | |||
</div> | |||
<div class="slide-image-big"> | |||
<div class="slide-image-small" style="top:{{.SlideImageInfo.ImageY}}px"></div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="verify-code-c"> | |||
<div class="verify-code-label _label-c required"> | |||
<span class="_label">{{.i18n.Tr "phone.mobile_phone_verification_code"}}</span> | |||
</div> | |||
<div class="verify-code-num-c"> | |||
<input class="verifyCode" style="width:100% !important" name="verify_code" value="{{.verify_code}}" placeholder="{{.i18n.Tr "phone.please_enter_SMS_verification_code"}}" required autocomplete="off" /> | |||
</div> | |||
<div class="verify-code-send"> | |||
<div class="verify-code-send-btn __disabled">{{.i18n.Tr "phone.get_verification_code"}}</div> | |||
</div> | |||
</div> | |||
<div class="new-pass-word-wrap"> | |||
<div class="new-pass-word-label _label-c required"> | |||
<span class="_label">{{.i18n.Tr "phone.new_login_password"}}</span> | |||
</div> | |||
<div class="new-pass-word-c"> | |||
<input class="newPassword" style="width:100% !important" name="password" type="password" placeholder="{{.i18n.Tr "phone.please_enter_new_password"}}" required autocomplete="off" /> | |||
</div> | |||
</div> | |||
<div class="new-pass-word-wrap"> | |||
<div class="new-pass-word-label _label-c required"></div> | |||
<div class="field" style="flex:1;"> | |||
<div class="ui checkbox"> | |||
<label>{{.i18n.Tr "auth.remember_me"}}</label> | |||
<input name="remember" type="checkbox"> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<script src="{{StaticUrlPrefix}}/js/phoneverify.js?v={{MD5 AppVer}}" type="text/javascript"></script> | |||
<script> | |||
(function(){ | |||
window.addEventListener('load', function () { | |||
var phoneVerifyCode = new PhoneVerifyCode($('.__phone-verify-code'), { | |||
Lang: { | |||
second_resend: {{.i18n.Tr "phone.second_resend"}}, | |||
get_verification_code: {{.i18n.Tr "phone.get_verification_code"}}, | |||
}, | |||
}); | |||
}); | |||
})(); | |||
</script> |
@@ -38,7 +38,7 @@ | |||
<div class="field"> | |||
<div class="ui left icon input {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}"> | |||
<i class="user icon"></i> | |||
<input id="user_name" name="user_name" value="{{.user_name}}" placeholder="{{.i18n.Tr "home.uname_holder"}}" autofocus required> | |||
<input id="user_name" name="user_name" value="{{.user_name}}" placeholder="{{.i18n.Tr "home.login_uname_holder"}}" autofocus required> | |||
</div> | |||
</div> | |||
{{if or (not .DisablePassword) .LinkAccountMode}} | |||
@@ -1,10 +1,15 @@ | |||
{{if or .EnableOpenIDSignIn .EnableSSPI .EnableCloudBrain}} | |||
<div class="ui secondary pointing tabular top attached borderless menu new-menu navbar"> | |||
{{if .EnablePhone }} | |||
<a class="{{if .PageIsPhoneLogin}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/phone"> | |||
{{.i18n.Tr "phone.mobile_login"}} | |||
</a> | |||
{{end}} | |||
<a class="{{if .PageIsLogin}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login"> | |||
{{.i18n.Tr "auth.login_userpass"}} | |||
{{.i18n.Tr "phone.account_password_login"}} | |||
</a> | |||
<a class="{{if .PageIsCloudBrainLogin}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/cloud_brain"> | |||
{{.i18n.Tr "auth.login_cloudbrain"}} | |||
{{.i18n.Tr "phone.cloud_brain_user_login"}} | |||
</a> | |||
{{if .EnableOpenIDSignIn}} | |||
<a class="{{if .PageIsLoginOpenID}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/openid"> | |||
@@ -0,0 +1,73 @@ | |||
{{template "base/head" .}} | |||
<div class="user signin"> | |||
{{template "user/auth/signin_navbar" .}} | |||
<div class="ui container"> | |||
<div class="ui raised very padded text container segment"> | |||
<style> | |||
.full.height{background-color: #F9F9F9;} | |||
.ui.left:not(.action){ float:none;} | |||
.ui.left{ float:none;} | |||
.ui.secondary.pointing.menu{ border-bottom:none;} | |||
</style> | |||
{{template "base/alert" .}} | |||
<div class="ui negative message" style="display:none;"> | |||
<p></p> | |||
</div> | |||
<div class="ui centered grid"> | |||
<div class="sixteen wide mobile ten wide tablet ten wide computer column"> | |||
<div class="ui bottom aligned two column grid"> | |||
<div class="column"> | |||
<h2 class="ui header"> | |||
{{if .LinkAccountMode}} | |||
{{.i18n.Tr "auth.oauth_signin_title"}} | |||
{{else}} | |||
{{.i18n.Tr "auth.login_userpass"}} | |||
{{end}} | |||
</h2> | |||
</div> | |||
{{if .ShowRegistrationButton}} | |||
<div class="ui right floated column"> | |||
<a href="{{AppSubUrl}}/user/sign_up">{{.i18n.Tr "auth.sign_up_now" | Str2html}}</a> | |||
</div> | |||
{{end}} | |||
</div> | |||
<div class="ui grid"> | |||
<div class="column"> | |||
<form class="ui form" action="/user/login/phone" method="post"> | |||
{{.CsrfTokenHtml}} | |||
{{if .EnablePhone }} | |||
<div class="use-type" usetype="1" autofocus="true"> | |||
{{template "user/auth/phone_verify" .}} | |||
</div> | |||
{{end}} | |||
<div class="two fields inline"> | |||
<div class="field"> | |||
<div class="ui checkbox"> | |||
<label>{{.i18n.Tr "auth.remember_me"}}</label> | |||
<input name="remember" type="checkbox"> | |||
</div> | |||
</div> | |||
<div class="field" style="padding-right: 0; text-align: right;"> | |||
<a href="{{AppSubUrl}}/user/forgot_password?type=phone">{{.i18n.Tr "auth.forgot_password"}}</a> | |||
</div> | |||
</div> | |||
<div class="ui hidden divider"></div> | |||
<div class="center aligned field"> | |||
<button class="fluid large ui blue button"> | |||
{{.i18n.Tr "sign_in"}} | |||
</button> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -29,7 +29,9 @@ | |||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}} | |||
{{template "base/alert" .}} | |||
{{end}} | |||
<div class="ui negative message" style="display:none;"> | |||
<p></p> | |||
</div> | |||
{{if .DisableRegistration}} | |||
<p>{{.i18n.Tr "auth.disable_register_prompt"}}</p> | |||
{{else}} | |||
@@ -64,6 +66,12 @@ | |||
</div> | |||
{{end}} | |||
{{if .EnablePhone }} | |||
<div class="use-type" usetype="0"> | |||
{{template "user/auth/phone_verify" .}} | |||
</div> | |||
{{end}} | |||
<div class="ui hidden divider"></div> | |||
<div class="center aligned field"> | |||
@@ -1,5 +1,5 @@ | |||
{{template "base/head" .}} | |||
<div class="user settings organization"> | |||
<div class="user settings organization" style="padding-top:15px;"> | |||
{{template "user/settings/navbar" .}} | |||
<div class="ui container"> | |||
{{template "base/alert" .}} | |||
@@ -4,6 +4,9 @@ | |||
<div class="alert" style="top: 0;"></div> | |||
<div class="ui container"> | |||
{{template "base/alert" .}} | |||
<div style="display:none;" class="ui negative message"> | |||
<p></p> | |||
</div> | |||
<h4 class="ui top attached header"> | |||
{{.i18n.Tr "settings.public_profile"}} | |||
</h4> | |||
@@ -29,10 +32,22 @@ | |||
</div> | |||
<div class="inline field"> | |||
<div class="ui checkbox" id="keep-email-private"> | |||
<label class="poping up" data-content="{{.i18n.Tr "settings.keep_email_private_popup"}}"><strong>{{.i18n.Tr "settings.keep_email_private"}}</strong></label> | |||
<label class="poping up" data-content="{{.i18n.Tr "settings.keep_email_private_popup"}}"><strong style="font-weight:500;">{{.i18n.Tr "settings.keep_email_private"}}</strong></label> | |||
<input name="keep_email_private" type="checkbox" {{if .SignedUser.KeepEmailPrivate}}checked{{end}}> | |||
</div> | |||
</div> | |||
{{if .EnablePhone }} | |||
<div class="field required" style="width:391px;"> | |||
<label for="phone">{{.i18n.Tr "phone.phone_number"}}</label> | |||
<div class="use-type" usetype="2" ophonenumber="{{.SignedUser.PhoneNumber}}" readonly="true" verifycodenorequired="true"> | |||
{{template "user/auth/phone_verify" .}} | |||
</div> | |||
</div> | |||
<style> | |||
.use-type .modify-phone-number { display: flex;} | |||
.use-type .slide-bar-wrap, .use-type .verify-code-c{ display: none; } | |||
</style> | |||
{{end}} | |||
<div class="field {{if .Err_Description}}error{{end}}"> | |||
<label for="description">{{$.i18n.Tr "user.user_bio"}}</label> | |||
<textarea id="description" name="description" rows="2" maxlength="255">{{.SignedUser.Description}}</textarea> | |||
@@ -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 (c) 2009-present, Alibaba Cloud All rights reserved. | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. |
@@ -0,0 +1,305 @@ | |||
// This file is auto-generated, don't edit it. Thanks. | |||
package client | |||
import ( | |||
"io" | |||
"github.com/alibabacloud-go/tea/tea" | |||
credential "github.com/aliyun/credentials-go/credentials" | |||
) | |||
type InterceptorContext struct { | |||
Request *InterceptorContextRequest `json:"request,omitempty" xml:"request,omitempty" require:"true" type:"Struct"` | |||
Configuration *InterceptorContextConfiguration `json:"configuration,omitempty" xml:"configuration,omitempty" require:"true" type:"Struct"` | |||
Response *InterceptorContextResponse `json:"response,omitempty" xml:"response,omitempty" require:"true" type:"Struct"` | |||
} | |||
func (s InterceptorContext) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s InterceptorContext) GoString() string { | |||
return s.String() | |||
} | |||
func (s *InterceptorContext) SetRequest(v *InterceptorContextRequest) *InterceptorContext { | |||
s.Request = v | |||
return s | |||
} | |||
func (s *InterceptorContext) SetConfiguration(v *InterceptorContextConfiguration) *InterceptorContext { | |||
s.Configuration = v | |||
return s | |||
} | |||
func (s *InterceptorContext) SetResponse(v *InterceptorContextResponse) *InterceptorContext { | |||
s.Response = v | |||
return s | |||
} | |||
type InterceptorContextRequest struct { | |||
Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty"` | |||
Query map[string]*string `json:"query,omitempty" xml:"query,omitempty"` | |||
Body interface{} `json:"body,omitempty" xml:"body,omitempty"` | |||
Stream io.Reader `json:"stream,omitempty" xml:"stream,omitempty"` | |||
HostMap map[string]*string `json:"hostMap,omitempty" xml:"hostMap,omitempty"` | |||
Pathname *string `json:"pathname,omitempty" xml:"pathname,omitempty" require:"true"` | |||
ProductId *string `json:"productId,omitempty" xml:"productId,omitempty" require:"true"` | |||
Action *string `json:"action,omitempty" xml:"action,omitempty" require:"true"` | |||
Version *string `json:"version,omitempty" xml:"version,omitempty" require:"true"` | |||
Protocol *string `json:"protocol,omitempty" xml:"protocol,omitempty" require:"true"` | |||
Method *string `json:"method,omitempty" xml:"method,omitempty" require:"true"` | |||
AuthType *string `json:"authType,omitempty" xml:"authType,omitempty" require:"true"` | |||
BodyType *string `json:"bodyType,omitempty" xml:"bodyType,omitempty" require:"true"` | |||
ReqBodyType *string `json:"reqBodyType,omitempty" xml:"reqBodyType,omitempty" require:"true"` | |||
Style *string `json:"style,omitempty" xml:"style,omitempty"` | |||
Credential credential.Credential `json:"credential,omitempty" xml:"credential,omitempty" require:"true"` | |||
SignatureVersion *string `json:"signatureVersion,omitempty" xml:"signatureVersion,omitempty"` | |||
SignatureAlgorithm *string `json:"signatureAlgorithm,omitempty" xml:"signatureAlgorithm,omitempty"` | |||
UserAgent *string `json:"userAgent,omitempty" xml:"userAgent,omitempty" require:"true"` | |||
} | |||
func (s InterceptorContextRequest) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s InterceptorContextRequest) GoString() string { | |||
return s.String() | |||
} | |||
func (s *InterceptorContextRequest) SetHeaders(v map[string]*string) *InterceptorContextRequest { | |||
s.Headers = v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetQuery(v map[string]*string) *InterceptorContextRequest { | |||
s.Query = v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetBody(v interface{}) *InterceptorContextRequest { | |||
s.Body = v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetStream(v io.Reader) *InterceptorContextRequest { | |||
s.Stream = v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetHostMap(v map[string]*string) *InterceptorContextRequest { | |||
s.HostMap = v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetPathname(v string) *InterceptorContextRequest { | |||
s.Pathname = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetProductId(v string) *InterceptorContextRequest { | |||
s.ProductId = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetAction(v string) *InterceptorContextRequest { | |||
s.Action = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetVersion(v string) *InterceptorContextRequest { | |||
s.Version = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetProtocol(v string) *InterceptorContextRequest { | |||
s.Protocol = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetMethod(v string) *InterceptorContextRequest { | |||
s.Method = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetAuthType(v string) *InterceptorContextRequest { | |||
s.AuthType = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetBodyType(v string) *InterceptorContextRequest { | |||
s.BodyType = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetReqBodyType(v string) *InterceptorContextRequest { | |||
s.ReqBodyType = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetStyle(v string) *InterceptorContextRequest { | |||
s.Style = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetCredential(v credential.Credential) *InterceptorContextRequest { | |||
s.Credential = v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetSignatureVersion(v string) *InterceptorContextRequest { | |||
s.SignatureVersion = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetSignatureAlgorithm(v string) *InterceptorContextRequest { | |||
s.SignatureAlgorithm = &v | |||
return s | |||
} | |||
func (s *InterceptorContextRequest) SetUserAgent(v string) *InterceptorContextRequest { | |||
s.UserAgent = &v | |||
return s | |||
} | |||
type InterceptorContextConfiguration struct { | |||
RegionId *string `json:"regionId,omitempty" xml:"regionId,omitempty" require:"true"` | |||
Endpoint *string `json:"endpoint,omitempty" xml:"endpoint,omitempty"` | |||
EndpointRule *string `json:"endpointRule,omitempty" xml:"endpointRule,omitempty"` | |||
EndpointMap map[string]*string `json:"endpointMap,omitempty" xml:"endpointMap,omitempty"` | |||
EndpointType *string `json:"endpointType,omitempty" xml:"endpointType,omitempty"` | |||
Network *string `json:"network,omitempty" xml:"network,omitempty"` | |||
Suffix *string `json:"suffix,omitempty" xml:"suffix,omitempty"` | |||
} | |||
func (s InterceptorContextConfiguration) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s InterceptorContextConfiguration) GoString() string { | |||
return s.String() | |||
} | |||
func (s *InterceptorContextConfiguration) SetRegionId(v string) *InterceptorContextConfiguration { | |||
s.RegionId = &v | |||
return s | |||
} | |||
func (s *InterceptorContextConfiguration) SetEndpoint(v string) *InterceptorContextConfiguration { | |||
s.Endpoint = &v | |||
return s | |||
} | |||
func (s *InterceptorContextConfiguration) SetEndpointRule(v string) *InterceptorContextConfiguration { | |||
s.EndpointRule = &v | |||
return s | |||
} | |||
func (s *InterceptorContextConfiguration) SetEndpointMap(v map[string]*string) *InterceptorContextConfiguration { | |||
s.EndpointMap = v | |||
return s | |||
} | |||
func (s *InterceptorContextConfiguration) SetEndpointType(v string) *InterceptorContextConfiguration { | |||
s.EndpointType = &v | |||
return s | |||
} | |||
func (s *InterceptorContextConfiguration) SetNetwork(v string) *InterceptorContextConfiguration { | |||
s.Network = &v | |||
return s | |||
} | |||
func (s *InterceptorContextConfiguration) SetSuffix(v string) *InterceptorContextConfiguration { | |||
s.Suffix = &v | |||
return s | |||
} | |||
type InterceptorContextResponse struct { | |||
StatusCode *int `json:"statusCode,omitempty" xml:"statusCode,omitempty"` | |||
Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty"` | |||
Body io.Reader `json:"body,omitempty" xml:"body,omitempty"` | |||
DeserializedBody interface{} `json:"deserializedBody,omitempty" xml:"deserializedBody,omitempty"` | |||
} | |||
func (s InterceptorContextResponse) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s InterceptorContextResponse) GoString() string { | |||
return s.String() | |||
} | |||
func (s *InterceptorContextResponse) SetStatusCode(v int) *InterceptorContextResponse { | |||
s.StatusCode = &v | |||
return s | |||
} | |||
func (s *InterceptorContextResponse) SetHeaders(v map[string]*string) *InterceptorContextResponse { | |||
s.Headers = v | |||
return s | |||
} | |||
func (s *InterceptorContextResponse) SetBody(v io.Reader) *InterceptorContextResponse { | |||
s.Body = v | |||
return s | |||
} | |||
func (s *InterceptorContextResponse) SetDeserializedBody(v interface{}) *InterceptorContextResponse { | |||
s.DeserializedBody = v | |||
return s | |||
} | |||
type AttributeMap struct { | |||
Attributes map[string]interface{} `json:"attributes,omitempty" xml:"attributes,omitempty" require:"true"` | |||
Key map[string]*string `json:"key,omitempty" xml:"key,omitempty" require:"true"` | |||
} | |||
func (s AttributeMap) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s AttributeMap) GoString() string { | |||
return s.String() | |||
} | |||
func (s *AttributeMap) SetAttributes(v map[string]interface{}) *AttributeMap { | |||
s.Attributes = v | |||
return s | |||
} | |||
func (s *AttributeMap) SetKey(v map[string]*string) *AttributeMap { | |||
s.Key = v | |||
return s | |||
} | |||
type ClientInterface interface { | |||
ModifyConfiguration(context *InterceptorContext, attributeMap *AttributeMap) error | |||
ModifyRequest(context *InterceptorContext, attributeMap *AttributeMap) error | |||
ModifyResponse(context *InterceptorContext, attributeMap *AttributeMap) error | |||
} | |||
type Client struct { | |||
} | |||
func NewClient() (*Client, error) { | |||
client := new(Client) | |||
err := client.Init() | |||
return client, err | |||
} | |||
func (client *Client) Init() (_err error) { | |||
return nil | |||
} | |||
func (client *Client) ModifyConfiguration(context *InterceptorContext, attributeMap *AttributeMap) (_err error) { | |||
panic("No Support!") | |||
} | |||
func (client *Client) ModifyRequest(context *InterceptorContext, attributeMap *AttributeMap) (_err error) { | |||
panic("No Support!") | |||
} | |||
func (client *Client) ModifyResponse(context *InterceptorContext, attributeMap *AttributeMap) (_err error) { | |||
panic("No Support!") | |||
} |
@@ -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 (c) 2009-present, Alibaba Cloud All rights reserved. | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. |
@@ -0,0 +1,1623 @@ | |||
// This file is auto-generated, don't edit it. Thanks. | |||
/** | |||
* This is for OpenApi SDK | |||
*/ | |||
package client | |||
import ( | |||
"io" | |||
spi "github.com/alibabacloud-go/alibabacloud-gateway-spi/client" | |||
openapiutil "github.com/alibabacloud-go/openapi-util/service" | |||
util "github.com/alibabacloud-go/tea-utils/service" | |||
xml "github.com/alibabacloud-go/tea-xml/service" | |||
"github.com/alibabacloud-go/tea/tea" | |||
credential "github.com/aliyun/credentials-go/credentials" | |||
) | |||
/** | |||
* Model for initing client | |||
*/ | |||
type Config struct { | |||
// accesskey id | |||
AccessKeyId *string `json:"accessKeyId,omitempty" xml:"accessKeyId,omitempty"` | |||
// accesskey secret | |||
AccessKeySecret *string `json:"accessKeySecret,omitempty" xml:"accessKeySecret,omitempty"` | |||
// security token | |||
SecurityToken *string `json:"securityToken,omitempty" xml:"securityToken,omitempty"` | |||
// http protocol | |||
Protocol *string `json:"protocol,omitempty" xml:"protocol,omitempty"` | |||
// http method | |||
Method *string `json:"method,omitempty" xml:"method,omitempty"` | |||
// region id | |||
RegionId *string `json:"regionId,omitempty" xml:"regionId,omitempty"` | |||
// read timeout | |||
ReadTimeout *int `json:"readTimeout,omitempty" xml:"readTimeout,omitempty"` | |||
// connect timeout | |||
ConnectTimeout *int `json:"connectTimeout,omitempty" xml:"connectTimeout,omitempty"` | |||
// http proxy | |||
HttpProxy *string `json:"httpProxy,omitempty" xml:"httpProxy,omitempty"` | |||
// https proxy | |||
HttpsProxy *string `json:"httpsProxy,omitempty" xml:"httpsProxy,omitempty"` | |||
// credential | |||
Credential credential.Credential `json:"credential,omitempty" xml:"credential,omitempty"` | |||
// endpoint | |||
Endpoint *string `json:"endpoint,omitempty" xml:"endpoint,omitempty"` | |||
// proxy white list | |||
NoProxy *string `json:"noProxy,omitempty" xml:"noProxy,omitempty"` | |||
// max idle conns | |||
MaxIdleConns *int `json:"maxIdleConns,omitempty" xml:"maxIdleConns,omitempty"` | |||
// network for endpoint | |||
Network *string `json:"network,omitempty" xml:"network,omitempty"` | |||
// user agent | |||
UserAgent *string `json:"userAgent,omitempty" xml:"userAgent,omitempty"` | |||
// suffix for endpoint | |||
Suffix *string `json:"suffix,omitempty" xml:"suffix,omitempty"` | |||
// socks5 proxy | |||
Socks5Proxy *string `json:"socks5Proxy,omitempty" xml:"socks5Proxy,omitempty"` | |||
// socks5 network | |||
Socks5NetWork *string `json:"socks5NetWork,omitempty" xml:"socks5NetWork,omitempty"` | |||
// endpoint type | |||
EndpointType *string `json:"endpointType,omitempty" xml:"endpointType,omitempty"` | |||
// OpenPlatform endpoint | |||
OpenPlatformEndpoint *string `json:"openPlatformEndpoint,omitempty" xml:"openPlatformEndpoint,omitempty"` | |||
// Deprecated | |||
// credential type | |||
Type *string `json:"type,omitempty" xml:"type,omitempty"` | |||
// Signature Version | |||
SignatureVersion *string `json:"signatureVersion,omitempty" xml:"signatureVersion,omitempty"` | |||
// Signature Algorithm | |||
SignatureAlgorithm *string `json:"signatureAlgorithm,omitempty" xml:"signatureAlgorithm,omitempty"` | |||
} | |||
func (s Config) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s Config) GoString() string { | |||
return s.String() | |||
} | |||
func (s *Config) SetAccessKeyId(v string) *Config { | |||
s.AccessKeyId = &v | |||
return s | |||
} | |||
func (s *Config) SetAccessKeySecret(v string) *Config { | |||
s.AccessKeySecret = &v | |||
return s | |||
} | |||
func (s *Config) SetSecurityToken(v string) *Config { | |||
s.SecurityToken = &v | |||
return s | |||
} | |||
func (s *Config) SetProtocol(v string) *Config { | |||
s.Protocol = &v | |||
return s | |||
} | |||
func (s *Config) SetMethod(v string) *Config { | |||
s.Method = &v | |||
return s | |||
} | |||
func (s *Config) SetRegionId(v string) *Config { | |||
s.RegionId = &v | |||
return s | |||
} | |||
func (s *Config) SetReadTimeout(v int) *Config { | |||
s.ReadTimeout = &v | |||
return s | |||
} | |||
func (s *Config) SetConnectTimeout(v int) *Config { | |||
s.ConnectTimeout = &v | |||
return s | |||
} | |||
func (s *Config) SetHttpProxy(v string) *Config { | |||
s.HttpProxy = &v | |||
return s | |||
} | |||
func (s *Config) SetHttpsProxy(v string) *Config { | |||
s.HttpsProxy = &v | |||
return s | |||
} | |||
func (s *Config) SetCredential(v credential.Credential) *Config { | |||
s.Credential = v | |||
return s | |||
} | |||
func (s *Config) SetEndpoint(v string) *Config { | |||
s.Endpoint = &v | |||
return s | |||
} | |||
func (s *Config) SetNoProxy(v string) *Config { | |||
s.NoProxy = &v | |||
return s | |||
} | |||
func (s *Config) SetMaxIdleConns(v int) *Config { | |||
s.MaxIdleConns = &v | |||
return s | |||
} | |||
func (s *Config) SetNetwork(v string) *Config { | |||
s.Network = &v | |||
return s | |||
} | |||
func (s *Config) SetUserAgent(v string) *Config { | |||
s.UserAgent = &v | |||
return s | |||
} | |||
func (s *Config) SetSuffix(v string) *Config { | |||
s.Suffix = &v | |||
return s | |||
} | |||
func (s *Config) SetSocks5Proxy(v string) *Config { | |||
s.Socks5Proxy = &v | |||
return s | |||
} | |||
func (s *Config) SetSocks5NetWork(v string) *Config { | |||
s.Socks5NetWork = &v | |||
return s | |||
} | |||
func (s *Config) SetEndpointType(v string) *Config { | |||
s.EndpointType = &v | |||
return s | |||
} | |||
func (s *Config) SetOpenPlatformEndpoint(v string) *Config { | |||
s.OpenPlatformEndpoint = &v | |||
return s | |||
} | |||
func (s *Config) SetType(v string) *Config { | |||
s.Type = &v | |||
return s | |||
} | |||
func (s *Config) SetSignatureVersion(v string) *Config { | |||
s.SignatureVersion = &v | |||
return s | |||
} | |||
func (s *Config) SetSignatureAlgorithm(v string) *Config { | |||
s.SignatureAlgorithm = &v | |||
return s | |||
} | |||
type OpenApiRequest struct { | |||
Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty"` | |||
Query map[string]*string `json:"query,omitempty" xml:"query,omitempty"` | |||
Body interface{} `json:"body,omitempty" xml:"body,omitempty"` | |||
Stream io.Reader `json:"stream,omitempty" xml:"stream,omitempty"` | |||
HostMap map[string]*string `json:"hostMap,omitempty" xml:"hostMap,omitempty"` | |||
EndpointOverride *string `json:"endpointOverride,omitempty" xml:"endpointOverride,omitempty"` | |||
} | |||
func (s OpenApiRequest) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s OpenApiRequest) GoString() string { | |||
return s.String() | |||
} | |||
func (s *OpenApiRequest) SetHeaders(v map[string]*string) *OpenApiRequest { | |||
s.Headers = v | |||
return s | |||
} | |||
func (s *OpenApiRequest) SetQuery(v map[string]*string) *OpenApiRequest { | |||
s.Query = v | |||
return s | |||
} | |||
func (s *OpenApiRequest) SetBody(v interface{}) *OpenApiRequest { | |||
s.Body = v | |||
return s | |||
} | |||
func (s *OpenApiRequest) SetStream(v io.Reader) *OpenApiRequest { | |||
s.Stream = v | |||
return s | |||
} | |||
func (s *OpenApiRequest) SetHostMap(v map[string]*string) *OpenApiRequest { | |||
s.HostMap = v | |||
return s | |||
} | |||
func (s *OpenApiRequest) SetEndpointOverride(v string) *OpenApiRequest { | |||
s.EndpointOverride = &v | |||
return s | |||
} | |||
type Params struct { | |||
Action *string `json:"action,omitempty" xml:"action,omitempty" require:"true"` | |||
Version *string `json:"version,omitempty" xml:"version,omitempty" require:"true"` | |||
Protocol *string `json:"protocol,omitempty" xml:"protocol,omitempty" require:"true"` | |||
Pathname *string `json:"pathname,omitempty" xml:"pathname,omitempty" require:"true"` | |||
Method *string `json:"method,omitempty" xml:"method,omitempty" require:"true"` | |||
AuthType *string `json:"authType,omitempty" xml:"authType,omitempty" require:"true"` | |||
BodyType *string `json:"bodyType,omitempty" xml:"bodyType,omitempty" require:"true"` | |||
ReqBodyType *string `json:"reqBodyType,omitempty" xml:"reqBodyType,omitempty" require:"true"` | |||
Style *string `json:"style,omitempty" xml:"style,omitempty"` | |||
} | |||
func (s Params) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s Params) GoString() string { | |||
return s.String() | |||
} | |||
func (s *Params) SetAction(v string) *Params { | |||
s.Action = &v | |||
return s | |||
} | |||
func (s *Params) SetVersion(v string) *Params { | |||
s.Version = &v | |||
return s | |||
} | |||
func (s *Params) SetProtocol(v string) *Params { | |||
s.Protocol = &v | |||
return s | |||
} | |||
func (s *Params) SetPathname(v string) *Params { | |||
s.Pathname = &v | |||
return s | |||
} | |||
func (s *Params) SetMethod(v string) *Params { | |||
s.Method = &v | |||
return s | |||
} | |||
func (s *Params) SetAuthType(v string) *Params { | |||
s.AuthType = &v | |||
return s | |||
} | |||
func (s *Params) SetBodyType(v string) *Params { | |||
s.BodyType = &v | |||
return s | |||
} | |||
func (s *Params) SetReqBodyType(v string) *Params { | |||
s.ReqBodyType = &v | |||
return s | |||
} | |||
func (s *Params) SetStyle(v string) *Params { | |||
s.Style = &v | |||
return s | |||
} | |||
type Client struct { | |||
Endpoint *string | |||
RegionId *string | |||
Protocol *string | |||
Method *string | |||
UserAgent *string | |||
EndpointRule *string | |||
EndpointMap map[string]*string | |||
Suffix *string | |||
ReadTimeout *int | |||
ConnectTimeout *int | |||
HttpProxy *string | |||
HttpsProxy *string | |||
Socks5Proxy *string | |||
Socks5NetWork *string | |||
NoProxy *string | |||
Network *string | |||
ProductId *string | |||
MaxIdleConns *int | |||
EndpointType *string | |||
OpenPlatformEndpoint *string | |||
Credential credential.Credential | |||
SignatureVersion *string | |||
SignatureAlgorithm *string | |||
Headers map[string]*string | |||
Spi spi.ClientInterface | |||
} | |||
/** | |||
* Init client with Config | |||
* @param config config contains the necessary information to create a client | |||
*/ | |||
func NewClient(config *Config) (*Client, error) { | |||
client := new(Client) | |||
err := client.Init(config) | |||
return client, err | |||
} | |||
func (client *Client) Init(config *Config) (_err error) { | |||
if tea.BoolValue(util.IsUnset(tea.ToMap(config))) { | |||
_err = tea.NewSDKError(map[string]interface{}{ | |||
"code": "ParameterMissing", | |||
"message": "'config' can not be unset", | |||
}) | |||
return _err | |||
} | |||
if !tea.BoolValue(util.Empty(config.AccessKeyId)) && !tea.BoolValue(util.Empty(config.AccessKeySecret)) { | |||
if !tea.BoolValue(util.Empty(config.SecurityToken)) { | |||
config.Type = tea.String("sts") | |||
} else { | |||
config.Type = tea.String("access_key") | |||
} | |||
credentialConfig := &credential.Config{ | |||
AccessKeyId: config.AccessKeyId, | |||
Type: config.Type, | |||
AccessKeySecret: config.AccessKeySecret, | |||
SecurityToken: config.SecurityToken, | |||
} | |||
client.Credential, _err = credential.NewCredential(credentialConfig) | |||
if _err != nil { | |||
return _err | |||
} | |||
} else if !tea.BoolValue(util.IsUnset(config.Credential)) { | |||
client.Credential = config.Credential | |||
} | |||
client.Endpoint = config.Endpoint | |||
client.EndpointType = config.EndpointType | |||
client.Network = config.Network | |||
client.Suffix = config.Suffix | |||
client.Protocol = config.Protocol | |||
client.Method = config.Method | |||
client.RegionId = config.RegionId | |||
client.UserAgent = config.UserAgent | |||
client.ReadTimeout = config.ReadTimeout | |||
client.ConnectTimeout = config.ConnectTimeout | |||
client.HttpProxy = config.HttpProxy | |||
client.HttpsProxy = config.HttpsProxy | |||
client.NoProxy = config.NoProxy | |||
client.Socks5Proxy = config.Socks5Proxy | |||
client.Socks5NetWork = config.Socks5NetWork | |||
client.MaxIdleConns = config.MaxIdleConns | |||
client.SignatureVersion = config.SignatureVersion | |||
client.SignatureAlgorithm = config.SignatureAlgorithm | |||
return nil | |||
} | |||
/** | |||
* Encapsulate the request and invoke the network | |||
* @param action api name | |||
* @param version product version | |||
* @param protocol http or https | |||
* @param method e.g. GET | |||
* @param authType authorization type e.g. AK | |||
* @param bodyType response body type e.g. String | |||
* @param request object of OpenApiRequest | |||
* @param runtime which controls some details of call api, such as retry times | |||
* @return the response | |||
*/ | |||
func (client *Client) DoRPCRequest(action *string, version *string, protocol *string, method *string, authType *string, bodyType *string, request *OpenApiRequest, runtime *util.RuntimeOptions) (_result map[string]interface{}, _err error) { | |||
_err = tea.Validate(request) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_err = tea.Validate(runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_runtime := map[string]interface{}{ | |||
"timeouted": "retry", | |||
"readTimeout": tea.IntValue(util.DefaultNumber(runtime.ReadTimeout, client.ReadTimeout)), | |||
"connectTimeout": tea.IntValue(util.DefaultNumber(runtime.ConnectTimeout, client.ConnectTimeout)), | |||
"httpProxy": tea.StringValue(util.DefaultString(runtime.HttpProxy, client.HttpProxy)), | |||
"httpsProxy": tea.StringValue(util.DefaultString(runtime.HttpsProxy, client.HttpsProxy)), | |||
"noProxy": tea.StringValue(util.DefaultString(runtime.NoProxy, client.NoProxy)), | |||
"socks5Proxy": tea.StringValue(util.DefaultString(runtime.Socks5Proxy, client.Socks5Proxy)), | |||
"socks5NetWork": tea.StringValue(util.DefaultString(runtime.Socks5NetWork, client.Socks5NetWork)), | |||
"maxIdleConns": tea.IntValue(util.DefaultNumber(runtime.MaxIdleConns, client.MaxIdleConns)), | |||
"retry": map[string]interface{}{ | |||
"retryable": tea.BoolValue(runtime.Autoretry), | |||
"maxAttempts": tea.IntValue(util.DefaultNumber(runtime.MaxAttempts, tea.Int(3))), | |||
}, | |||
"backoff": map[string]interface{}{ | |||
"policy": tea.StringValue(util.DefaultString(runtime.BackoffPolicy, tea.String("no"))), | |||
"period": tea.IntValue(util.DefaultNumber(runtime.BackoffPeriod, tea.Int(1))), | |||
}, | |||
"ignoreSSL": tea.BoolValue(runtime.IgnoreSSL), | |||
} | |||
_resp := make(map[string]interface{}) | |||
for _retryTimes := 0; tea.BoolValue(tea.AllowRetry(_runtime["retry"], tea.Int(_retryTimes))); _retryTimes++ { | |||
if _retryTimes > 0 { | |||
_backoffTime := tea.GetBackoffTime(_runtime["backoff"], tea.Int(_retryTimes)) | |||
if tea.IntValue(_backoffTime) > 0 { | |||
tea.Sleep(_backoffTime) | |||
} | |||
} | |||
_resp, _err = func() (map[string]interface{}, error) { | |||
request_ := tea.NewRequest() | |||
request_.Protocol = util.DefaultString(client.Protocol, protocol) | |||
request_.Method = method | |||
request_.Pathname = tea.String("/") | |||
request_.Query = tea.Merge(map[string]*string{ | |||
"Action": action, | |||
"Format": tea.String("json"), | |||
"Version": version, | |||
"Timestamp": openapiutil.GetTimestamp(), | |||
"SignatureNonce": util.GetNonce(), | |||
}, request.Query) | |||
headers, _err := client.GetRpcHeaders() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.IsUnset(headers)) { | |||
// endpoint is setted in product client | |||
request_.Headers = map[string]*string{ | |||
"host": client.Endpoint, | |||
"x-acs-version": version, | |||
"x-acs-action": action, | |||
"user-agent": client.GetUserAgent(), | |||
} | |||
} else { | |||
request_.Headers = tea.Merge(map[string]*string{ | |||
"host": client.Endpoint, | |||
"x-acs-version": version, | |||
"x-acs-action": action, | |||
"user-agent": client.GetUserAgent(), | |||
}, headers) | |||
} | |||
if !tea.BoolValue(util.IsUnset(request.Body)) { | |||
m := util.AssertAsMap(request.Body) | |||
tmp := util.AnyifyMapValue(openapiutil.Query(m)) | |||
request_.Body = tea.ToReader(util.ToFormString(tmp)) | |||
request_.Headers["content-type"] = tea.String("application/x-www-form-urlencoded") | |||
} | |||
if !tea.BoolValue(util.EqualString(authType, tea.String("Anonymous"))) { | |||
accessKeyId, _err := client.GetAccessKeyId() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
accessKeySecret, _err := client.GetAccessKeySecret() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
securityToken, _err := client.GetSecurityToken() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if !tea.BoolValue(util.Empty(securityToken)) { | |||
request_.Query["SecurityToken"] = securityToken | |||
} | |||
request_.Query["SignatureMethod"] = tea.String("HMAC-SHA1") | |||
request_.Query["SignatureVersion"] = tea.String("1.0") | |||
request_.Query["AccessKeyId"] = accessKeyId | |||
var t map[string]interface{} | |||
if !tea.BoolValue(util.IsUnset(request.Body)) { | |||
t = util.AssertAsMap(request.Body) | |||
} | |||
signedParam := tea.Merge(request_.Query, | |||
openapiutil.Query(t)) | |||
request_.Query["Signature"] = openapiutil.GetRPCSignature(signedParam, request_.Method, accessKeySecret) | |||
} | |||
response_, _err := tea.DoRequest(request_, _runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.Is4xx(response_.StatusCode)) || tea.BoolValue(util.Is5xx(response_.StatusCode)) { | |||
_res, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
err := util.AssertAsMap(_res) | |||
requestId := DefaultAny(err["RequestId"], err["requestId"]) | |||
_err = tea.NewSDKError(map[string]interface{}{ | |||
"code": tea.ToString(DefaultAny(err["Code"], err["code"])), | |||
"message": "code: " + tea.ToString(tea.IntValue(response_.StatusCode)) + ", " + tea.ToString(DefaultAny(err["Message"], err["message"])) + " request id: " + tea.ToString(requestId), | |||
"data": err, | |||
}) | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.EqualString(bodyType, tea.String("binary"))) { | |||
resp := map[string]interface{}{ | |||
"body": response_.Body, | |||
"headers": response_.Headers, | |||
} | |||
_result = resp | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("byte"))) { | |||
byt, _err := util.ReadAsBytes(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": byt, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("string"))) { | |||
str, _err := util.ReadAsString(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": tea.StringValue(str), | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("json"))) { | |||
obj, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
res := util.AssertAsMap(obj) | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": res, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("array"))) { | |||
arr, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": arr, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else { | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]map[string]*string{ | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} | |||
}() | |||
if !tea.BoolValue(tea.Retryable(_err)) { | |||
break | |||
} | |||
} | |||
return _resp, _err | |||
} | |||
/** | |||
* Encapsulate the request and invoke the network | |||
* @param action api name | |||
* @param version product version | |||
* @param protocol http or https | |||
* @param method e.g. GET | |||
* @param authType authorization type e.g. AK | |||
* @param pathname pathname of every api | |||
* @param bodyType response body type e.g. String | |||
* @param request object of OpenApiRequest | |||
* @param runtime which controls some details of call api, such as retry times | |||
* @return the response | |||
*/ | |||
func (client *Client) DoROARequest(action *string, version *string, protocol *string, method *string, authType *string, pathname *string, bodyType *string, request *OpenApiRequest, runtime *util.RuntimeOptions) (_result map[string]interface{}, _err error) { | |||
_err = tea.Validate(request) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_err = tea.Validate(runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_runtime := map[string]interface{}{ | |||
"timeouted": "retry", | |||
"readTimeout": tea.IntValue(util.DefaultNumber(runtime.ReadTimeout, client.ReadTimeout)), | |||
"connectTimeout": tea.IntValue(util.DefaultNumber(runtime.ConnectTimeout, client.ConnectTimeout)), | |||
"httpProxy": tea.StringValue(util.DefaultString(runtime.HttpProxy, client.HttpProxy)), | |||
"httpsProxy": tea.StringValue(util.DefaultString(runtime.HttpsProxy, client.HttpsProxy)), | |||
"noProxy": tea.StringValue(util.DefaultString(runtime.NoProxy, client.NoProxy)), | |||
"socks5Proxy": tea.StringValue(util.DefaultString(runtime.Socks5Proxy, client.Socks5Proxy)), | |||
"socks5NetWork": tea.StringValue(util.DefaultString(runtime.Socks5NetWork, client.Socks5NetWork)), | |||
"maxIdleConns": tea.IntValue(util.DefaultNumber(runtime.MaxIdleConns, client.MaxIdleConns)), | |||
"retry": map[string]interface{}{ | |||
"retryable": tea.BoolValue(runtime.Autoretry), | |||
"maxAttempts": tea.IntValue(util.DefaultNumber(runtime.MaxAttempts, tea.Int(3))), | |||
}, | |||
"backoff": map[string]interface{}{ | |||
"policy": tea.StringValue(util.DefaultString(runtime.BackoffPolicy, tea.String("no"))), | |||
"period": tea.IntValue(util.DefaultNumber(runtime.BackoffPeriod, tea.Int(1))), | |||
}, | |||
"ignoreSSL": tea.BoolValue(runtime.IgnoreSSL), | |||
} | |||
_resp := make(map[string]interface{}) | |||
for _retryTimes := 0; tea.BoolValue(tea.AllowRetry(_runtime["retry"], tea.Int(_retryTimes))); _retryTimes++ { | |||
if _retryTimes > 0 { | |||
_backoffTime := tea.GetBackoffTime(_runtime["backoff"], tea.Int(_retryTimes)) | |||
if tea.IntValue(_backoffTime) > 0 { | |||
tea.Sleep(_backoffTime) | |||
} | |||
} | |||
_resp, _err = func() (map[string]interface{}, error) { | |||
request_ := tea.NewRequest() | |||
request_.Protocol = util.DefaultString(client.Protocol, protocol) | |||
request_.Method = method | |||
request_.Pathname = pathname | |||
request_.Headers = tea.Merge(map[string]*string{ | |||
"date": util.GetDateUTCString(), | |||
"host": client.Endpoint, | |||
"accept": tea.String("application/json"), | |||
"x-acs-signature-nonce": util.GetNonce(), | |||
"x-acs-signature-method": tea.String("HMAC-SHA1"), | |||
"x-acs-signature-version": tea.String("1.0"), | |||
"x-acs-version": version, | |||
"x-acs-action": action, | |||
"user-agent": util.GetUserAgent(client.UserAgent), | |||
}, request.Headers) | |||
if !tea.BoolValue(util.IsUnset(request.Body)) { | |||
request_.Body = tea.ToReader(util.ToJSONString(request.Body)) | |||
request_.Headers["content-type"] = tea.String("application/json; charset=utf-8") | |||
} | |||
if !tea.BoolValue(util.IsUnset(request.Query)) { | |||
request_.Query = request.Query | |||
} | |||
if !tea.BoolValue(util.EqualString(authType, tea.String("Anonymous"))) { | |||
accessKeyId, _err := client.GetAccessKeyId() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
accessKeySecret, _err := client.GetAccessKeySecret() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
securityToken, _err := client.GetSecurityToken() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if !tea.BoolValue(util.Empty(securityToken)) { | |||
request_.Headers["x-acs-accesskey-id"] = accessKeyId | |||
request_.Headers["x-acs-security-token"] = securityToken | |||
} | |||
stringToSign := openapiutil.GetStringToSign(request_) | |||
request_.Headers["authorization"] = tea.String("acs " + tea.StringValue(accessKeyId) + ":" + tea.StringValue(openapiutil.GetROASignature(stringToSign, accessKeySecret))) | |||
} | |||
response_, _err := tea.DoRequest(request_, _runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.EqualNumber(response_.StatusCode, tea.Int(204))) { | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]map[string]*string{ | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.Is4xx(response_.StatusCode)) || tea.BoolValue(util.Is5xx(response_.StatusCode)) { | |||
_res, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
err := util.AssertAsMap(_res) | |||
requestId := DefaultAny(err["RequestId"], err["requestId"]) | |||
requestId = DefaultAny(requestId, err["requestid"]) | |||
_err = tea.NewSDKError(map[string]interface{}{ | |||
"code": tea.ToString(DefaultAny(err["Code"], err["code"])), | |||
"message": "code: " + tea.ToString(tea.IntValue(response_.StatusCode)) + ", " + tea.ToString(DefaultAny(err["Message"], err["message"])) + " request id: " + tea.ToString(requestId), | |||
"data": err, | |||
}) | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.EqualString(bodyType, tea.String("binary"))) { | |||
resp := map[string]interface{}{ | |||
"body": response_.Body, | |||
"headers": response_.Headers, | |||
} | |||
_result = resp | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("byte"))) { | |||
byt, _err := util.ReadAsBytes(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": byt, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("string"))) { | |||
str, _err := util.ReadAsString(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": tea.StringValue(str), | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("json"))) { | |||
obj, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
res := util.AssertAsMap(obj) | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": res, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("array"))) { | |||
arr, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": arr, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else { | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]map[string]*string{ | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} | |||
}() | |||
if !tea.BoolValue(tea.Retryable(_err)) { | |||
break | |||
} | |||
} | |||
return _resp, _err | |||
} | |||
/** | |||
* Encapsulate the request and invoke the network with form body | |||
* @param action api name | |||
* @param version product version | |||
* @param protocol http or https | |||
* @param method e.g. GET | |||
* @param authType authorization type e.g. AK | |||
* @param pathname pathname of every api | |||
* @param bodyType response body type e.g. String | |||
* @param request object of OpenApiRequest | |||
* @param runtime which controls some details of call api, such as retry times | |||
* @return the response | |||
*/ | |||
func (client *Client) DoROARequestWithForm(action *string, version *string, protocol *string, method *string, authType *string, pathname *string, bodyType *string, request *OpenApiRequest, runtime *util.RuntimeOptions) (_result map[string]interface{}, _err error) { | |||
_err = tea.Validate(request) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_err = tea.Validate(runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_runtime := map[string]interface{}{ | |||
"timeouted": "retry", | |||
"readTimeout": tea.IntValue(util.DefaultNumber(runtime.ReadTimeout, client.ReadTimeout)), | |||
"connectTimeout": tea.IntValue(util.DefaultNumber(runtime.ConnectTimeout, client.ConnectTimeout)), | |||
"httpProxy": tea.StringValue(util.DefaultString(runtime.HttpProxy, client.HttpProxy)), | |||
"httpsProxy": tea.StringValue(util.DefaultString(runtime.HttpsProxy, client.HttpsProxy)), | |||
"noProxy": tea.StringValue(util.DefaultString(runtime.NoProxy, client.NoProxy)), | |||
"socks5Proxy": tea.StringValue(util.DefaultString(runtime.Socks5Proxy, client.Socks5Proxy)), | |||
"socks5NetWork": tea.StringValue(util.DefaultString(runtime.Socks5NetWork, client.Socks5NetWork)), | |||
"maxIdleConns": tea.IntValue(util.DefaultNumber(runtime.MaxIdleConns, client.MaxIdleConns)), | |||
"retry": map[string]interface{}{ | |||
"retryable": tea.BoolValue(runtime.Autoretry), | |||
"maxAttempts": tea.IntValue(util.DefaultNumber(runtime.MaxAttempts, tea.Int(3))), | |||
}, | |||
"backoff": map[string]interface{}{ | |||
"policy": tea.StringValue(util.DefaultString(runtime.BackoffPolicy, tea.String("no"))), | |||
"period": tea.IntValue(util.DefaultNumber(runtime.BackoffPeriod, tea.Int(1))), | |||
}, | |||
"ignoreSSL": tea.BoolValue(runtime.IgnoreSSL), | |||
} | |||
_resp := make(map[string]interface{}) | |||
for _retryTimes := 0; tea.BoolValue(tea.AllowRetry(_runtime["retry"], tea.Int(_retryTimes))); _retryTimes++ { | |||
if _retryTimes > 0 { | |||
_backoffTime := tea.GetBackoffTime(_runtime["backoff"], tea.Int(_retryTimes)) | |||
if tea.IntValue(_backoffTime) > 0 { | |||
tea.Sleep(_backoffTime) | |||
} | |||
} | |||
_resp, _err = func() (map[string]interface{}, error) { | |||
request_ := tea.NewRequest() | |||
request_.Protocol = util.DefaultString(client.Protocol, protocol) | |||
request_.Method = method | |||
request_.Pathname = pathname | |||
request_.Headers = tea.Merge(map[string]*string{ | |||
"date": util.GetDateUTCString(), | |||
"host": client.Endpoint, | |||
"accept": tea.String("application/json"), | |||
"x-acs-signature-nonce": util.GetNonce(), | |||
"x-acs-signature-method": tea.String("HMAC-SHA1"), | |||
"x-acs-signature-version": tea.String("1.0"), | |||
"x-acs-version": version, | |||
"x-acs-action": action, | |||
"user-agent": util.GetUserAgent(client.UserAgent), | |||
}, request.Headers) | |||
if !tea.BoolValue(util.IsUnset(request.Body)) { | |||
m := util.AssertAsMap(request.Body) | |||
request_.Body = tea.ToReader(openapiutil.ToForm(m)) | |||
request_.Headers["content-type"] = tea.String("application/x-www-form-urlencoded") | |||
} | |||
if !tea.BoolValue(util.IsUnset(request.Query)) { | |||
request_.Query = request.Query | |||
} | |||
if !tea.BoolValue(util.EqualString(authType, tea.String("Anonymous"))) { | |||
accessKeyId, _err := client.GetAccessKeyId() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
accessKeySecret, _err := client.GetAccessKeySecret() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
securityToken, _err := client.GetSecurityToken() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if !tea.BoolValue(util.Empty(securityToken)) { | |||
request_.Headers["x-acs-accesskey-id"] = accessKeyId | |||
request_.Headers["x-acs-security-token"] = securityToken | |||
} | |||
stringToSign := openapiutil.GetStringToSign(request_) | |||
request_.Headers["authorization"] = tea.String("acs " + tea.StringValue(accessKeyId) + ":" + tea.StringValue(openapiutil.GetROASignature(stringToSign, accessKeySecret))) | |||
} | |||
response_, _err := tea.DoRequest(request_, _runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.EqualNumber(response_.StatusCode, tea.Int(204))) { | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]map[string]*string{ | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.Is4xx(response_.StatusCode)) || tea.BoolValue(util.Is5xx(response_.StatusCode)) { | |||
_res, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
err := util.AssertAsMap(_res) | |||
_err = tea.NewSDKError(map[string]interface{}{ | |||
"code": tea.ToString(DefaultAny(err["Code"], err["code"])), | |||
"message": "code: " + tea.ToString(tea.IntValue(response_.StatusCode)) + ", " + tea.ToString(DefaultAny(err["Message"], err["message"])) + " request id: " + tea.ToString(DefaultAny(err["RequestId"], err["requestId"])), | |||
"data": err, | |||
}) | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.EqualString(bodyType, tea.String("binary"))) { | |||
resp := map[string]interface{}{ | |||
"body": response_.Body, | |||
"headers": response_.Headers, | |||
} | |||
_result = resp | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("byte"))) { | |||
byt, _err := util.ReadAsBytes(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": byt, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("string"))) { | |||
str, _err := util.ReadAsString(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": tea.StringValue(str), | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("json"))) { | |||
obj, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
res := util.AssertAsMap(obj) | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": res, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(bodyType, tea.String("array"))) { | |||
arr, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": arr, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else { | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]map[string]*string{ | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} | |||
}() | |||
if !tea.BoolValue(tea.Retryable(_err)) { | |||
break | |||
} | |||
} | |||
return _resp, _err | |||
} | |||
/** | |||
* Encapsulate the request and invoke the network | |||
* @param action api name | |||
* @param version product version | |||
* @param protocol http or https | |||
* @param method e.g. GET | |||
* @param authType authorization type e.g. AK | |||
* @param bodyType response body type e.g. String | |||
* @param request object of OpenApiRequest | |||
* @param runtime which controls some details of call api, such as retry times | |||
* @return the response | |||
*/ | |||
func (client *Client) DoRequest(params *Params, request *OpenApiRequest, runtime *util.RuntimeOptions) (_result map[string]interface{}, _err error) { | |||
_err = tea.Validate(params) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_err = tea.Validate(request) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_err = tea.Validate(runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_runtime := map[string]interface{}{ | |||
"timeouted": "retry", | |||
"readTimeout": tea.IntValue(util.DefaultNumber(runtime.ReadTimeout, client.ReadTimeout)), | |||
"connectTimeout": tea.IntValue(util.DefaultNumber(runtime.ConnectTimeout, client.ConnectTimeout)), | |||
"httpProxy": tea.StringValue(util.DefaultString(runtime.HttpProxy, client.HttpProxy)), | |||
"httpsProxy": tea.StringValue(util.DefaultString(runtime.HttpsProxy, client.HttpsProxy)), | |||
"noProxy": tea.StringValue(util.DefaultString(runtime.NoProxy, client.NoProxy)), | |||
"socks5Proxy": tea.StringValue(util.DefaultString(runtime.Socks5Proxy, client.Socks5Proxy)), | |||
"socks5NetWork": tea.StringValue(util.DefaultString(runtime.Socks5NetWork, client.Socks5NetWork)), | |||
"maxIdleConns": tea.IntValue(util.DefaultNumber(runtime.MaxIdleConns, client.MaxIdleConns)), | |||
"retry": map[string]interface{}{ | |||
"retryable": tea.BoolValue(runtime.Autoretry), | |||
"maxAttempts": tea.IntValue(util.DefaultNumber(runtime.MaxAttempts, tea.Int(3))), | |||
}, | |||
"backoff": map[string]interface{}{ | |||
"policy": tea.StringValue(util.DefaultString(runtime.BackoffPolicy, tea.String("no"))), | |||
"period": tea.IntValue(util.DefaultNumber(runtime.BackoffPeriod, tea.Int(1))), | |||
}, | |||
"ignoreSSL": tea.BoolValue(runtime.IgnoreSSL), | |||
} | |||
_resp := make(map[string]interface{}) | |||
for _retryTimes := 0; tea.BoolValue(tea.AllowRetry(_runtime["retry"], tea.Int(_retryTimes))); _retryTimes++ { | |||
if _retryTimes > 0 { | |||
_backoffTime := tea.GetBackoffTime(_runtime["backoff"], tea.Int(_retryTimes)) | |||
if tea.IntValue(_backoffTime) > 0 { | |||
tea.Sleep(_backoffTime) | |||
} | |||
} | |||
_resp, _err = func() (map[string]interface{}, error) { | |||
request_ := tea.NewRequest() | |||
request_.Protocol = util.DefaultString(client.Protocol, params.Protocol) | |||
request_.Method = params.Method | |||
request_.Pathname = params.Pathname | |||
request_.Query = request.Query | |||
// endpoint is setted in product client | |||
request_.Headers = tea.Merge(map[string]*string{ | |||
"host": client.Endpoint, | |||
"x-acs-version": params.Version, | |||
"x-acs-action": params.Action, | |||
"user-agent": client.GetUserAgent(), | |||
"x-acs-date": openapiutil.GetTimestamp(), | |||
"x-acs-signature-nonce": util.GetNonce(), | |||
"accept": tea.String("application/json"), | |||
}, request.Headers) | |||
if tea.BoolValue(util.EqualString(params.Style, tea.String("RPC"))) { | |||
headers, _err := client.GetRpcHeaders() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if !tea.BoolValue(util.IsUnset(headers)) { | |||
request_.Headers = tea.Merge(request_.Headers, | |||
headers) | |||
} | |||
} | |||
signatureAlgorithm := util.DefaultString(client.SignatureAlgorithm, tea.String("ACS3-HMAC-SHA256")) | |||
hashedRequestPayload := openapiutil.HexEncode(openapiutil.Hash(util.ToBytes(tea.String("")), signatureAlgorithm)) | |||
if !tea.BoolValue(util.IsUnset(request.Stream)) { | |||
tmp, _err := util.ReadAsBytes(request.Stream) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
hashedRequestPayload = openapiutil.HexEncode(openapiutil.Hash(tmp, signatureAlgorithm)) | |||
request_.Body = tea.ToReader(tmp) | |||
request_.Headers["content-type"] = tea.String("application/octet-stream") | |||
} else { | |||
if !tea.BoolValue(util.IsUnset(request.Body)) { | |||
if tea.BoolValue(util.EqualString(params.ReqBodyType, tea.String("json"))) { | |||
jsonObj := util.ToJSONString(request.Body) | |||
hashedRequestPayload = openapiutil.HexEncode(openapiutil.Hash(util.ToBytes(jsonObj), signatureAlgorithm)) | |||
request_.Body = tea.ToReader(jsonObj) | |||
request_.Headers["content-type"] = tea.String("application/json; charset=utf-8") | |||
} else { | |||
m := util.AssertAsMap(request.Body) | |||
formObj := openapiutil.ToForm(m) | |||
hashedRequestPayload = openapiutil.HexEncode(openapiutil.Hash(util.ToBytes(formObj), signatureAlgorithm)) | |||
request_.Body = tea.ToReader(formObj) | |||
request_.Headers["content-type"] = tea.String("application/x-www-form-urlencoded") | |||
} | |||
} | |||
} | |||
request_.Headers["x-acs-content-sha256"] = hashedRequestPayload | |||
if !tea.BoolValue(util.EqualString(params.AuthType, tea.String("Anonymous"))) { | |||
authType, _err := client.GetType() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.EqualString(authType, tea.String("bearer"))) { | |||
bearerToken, _err := client.GetBearerToken() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
request_.Headers["x-acs-bearer-token"] = bearerToken | |||
} else { | |||
accessKeyId, _err := client.GetAccessKeyId() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
accessKeySecret, _err := client.GetAccessKeySecret() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
securityToken, _err := client.GetSecurityToken() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if !tea.BoolValue(util.Empty(securityToken)) { | |||
request_.Headers["x-acs-accesskey-id"] = accessKeyId | |||
request_.Headers["x-acs-security-token"] = securityToken | |||
} | |||
request_.Headers["Authorization"] = openapiutil.GetAuthorization(request_, signatureAlgorithm, hashedRequestPayload, accessKeyId, accessKeySecret) | |||
} | |||
} | |||
response_, _err := tea.DoRequest(request_, _runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.Is4xx(response_.StatusCode)) || tea.BoolValue(util.Is5xx(response_.StatusCode)) { | |||
err := map[string]interface{}{} | |||
if !tea.BoolValue(util.IsUnset(response_.Headers["content-type"])) && tea.BoolValue(util.EqualString(response_.Headers["content-type"], tea.String("text/xml;charset=utf-8"))) { | |||
_str, _err := util.ReadAsString(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
respMap := xml.ParseXml(_str, nil) | |||
err = util.AssertAsMap(respMap["Error"]) | |||
} else { | |||
_res, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
err = util.AssertAsMap(_res) | |||
} | |||
err["statusCode"] = response_.StatusCode | |||
_err = tea.NewSDKError(map[string]interface{}{ | |||
"code": tea.ToString(DefaultAny(err["Code"], err["code"])), | |||
"message": "code: " + tea.ToString(tea.IntValue(response_.StatusCode)) + ", " + tea.ToString(DefaultAny(err["Message"], err["message"])) + " request id: " + tea.ToString(DefaultAny(err["RequestId"], err["requestId"])), | |||
"data": err, | |||
}) | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.EqualString(params.BodyType, tea.String("binary"))) { | |||
resp := map[string]interface{}{ | |||
"body": response_.Body, | |||
"headers": response_.Headers, | |||
} | |||
_result = resp | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(params.BodyType, tea.String("byte"))) { | |||
byt, _err := util.ReadAsBytes(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": byt, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(params.BodyType, tea.String("string"))) { | |||
str, _err := util.ReadAsString(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": tea.StringValue(str), | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(params.BodyType, tea.String("json"))) { | |||
obj, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
res := util.AssertAsMap(obj) | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": res, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(params.BodyType, tea.String("array"))) { | |||
arr, _err := util.ReadAsJSON(response_.Body) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"body": arr, | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} else { | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]map[string]*string{ | |||
"headers": response_.Headers, | |||
}, &_result) | |||
return _result, _err | |||
} | |||
}() | |||
if !tea.BoolValue(tea.Retryable(_err)) { | |||
break | |||
} | |||
} | |||
return _resp, _err | |||
} | |||
/** | |||
* Encapsulate the request and invoke the network | |||
* @param action api name | |||
* @param version product version | |||
* @param protocol http or https | |||
* @param method e.g. GET | |||
* @param authType authorization type e.g. AK | |||
* @param bodyType response body type e.g. String | |||
* @param request object of OpenApiRequest | |||
* @param runtime which controls some details of call api, such as retry times | |||
* @return the response | |||
*/ | |||
func (client *Client) Execute(params *Params, request *OpenApiRequest, runtime *util.RuntimeOptions) (_result map[string]interface{}, _err error) { | |||
_err = tea.Validate(params) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_err = tea.Validate(request) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_err = tea.Validate(runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_runtime := map[string]interface{}{ | |||
"timeouted": "retry", | |||
"readTimeout": tea.IntValue(util.DefaultNumber(runtime.ReadTimeout, client.ReadTimeout)), | |||
"connectTimeout": tea.IntValue(util.DefaultNumber(runtime.ConnectTimeout, client.ConnectTimeout)), | |||
"httpProxy": tea.StringValue(util.DefaultString(runtime.HttpProxy, client.HttpProxy)), | |||
"httpsProxy": tea.StringValue(util.DefaultString(runtime.HttpsProxy, client.HttpsProxy)), | |||
"noProxy": tea.StringValue(util.DefaultString(runtime.NoProxy, client.NoProxy)), | |||
"socks5Proxy": tea.StringValue(util.DefaultString(runtime.Socks5Proxy, client.Socks5Proxy)), | |||
"socks5NetWork": tea.StringValue(util.DefaultString(runtime.Socks5NetWork, client.Socks5NetWork)), | |||
"maxIdleConns": tea.IntValue(util.DefaultNumber(runtime.MaxIdleConns, client.MaxIdleConns)), | |||
"retry": map[string]interface{}{ | |||
"retryable": tea.BoolValue(runtime.Autoretry), | |||
"maxAttempts": tea.IntValue(util.DefaultNumber(runtime.MaxAttempts, tea.Int(3))), | |||
}, | |||
"backoff": map[string]interface{}{ | |||
"policy": tea.StringValue(util.DefaultString(runtime.BackoffPolicy, tea.String("no"))), | |||
"period": tea.IntValue(util.DefaultNumber(runtime.BackoffPeriod, tea.Int(1))), | |||
}, | |||
"ignoreSSL": tea.BoolValue(runtime.IgnoreSSL), | |||
} | |||
_resp := make(map[string]interface{}) | |||
for _retryTimes := 0; tea.BoolValue(tea.AllowRetry(_runtime["retry"], tea.Int(_retryTimes))); _retryTimes++ { | |||
if _retryTimes > 0 { | |||
_backoffTime := tea.GetBackoffTime(_runtime["backoff"], tea.Int(_retryTimes)) | |||
if tea.IntValue(_backoffTime) > 0 { | |||
tea.Sleep(_backoffTime) | |||
} | |||
} | |||
_resp, _err = func() (map[string]interface{}, error) { | |||
request_ := tea.NewRequest() | |||
// spi = new Gateway();//Gateway implements SPI,这一步在产品 SDK 中实例化 | |||
headers, _err := client.GetRpcHeaders() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
requestContext := &spi.InterceptorContextRequest{ | |||
Headers: tea.Merge(request.Headers, | |||
headers), | |||
Query: request.Query, | |||
Body: request.Body, | |||
Stream: request.Stream, | |||
HostMap: request.HostMap, | |||
Pathname: params.Pathname, | |||
ProductId: client.ProductId, | |||
Action: params.Action, | |||
Version: params.Version, | |||
Protocol: util.DefaultString(client.Protocol, params.Protocol), | |||
Method: util.DefaultString(client.Method, params.Method), | |||
AuthType: params.AuthType, | |||
BodyType: params.BodyType, | |||
ReqBodyType: params.ReqBodyType, | |||
Style: params.Style, | |||
Credential: client.Credential, | |||
SignatureVersion: client.SignatureVersion, | |||
SignatureAlgorithm: client.SignatureAlgorithm, | |||
UserAgent: client.GetUserAgent(), | |||
} | |||
configurationContext := &spi.InterceptorContextConfiguration{ | |||
RegionId: client.RegionId, | |||
Endpoint: util.DefaultString(request.EndpointOverride, client.Endpoint), | |||
EndpointRule: client.EndpointRule, | |||
EndpointMap: client.EndpointMap, | |||
EndpointType: client.EndpointType, | |||
Network: client.Network, | |||
Suffix: client.Suffix, | |||
} | |||
interceptorContext := &spi.InterceptorContext{ | |||
Request: requestContext, | |||
Configuration: configurationContext, | |||
} | |||
attributeMap := &spi.AttributeMap{} | |||
// 1. spi.modifyConfiguration(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap); | |||
_err = client.Spi.ModifyConfiguration(interceptorContext, attributeMap) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
// 2. spi.modifyRequest(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap); | |||
_err = client.Spi.ModifyRequest(interceptorContext, attributeMap) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
request_.Protocol = interceptorContext.Request.Protocol | |||
request_.Method = interceptorContext.Request.Method | |||
request_.Pathname = interceptorContext.Request.Pathname | |||
request_.Query = interceptorContext.Request.Query | |||
request_.Body = interceptorContext.Request.Stream | |||
request_.Headers = interceptorContext.Request.Headers | |||
response_, _err := tea.DoRequest(request_, _runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
responseContext := &spi.InterceptorContextResponse{ | |||
StatusCode: response_.StatusCode, | |||
Headers: response_.Headers, | |||
Body: response_.Body, | |||
} | |||
interceptorContext.Response = responseContext | |||
// 3. spi.modifyResponse(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap); | |||
_err = client.Spi.ModifyResponse(interceptorContext, attributeMap) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = make(map[string]interface{}) | |||
_err = tea.Convert(map[string]interface{}{ | |||
"headers": interceptorContext.Response.Headers, | |||
"body": interceptorContext.Response.DeserializedBody, | |||
}, &_result) | |||
return _result, _err | |||
}() | |||
if !tea.BoolValue(tea.Retryable(_err)) { | |||
break | |||
} | |||
} | |||
return _resp, _err | |||
} | |||
func (client *Client) CallApi(params *Params, request *OpenApiRequest, runtime *util.RuntimeOptions) (_result map[string]interface{}, _err error) { | |||
if tea.BoolValue(util.IsUnset(tea.ToMap(params))) { | |||
_err = tea.NewSDKError(map[string]interface{}{ | |||
"code": "ParameterMissing", | |||
"message": "'params' can not be unset", | |||
}) | |||
return _result, _err | |||
} | |||
if tea.BoolValue(util.IsUnset(client.SignatureAlgorithm)) || !tea.BoolValue(util.EqualString(client.SignatureAlgorithm, tea.String("v2"))) { | |||
_result = make(map[string]interface{}) | |||
_body, _err := client.DoRequest(params, request, runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = _body | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(params.Style, tea.String("ROA"))) && tea.BoolValue(util.EqualString(params.ReqBodyType, tea.String("json"))) { | |||
_result = make(map[string]interface{}) | |||
_body, _err := client.DoROARequest(params.Action, params.Version, params.Protocol, params.Method, params.AuthType, params.Pathname, params.BodyType, request, runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = _body | |||
return _result, _err | |||
} else if tea.BoolValue(util.EqualString(params.Style, tea.String("ROA"))) { | |||
_result = make(map[string]interface{}) | |||
_body, _err := client.DoROARequestWithForm(params.Action, params.Version, params.Protocol, params.Method, params.AuthType, params.Pathname, params.BodyType, request, runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = _body | |||
return _result, _err | |||
} else { | |||
_result = make(map[string]interface{}) | |||
_body, _err := client.DoRPCRequest(params.Action, params.Version, params.Protocol, params.Method, params.AuthType, params.BodyType, request, runtime) | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = _body | |||
return _result, _err | |||
} | |||
} | |||
/** | |||
* Get user agent | |||
* @return user agent | |||
*/ | |||
func (client *Client) GetUserAgent() (_result *string) { | |||
userAgent := util.GetUserAgent(client.UserAgent) | |||
_result = userAgent | |||
return _result | |||
} | |||
/** | |||
* Get accesskey id by using credential | |||
* @return accesskey id | |||
*/ | |||
func (client *Client) GetAccessKeyId() (_result *string, _err error) { | |||
if tea.BoolValue(util.IsUnset(client.Credential)) { | |||
_result = tea.String("") | |||
return _result, _err | |||
} | |||
accessKeyId, _err := client.Credential.GetAccessKeyId() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = accessKeyId | |||
return _result, _err | |||
} | |||
/** | |||
* Get accesskey secret by using credential | |||
* @return accesskey secret | |||
*/ | |||
func (client *Client) GetAccessKeySecret() (_result *string, _err error) { | |||
if tea.BoolValue(util.IsUnset(client.Credential)) { | |||
_result = tea.String("") | |||
return _result, _err | |||
} | |||
secret, _err := client.Credential.GetAccessKeySecret() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = secret | |||
return _result, _err | |||
} | |||
/** | |||
* Get security token by using credential | |||
* @return security token | |||
*/ | |||
func (client *Client) GetSecurityToken() (_result *string, _err error) { | |||
if tea.BoolValue(util.IsUnset(client.Credential)) { | |||
_result = tea.String("") | |||
return _result, _err | |||
} | |||
token, _err := client.Credential.GetSecurityToken() | |||
if _err != nil { | |||
return _result, _err | |||
} | |||
_result = token | |||
return _result, _err | |||
} | |||
/** | |||
* Get bearer token by credential | |||
* @return bearer token | |||
*/ | |||
func (client *Client) GetBearerToken() (_result *string, _err error) { | |||
if tea.BoolValue(util.IsUnset(client.Credential)) { | |||
_result = tea.String("") | |||
return _result, _err | |||
} | |||
token := client.Credential.GetBearerToken() | |||
_result = token | |||
return _result, _err | |||
} | |||
/** | |||
* Get credential type by credential | |||
* @return credential type e.g. access_key | |||
*/ | |||
func (client *Client) GetType() (_result *string, _err error) { | |||
if tea.BoolValue(util.IsUnset(client.Credential)) { | |||
_result = tea.String("") | |||
return _result, _err | |||
} | |||
authType := client.Credential.GetType() | |||
_result = authType | |||
return _result, _err | |||
} | |||
/** | |||
* If inputValue is not null, return it or return defaultValue | |||
* @param inputValue users input value | |||
* @param defaultValue default value | |||
* @return the final result | |||
*/ | |||
func DefaultAny(inputValue interface{}, defaultValue interface{}) (_result interface{}) { | |||
if tea.BoolValue(util.IsUnset(inputValue)) { | |||
_result = defaultValue | |||
return _result | |||
} | |||
_result = inputValue | |||
return _result | |||
} | |||
/** | |||
* If the endpointRule and config.endpoint are empty, throw error | |||
* @param config config contains the necessary information to create a client | |||
*/ | |||
func (client *Client) CheckConfig(config *Config) (_err error) { | |||
if tea.BoolValue(util.Empty(client.EndpointRule)) && tea.BoolValue(util.Empty(config.Endpoint)) { | |||
_err = tea.NewSDKError(map[string]interface{}{ | |||
"code": "ParameterMissing", | |||
"message": "'config.endpoint' can not be empty", | |||
}) | |||
return _err | |||
} | |||
return _err | |||
} | |||
/** | |||
* set RPC header for debug | |||
* @param headers headers for debug, this header can be used only once. | |||
*/ | |||
func (client *Client) SetRpcHeaders(headers map[string]*string) (_err error) { | |||
client.Headers = headers | |||
return _err | |||
} | |||
/** | |||
* get RPC header for debug | |||
*/ | |||
func (client *Client) GetRpcHeaders() (_result map[string]*string, _err error) { | |||
headers := client.Headers | |||
client.Headers = nil | |||
_result = headers | |||
return _result, _err | |||
} |
@@ -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. |
@@ -0,0 +1,12 @@ | |||
package debug | |||
import ( | |||
"reflect" | |||
"testing" | |||
) | |||
func assertEqual(t *testing.T, a, b interface{}) { | |||
if !reflect.DeepEqual(a, b) { | |||
t.Errorf("%v != %v", a, b) | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
package debug | |||
import ( | |||
"fmt" | |||
"os" | |||
"strings" | |||
) | |||
type Debug func(format string, v ...interface{}) | |||
var hookGetEnv = func() string { | |||
return os.Getenv("DEBUG") | |||
} | |||
var hookPrint = func(input string) { | |||
fmt.Println(input) | |||
} | |||
func Init(flag string) Debug { | |||
enable := false | |||
env := hookGetEnv() | |||
parts := strings.Split(env, ",") | |||
for _, part := range parts { | |||
if part == flag { | |||
enable = true | |||
break | |||
} | |||
} | |||
return func(format string, v ...interface{}) { | |||
if enable { | |||
hookPrint(fmt.Sprintf(format, v...)) | |||
} | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
// This file is auto-generated, don't edit it. Thanks. | |||
/** | |||
* Get endpoint | |||
* @return string | |||
*/ | |||
package service | |||
import ( | |||
"fmt" | |||
"strings" | |||
"github.com/alibabacloud-go/tea/tea" | |||
) | |||
func GetEndpointRules(product, regionId, endpointType, network, suffix *string) (_result *string, _err error) { | |||
if tea.StringValue(endpointType) == "regional" { | |||
if tea.StringValue(regionId) == "" { | |||
_err = fmt.Errorf("RegionId is empty, please set a valid RegionId") | |||
return tea.String(""), _err | |||
} | |||
_result = tea.String(strings.Replace("<product><suffix><network>.<region_id>.aliyuncs.com", | |||
"<region_id>", tea.StringValue(regionId), 1)) | |||
} else { | |||
_result = tea.String("<product><suffix><network>.aliyuncs.com") | |||
} | |||
_result = tea.String(strings.Replace(tea.StringValue(_result), | |||
"<product>", strings.ToLower(tea.StringValue(product)), 1)) | |||
if tea.StringValue(network) == "" || tea.StringValue(network) == "public" { | |||
_result = tea.String(strings.Replace(tea.StringValue(_result), "<network>", "", 1)) | |||
} else { | |||
_result = tea.String(strings.Replace(tea.StringValue(_result), | |||
"<network>", "-"+tea.StringValue(network), 1)) | |||
} | |||
if tea.StringValue(suffix) == "" { | |||
_result = tea.String(strings.Replace(tea.StringValue(_result), "<suffix>", "", 1)) | |||
} else { | |||
_result = tea.String(strings.Replace(tea.StringValue(_result), | |||
"<suffix>", "-"+tea.StringValue(suffix), 1)) | |||
} | |||
return _result, nil | |||
} |
@@ -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 (c) 2009-present, Alibaba Cloud All rights reserved. | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. |
@@ -0,0 +1,635 @@ | |||
// This file is auto-generated, don't edit it. Thanks. | |||
/** | |||
* This is for OpenApi Util | |||
*/ | |||
package service | |||
import ( | |||
"bytes" | |||
"crypto" | |||
"crypto/hmac" | |||
"crypto/rand" | |||
"crypto/rsa" | |||
"crypto/sha1" | |||
"crypto/sha256" | |||
"crypto/x509" | |||
"encoding/base64" | |||
"encoding/hex" | |||
"encoding/json" | |||
"encoding/pem" | |||
"errors" | |||
"fmt" | |||
"hash" | |||
"io" | |||
"net/http" | |||
"net/textproto" | |||
"net/url" | |||
"reflect" | |||
"sort" | |||
"strconv" | |||
"strings" | |||
"time" | |||
util "github.com/alibabacloud-go/tea-utils/service" | |||
"github.com/alibabacloud-go/tea/tea" | |||
"github.com/tjfoc/gmsm/sm3" | |||
) | |||
const ( | |||
PEM_BEGIN = "-----BEGIN RSA PRIVATE KEY-----\n" | |||
PEM_END = "\n-----END RSA PRIVATE KEY-----" | |||
) | |||
type Sorter struct { | |||
Keys []string | |||
Vals []string | |||
} | |||
func newSorter(m map[string]string) *Sorter { | |||
hs := &Sorter{ | |||
Keys: make([]string, 0, len(m)), | |||
Vals: make([]string, 0, len(m)), | |||
} | |||
for k, v := range m { | |||
hs.Keys = append(hs.Keys, k) | |||
hs.Vals = append(hs.Vals, v) | |||
} | |||
return hs | |||
} | |||
// Sort is an additional function for function SignHeader. | |||
func (hs *Sorter) Sort() { | |||
sort.Sort(hs) | |||
} | |||
// Len is an additional function for function SignHeader. | |||
func (hs *Sorter) Len() int { | |||
return len(hs.Vals) | |||
} | |||
// Less is an additional function for function SignHeader. | |||
func (hs *Sorter) Less(i, j int) bool { | |||
return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0 | |||
} | |||
// Swap is an additional function for function SignHeader. | |||
func (hs *Sorter) Swap(i, j int) { | |||
hs.Vals[i], hs.Vals[j] = hs.Vals[j], hs.Vals[i] | |||
hs.Keys[i], hs.Keys[j] = hs.Keys[j], hs.Keys[i] | |||
} | |||
/** | |||
* Convert all params of body other than type of readable into content | |||
* @param body source Model | |||
* @param content target Model | |||
* @return void | |||
*/ | |||
func Convert(body interface{}, content interface{}) { | |||
res := make(map[string]interface{}) | |||
val := reflect.ValueOf(body).Elem() | |||
dataType := val.Type() | |||
for i := 0; i < dataType.NumField(); i++ { | |||
field := dataType.Field(i) | |||
name, _ := field.Tag.Lookup("json") | |||
name = strings.Split(name, ",omitempty")[0] | |||
_, ok := val.Field(i).Interface().(io.Reader) | |||
if !ok { | |||
res[name] = val.Field(i).Interface() | |||
} | |||
} | |||
byt, _ := json.Marshal(res) | |||
json.Unmarshal(byt, content) | |||
} | |||
/** | |||
* Get the string to be signed according to request | |||
* @param request which contains signed messages | |||
* @return the signed string | |||
*/ | |||
func GetStringToSign(request *tea.Request) (_result *string) { | |||
return tea.String(getStringToSign(request)) | |||
} | |||
func getStringToSign(request *tea.Request) string { | |||
resource := tea.StringValue(request.Pathname) | |||
queryParams := request.Query | |||
// sort QueryParams by key | |||
var queryKeys []string | |||
for key := range queryParams { | |||
queryKeys = append(queryKeys, key) | |||
} | |||
sort.Strings(queryKeys) | |||
tmp := "" | |||
for i := 0; i < len(queryKeys); i++ { | |||
queryKey := queryKeys[i] | |||
v := tea.StringValue(queryParams[queryKey]) | |||
if v != "" { | |||
tmp = tmp + "&" + queryKey + "=" + v | |||
} else { | |||
tmp = tmp + "&" + queryKey | |||
} | |||
} | |||
if tmp != "" { | |||
tmp = strings.TrimLeft(tmp, "&") | |||
resource = resource + "?" + tmp | |||
} | |||
return getSignedStr(request, resource) | |||
} | |||
func getSignedStr(req *tea.Request, canonicalizedResource string) string { | |||
temp := make(map[string]string) | |||
for k, v := range req.Headers { | |||
if strings.HasPrefix(strings.ToLower(k), "x-acs-") { | |||
temp[strings.ToLower(k)] = tea.StringValue(v) | |||
} | |||
} | |||
hs := newSorter(temp) | |||
// Sort the temp by the ascending order | |||
hs.Sort() | |||
// Get the canonicalizedOSSHeaders | |||
canonicalizedOSSHeaders := "" | |||
for i := range hs.Keys { | |||
canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n" | |||
} | |||
// Give other parameters values | |||
// when sign URL, date is expires | |||
date := tea.StringValue(req.Headers["date"]) | |||
accept := tea.StringValue(req.Headers["accept"]) | |||
contentType := tea.StringValue(req.Headers["content-type"]) | |||
contentMd5 := tea.StringValue(req.Headers["content-md5"]) | |||
signStr := tea.StringValue(req.Method) + "\n" + accept + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedOSSHeaders + canonicalizedResource | |||
return signStr | |||
} | |||
/** | |||
* Get signature according to stringToSign, secret | |||
* @param stringToSign the signed string | |||
* @param secret accesskey secret | |||
* @return the signature | |||
*/ | |||
func GetROASignature(stringToSign *string, secret *string) (_result *string) { | |||
h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(tea.StringValue(secret))) | |||
io.WriteString(h, tea.StringValue(stringToSign)) | |||
signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil)) | |||
return tea.String(signedStr) | |||
} | |||
func GetEndpoint(endpoint *string, server *bool, endpointType *string) *string { | |||
if tea.StringValue(endpointType) == "internal" { | |||
strs := strings.Split(tea.StringValue(endpoint), ".") | |||
strs[0] += "-internal" | |||
endpoint = tea.String(strings.Join(strs, ".")) | |||
} | |||
if tea.BoolValue(server) && tea.StringValue(endpointType) == "accelerate" { | |||
return tea.String("oss-accelerate.aliyuncs.com") | |||
} | |||
return endpoint | |||
} | |||
func HexEncode(raw []byte) *string { | |||
return tea.String(hex.EncodeToString(raw)) | |||
} | |||
func Hash(raw []byte, signatureAlgorithm *string) []byte { | |||
signType := tea.StringValue(signatureAlgorithm) | |||
if signType == "ACS3-HMAC-SHA256" || signType == "ACS3-RSA-SHA256" { | |||
h := sha256.New() | |||
h.Write(raw) | |||
return h.Sum(nil) | |||
} else if signType == "ACS3-HMAC-SM3" { | |||
h := sm3.New() | |||
h.Write(raw) | |||
return h.Sum(nil) | |||
} | |||
return nil | |||
} | |||
func GetEncodePath(path *string) *string { | |||
uri := tea.StringValue(path) | |||
strs := strings.Split(uri, "/") | |||
for i, v := range strs { | |||
strs[i] = url.QueryEscape(v) | |||
} | |||
uri = strings.Join(strs, "/") | |||
uri = strings.Replace(uri, "+", "%20", -1) | |||
uri = strings.Replace(uri, "*", "%2A", -1) | |||
uri = strings.Replace(uri, "%7E", "~", -1) | |||
return tea.String(uri) | |||
} | |||
func GetEncodeParam(param *string) *string { | |||
uri := tea.StringValue(param) | |||
uri = url.QueryEscape(uri) | |||
uri = strings.Replace(uri, "+", "%20", -1) | |||
uri = strings.Replace(uri, "*", "%2A", -1) | |||
uri = strings.Replace(uri, "%7E", "~", -1) | |||
return tea.String(uri) | |||
} | |||
func GetAuthorization(request *tea.Request, signatureAlgorithm, payload, acesskey, secret *string) *string { | |||
canonicalURI := tea.StringValue(request.Pathname) | |||
if canonicalURI == "" { | |||
canonicalURI = "/" | |||
} | |||
canonicalURI = strings.Replace(canonicalURI, "+", "%20", -1) | |||
canonicalURI = strings.Replace(canonicalURI, "*", "%2A", -1) | |||
canonicalURI = strings.Replace(canonicalURI, "%7E", "~", -1) | |||
method := tea.StringValue(request.Method) | |||
canonicalQueryString := getCanonicalQueryString(request.Query) | |||
canonicalheaders, signedHeaders := getCanonicalHeaders(request.Headers) | |||
canonicalRequest := method + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalheaders + "\n" + | |||
strings.Join(signedHeaders, ";") + "\n" + tea.StringValue(payload) | |||
signType := tea.StringValue(signatureAlgorithm) | |||
StringToSign := signType + "\n" + tea.StringValue(HexEncode(Hash([]byte(canonicalRequest), signatureAlgorithm))) | |||
signature := tea.StringValue(HexEncode(SignatureMethod(tea.StringValue(secret), StringToSign, signType))) | |||
auth := signType + " Credential=" + tea.StringValue(acesskey) + ",SignedHeaders=" + | |||
strings.Join(signedHeaders, ";") + ",Signature=" + signature | |||
return tea.String(auth) | |||
} | |||
func SignatureMethod(secret, source, signatureAlgorithm string) []byte { | |||
if signatureAlgorithm == "ACS3-HMAC-SHA256" { | |||
h := hmac.New(sha256.New, []byte(secret)) | |||
h.Write([]byte(source)) | |||
return h.Sum(nil) | |||
} else if signatureAlgorithm == "ACS3-HMAC-SM3" { | |||
h := hmac.New(sm3.New, []byte(secret)) | |||
h.Write([]byte(source)) | |||
return h.Sum(nil) | |||
} else if signatureAlgorithm == "ACS3-RSA-SHA256" { | |||
return rsaSign(source, secret) | |||
} | |||
return nil | |||
} | |||
func rsaSign(content, secret string) []byte { | |||
h := crypto.SHA256.New() | |||
h.Write([]byte(content)) | |||
hashed := h.Sum(nil) | |||
priv, err := parsePrivateKey(secret) | |||
if err != nil { | |||
return nil | |||
} | |||
sign, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, hashed) | |||
if err != nil { | |||
return nil | |||
} | |||
return sign | |||
} | |||
func parsePrivateKey(privateKey string) (*rsa.PrivateKey, error) { | |||
privateKey = formatPrivateKey(privateKey) | |||
block, _ := pem.Decode([]byte(privateKey)) | |||
if block == nil { | |||
return nil, errors.New("PrivateKey is invalid") | |||
} | |||
priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) | |||
if err != nil { | |||
return nil, err | |||
} | |||
switch priKey.(type) { | |||
case *rsa.PrivateKey: | |||
return priKey.(*rsa.PrivateKey), nil | |||
default: | |||
return nil, nil | |||
} | |||
} | |||
func formatPrivateKey(privateKey string) string { | |||
if !strings.HasPrefix(privateKey, PEM_BEGIN) { | |||
privateKey = PEM_BEGIN + privateKey | |||
} | |||
if !strings.HasSuffix(privateKey, PEM_END) { | |||
privateKey += PEM_END | |||
} | |||
return privateKey | |||
} | |||
func getCanonicalHeaders(headers map[string]*string) (string, []string) { | |||
tmp := make(map[string]string) | |||
tmpHeader := http.Header{} | |||
for k, v := range headers { | |||
if strings.HasPrefix(strings.ToLower(k), "x-acs-") || strings.ToLower(k) == "host" || | |||
strings.ToLower(k) == "content-type" { | |||
tmp[strings.ToLower(k)] = strings.TrimSpace(tea.StringValue(v)) | |||
tmpHeader.Add(strings.ToLower(k), strings.TrimSpace(tea.StringValue(v))) | |||
} | |||
} | |||
hs := newSorter(tmp) | |||
// Sort the temp by the ascending order | |||
hs.Sort() | |||
canonicalheaders := "" | |||
for _, key := range hs.Keys { | |||
vals := tmpHeader[textproto.CanonicalMIMEHeaderKey(key)] | |||
sort.Strings(vals) | |||
canonicalheaders += key + ":" + strings.Join(vals, ",") + "\n" | |||
} | |||
return canonicalheaders, hs.Keys | |||
} | |||
func getCanonicalQueryString(query map[string]*string) string { | |||
canonicalQueryString := "" | |||
if tea.BoolValue(util.IsUnset(query)) { | |||
return canonicalQueryString | |||
} | |||
tmp := make(map[string]string) | |||
for k, v := range query { | |||
tmp[k] = tea.StringValue(v) | |||
} | |||
hs := newSorter(tmp) | |||
// Sort the temp by the ascending order | |||
hs.Sort() | |||
for i := range hs.Keys { | |||
if hs.Vals[i] != "" { | |||
canonicalQueryString += "&" + hs.Keys[i] + "=" + url.QueryEscape(hs.Vals[i]) | |||
} else { | |||
canonicalQueryString += "&" + hs.Keys[i] + "=" | |||
} | |||
} | |||
canonicalQueryString = strings.Replace(canonicalQueryString, "+", "%20", -1) | |||
canonicalQueryString = strings.Replace(canonicalQueryString, "*", "%2A", -1) | |||
canonicalQueryString = strings.Replace(canonicalQueryString, "%7E", "~", -1) | |||
if canonicalQueryString != "" { | |||
canonicalQueryString = strings.TrimLeft(canonicalQueryString, "&") | |||
} | |||
return canonicalQueryString | |||
} | |||
/** | |||
* Parse filter into a form string | |||
* @param filter object | |||
* @return the string | |||
*/ | |||
func ToForm(filter map[string]interface{}) (_result *string) { | |||
tmp := make(map[string]interface{}) | |||
byt, _ := json.Marshal(filter) | |||
d := json.NewDecoder(bytes.NewReader(byt)) | |||
d.UseNumber() | |||
_ = d.Decode(&tmp) | |||
result := make(map[string]*string) | |||
for key, value := range tmp { | |||
filterValue := reflect.ValueOf(value) | |||
flatRepeatedList(filterValue, result, key) | |||
} | |||
m := util.AnyifyMapValue(result) | |||
return util.ToFormString(m) | |||
} | |||
func flatRepeatedList(dataValue reflect.Value, result map[string]*string, prefix string) { | |||
if !dataValue.IsValid() { | |||
return | |||
} | |||
dataType := dataValue.Type() | |||
if dataType.Kind().String() == "slice" { | |||
handleRepeatedParams(dataValue, result, prefix) | |||
} else if dataType.Kind().String() == "map" { | |||
handleMap(dataValue, result, prefix) | |||
} else { | |||
result[prefix] = tea.String(fmt.Sprintf("%v", dataValue.Interface())) | |||
} | |||
} | |||
func handleRepeatedParams(repeatedFieldValue reflect.Value, result map[string]*string, prefix string) { | |||
if repeatedFieldValue.IsValid() && !repeatedFieldValue.IsNil() { | |||
for m := 0; m < repeatedFieldValue.Len(); m++ { | |||
elementValue := repeatedFieldValue.Index(m) | |||
key := prefix + "." + strconv.Itoa(m+1) | |||
fieldValue := reflect.ValueOf(elementValue.Interface()) | |||
if fieldValue.Kind().String() == "map" { | |||
handleMap(fieldValue, result, key) | |||
} else { | |||
result[key] = tea.String(fmt.Sprintf("%v", fieldValue.Interface())) | |||
} | |||
} | |||
} | |||
} | |||
func handleMap(valueField reflect.Value, result map[string]*string, prefix string) { | |||
if valueField.IsValid() && valueField.String() != "" { | |||
valueFieldType := valueField.Type() | |||
if valueFieldType.Kind().String() == "map" { | |||
var byt []byte | |||
byt, _ = json.Marshal(valueField.Interface()) | |||
cache := make(map[string]interface{}) | |||
d := json.NewDecoder(bytes.NewReader(byt)) | |||
d.UseNumber() | |||
_ = d.Decode(&cache) | |||
for key, value := range cache { | |||
pre := "" | |||
if prefix != "" { | |||
pre = prefix + "." + key | |||
} else { | |||
pre = key | |||
} | |||
fieldValue := reflect.ValueOf(value) | |||
flatRepeatedList(fieldValue, result, pre) | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Get timestamp | |||
* @return the timestamp string | |||
*/ | |||
func GetTimestamp() (_result *string) { | |||
gmt := time.FixedZone("GMT", 0) | |||
return tea.String(time.Now().In(gmt).Format("2006-01-02T15:04:05Z")) | |||
} | |||
/** | |||
* Parse filter into a object which's type is map[string]string | |||
* @param filter query param | |||
* @return the object | |||
*/ | |||
func Query(filter interface{}) (_result map[string]*string) { | |||
tmp := make(map[string]interface{}) | |||
byt, _ := json.Marshal(filter) | |||
d := json.NewDecoder(bytes.NewReader(byt)) | |||
d.UseNumber() | |||
_ = d.Decode(&tmp) | |||
result := make(map[string]*string) | |||
for key, value := range tmp { | |||
filterValue := reflect.ValueOf(value) | |||
flatRepeatedList(filterValue, result, key) | |||
} | |||
return result | |||
} | |||
/** | |||
* Get signature according to signedParams, method and secret | |||
* @param signedParams params which need to be signed | |||
* @param method http method e.g. GET | |||
* @param secret AccessKeySecret | |||
* @return the signature | |||
*/ | |||
func GetRPCSignature(signedParams map[string]*string, method *string, secret *string) (_result *string) { | |||
stringToSign := buildRpcStringToSign(signedParams, tea.StringValue(method)) | |||
signature := sign(stringToSign, tea.StringValue(secret), "&") | |||
return tea.String(signature) | |||
} | |||
/** | |||
* Parse array into a string with specified style | |||
* @param array the array | |||
* @param prefix the prefix string | |||
* @style specified style e.g. repeatList | |||
* @return the string | |||
*/ | |||
func ArrayToStringWithSpecifiedStyle(array interface{}, prefix *string, style *string) (_result *string) { | |||
if tea.BoolValue(util.IsUnset(array)) { | |||
return tea.String("") | |||
} | |||
sty := tea.StringValue(style) | |||
if sty == "repeatList" { | |||
tmp := map[string]interface{}{ | |||
tea.StringValue(prefix): array, | |||
} | |||
return flatRepeatList(tmp) | |||
} else if sty == "simple" || sty == "spaceDelimited" || sty == "pipeDelimited" { | |||
return flatArray(array, sty) | |||
} else if sty == "json" { | |||
return util.ToJSONString(array) | |||
} | |||
return tea.String("") | |||
} | |||
func ParseToMap(in interface{}) map[string]interface{} { | |||
if tea.BoolValue(util.IsUnset(in)) { | |||
return nil | |||
} | |||
tmp := make(map[string]interface{}) | |||
byt, _ := json.Marshal(in) | |||
d := json.NewDecoder(bytes.NewReader(byt)) | |||
d.UseNumber() | |||
err := d.Decode(&tmp) | |||
if err != nil { | |||
return nil | |||
} | |||
return tmp | |||
} | |||
func flatRepeatList(filter map[string]interface{}) (_result *string) { | |||
tmp := make(map[string]interface{}) | |||
byt, _ := json.Marshal(filter) | |||
d := json.NewDecoder(bytes.NewReader(byt)) | |||
d.UseNumber() | |||
_ = d.Decode(&tmp) | |||
result := make(map[string]*string) | |||
for key, value := range tmp { | |||
filterValue := reflect.ValueOf(value) | |||
flatRepeatedList(filterValue, result, key) | |||
} | |||
res := make(map[string]string) | |||
for k, v := range result { | |||
res[k] = tea.StringValue(v) | |||
} | |||
hs := newSorter(res) | |||
hs.Sort() | |||
// Get the canonicalizedOSSHeaders | |||
t := "" | |||
for i := range hs.Keys { | |||
if i == len(hs.Keys)-1 { | |||
t += hs.Keys[i] + "=" + hs.Vals[i] | |||
} else { | |||
t += hs.Keys[i] + "=" + hs.Vals[i] + "&&" | |||
} | |||
} | |||
return tea.String(t) | |||
} | |||
func flatArray(array interface{}, sty string) *string { | |||
t := reflect.ValueOf(array) | |||
strs := make([]string, 0) | |||
for i := 0; i < t.Len(); i++ { | |||
tmp := t.Index(i) | |||
if tmp.Kind() == reflect.Ptr || tmp.Kind() == reflect.Interface { | |||
tmp = tmp.Elem() | |||
} | |||
if tmp.Kind() == reflect.Ptr { | |||
tmp = tmp.Elem() | |||
} | |||
if tmp.Kind() == reflect.String { | |||
strs = append(strs, tmp.String()) | |||
} else { | |||
inter := tmp.Interface() | |||
byt, _ := json.Marshal(inter) | |||
strs = append(strs, string(byt)) | |||
} | |||
} | |||
str := "" | |||
if sty == "simple" { | |||
str = strings.Join(strs, ",") | |||
} else if sty == "spaceDelimited" { | |||
str = strings.Join(strs, " ") | |||
} else if sty == "pipeDelimited" { | |||
str = strings.Join(strs, "|") | |||
} | |||
return tea.String(str) | |||
} | |||
func buildRpcStringToSign(signedParam map[string]*string, method string) (stringToSign string) { | |||
signParams := make(map[string]string) | |||
for key, value := range signedParam { | |||
signParams[key] = tea.StringValue(value) | |||
} | |||
stringToSign = getUrlFormedMap(signParams) | |||
stringToSign = strings.Replace(stringToSign, "+", "%20", -1) | |||
stringToSign = strings.Replace(stringToSign, "*", "%2A", -1) | |||
stringToSign = strings.Replace(stringToSign, "%7E", "~", -1) | |||
stringToSign = url.QueryEscape(stringToSign) | |||
stringToSign = method + "&%2F&" + stringToSign | |||
return | |||
} | |||
func getUrlFormedMap(source map[string]string) (urlEncoded string) { | |||
urlEncoder := url.Values{} | |||
for key, value := range source { | |||
urlEncoder.Add(key, value) | |||
} | |||
urlEncoded = urlEncoder.Encode() | |||
return | |||
} | |||
func sign(stringToSign, accessKeySecret, secretSuffix string) string { | |||
secret := accessKeySecret + secretSuffix | |||
signedBytes := shaHmac1(stringToSign, secret) | |||
signedString := base64.StdEncoding.EncodeToString(signedBytes) | |||
return signedString | |||
} | |||
func shaHmac1(source, secret string) []byte { | |||
key := []byte(secret) | |||
hmac := hmac.New(sha1.New, key) | |||
hmac.Write([]byte(source)) | |||
return hmac.Sum(nil) | |||
} |
@@ -0,0 +1,462 @@ | |||
package service | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"io" | |||
"io/ioutil" | |||
"net/http" | |||
"net/url" | |||
"reflect" | |||
"runtime" | |||
"strconv" | |||
"strings" | |||
"time" | |||
"github.com/alibabacloud-go/tea/tea" | |||
) | |||
var defaultUserAgent = fmt.Sprintf("AlibabaCloud (%s; %s) Golang/%s Core/%s TeaDSL/1", runtime.GOOS, runtime.GOARCH, strings.Trim(runtime.Version(), "go"), "0.01") | |||
type RuntimeOptions struct { | |||
Autoretry *bool `json:"autoretry" xml:"autoretry"` | |||
IgnoreSSL *bool `json:"ignoreSSL" xml:"ignoreSSL"` | |||
MaxAttempts *int `json:"maxAttempts" xml:"maxAttempts"` | |||
BackoffPolicy *string `json:"backoffPolicy" xml:"backoffPolicy"` | |||
BackoffPeriod *int `json:"backoffPeriod" xml:"backoffPeriod"` | |||
ReadTimeout *int `json:"readTimeout" xml:"readTimeout"` | |||
ConnectTimeout *int `json:"connectTimeout" xml:"connectTimeout"` | |||
LocalAddr *string `json:"localAddr" xml:"localAddr"` | |||
HttpProxy *string `json:"httpProxy" xml:"httpProxy"` | |||
HttpsProxy *string `json:"httpsProxy" xml:"httpsProxy"` | |||
NoProxy *string `json:"noProxy" xml:"noProxy"` | |||
MaxIdleConns *int `json:"maxIdleConns" xml:"maxIdleConns"` | |||
Socks5Proxy *string `json:"socks5Proxy" xml:"socks5Proxy"` | |||
Socks5NetWork *string `json:"socks5NetWork" xml:"socks5NetWork"` | |||
} | |||
func (s RuntimeOptions) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s RuntimeOptions) GoString() string { | |||
return s.String() | |||
} | |||
func (s *RuntimeOptions) SetAutoretry(v bool) *RuntimeOptions { | |||
s.Autoretry = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetIgnoreSSL(v bool) *RuntimeOptions { | |||
s.IgnoreSSL = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetMaxAttempts(v int) *RuntimeOptions { | |||
s.MaxAttempts = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetBackoffPolicy(v string) *RuntimeOptions { | |||
s.BackoffPolicy = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetBackoffPeriod(v int) *RuntimeOptions { | |||
s.BackoffPeriod = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetReadTimeout(v int) *RuntimeOptions { | |||
s.ReadTimeout = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetConnectTimeout(v int) *RuntimeOptions { | |||
s.ConnectTimeout = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetHttpProxy(v string) *RuntimeOptions { | |||
s.HttpProxy = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetHttpsProxy(v string) *RuntimeOptions { | |||
s.HttpsProxy = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetNoProxy(v string) *RuntimeOptions { | |||
s.NoProxy = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetMaxIdleConns(v int) *RuntimeOptions { | |||
s.MaxIdleConns = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetLocalAddr(v string) *RuntimeOptions { | |||
s.LocalAddr = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetSocks5Proxy(v string) *RuntimeOptions { | |||
s.Socks5Proxy = &v | |||
return s | |||
} | |||
func (s *RuntimeOptions) SetSocks5NetWork(v string) *RuntimeOptions { | |||
s.Socks5NetWork = &v | |||
return s | |||
} | |||
func ReadAsString(body io.Reader) (*string, error) { | |||
byt, err := ioutil.ReadAll(body) | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
r, ok := body.(io.ReadCloser) | |||
if ok { | |||
r.Close() | |||
} | |||
return tea.String(string(byt)), nil | |||
} | |||
func StringifyMapValue(a map[string]interface{}) map[string]*string { | |||
res := make(map[string]*string) | |||
for key, value := range a { | |||
if value != nil { | |||
switch value.(type) { | |||
case string: | |||
res[key] = tea.String(value.(string)) | |||
default: | |||
byt, _ := json.Marshal(value) | |||
res[key] = tea.String(string(byt)) | |||
} | |||
} | |||
} | |||
return res | |||
} | |||
func AnyifyMapValue(a map[string]*string) map[string]interface{} { | |||
res := make(map[string]interface{}) | |||
for key, value := range a { | |||
res[key] = tea.StringValue(value) | |||
} | |||
return res | |||
} | |||
func ReadAsBytes(body io.Reader) ([]byte, error) { | |||
byt, err := ioutil.ReadAll(body) | |||
if err != nil { | |||
return nil, err | |||
} | |||
r, ok := body.(io.ReadCloser) | |||
if ok { | |||
r.Close() | |||
} | |||
return byt, nil | |||
} | |||
func DefaultString(reaStr, defaultStr *string) *string { | |||
if reaStr == nil { | |||
return defaultStr | |||
} | |||
return reaStr | |||
} | |||
func ToJSONString(a interface{}) *string { | |||
switch v := a.(type) { | |||
case *string: | |||
return v | |||
case string: | |||
return tea.String(v) | |||
case []byte: | |||
return tea.String(string(v)) | |||
case io.Reader: | |||
byt, err := ioutil.ReadAll(v) | |||
if err != nil { | |||
return nil | |||
} | |||
return tea.String(string(byt)) | |||
} | |||
byt, err := json.Marshal(a) | |||
if err != nil { | |||
return nil | |||
} | |||
return tea.String(string(byt)) | |||
} | |||
func DefaultNumber(reaNum, defaultNum *int) *int { | |||
if reaNum == nil { | |||
return defaultNum | |||
} | |||
return reaNum | |||
} | |||
func ReadAsJSON(body io.Reader) (result interface{}, err error) { | |||
byt, err := ioutil.ReadAll(body) | |||
if err != nil { | |||
return | |||
} | |||
if string(byt) == "" { | |||
return | |||
} | |||
r, ok := body.(io.ReadCloser) | |||
if ok { | |||
r.Close() | |||
} | |||
d := json.NewDecoder(bytes.NewReader(byt)) | |||
d.UseNumber() | |||
err = d.Decode(&result) | |||
return | |||
} | |||
func GetNonce() *string { | |||
return tea.String(getUUID()) | |||
} | |||
func Empty(val *string) *bool { | |||
return tea.Bool(val == nil || tea.StringValue(val) == "") | |||
} | |||
func ValidateModel(a interface{}) error { | |||
if a == nil { | |||
return nil | |||
} | |||
err := tea.Validate(a) | |||
return err | |||
} | |||
func EqualString(val1, val2 *string) *bool { | |||
return tea.Bool(tea.StringValue(val1) == tea.StringValue(val2)) | |||
} | |||
func EqualNumber(val1, val2 *int) *bool { | |||
return tea.Bool(tea.IntValue(val1) == tea.IntValue(val2)) | |||
} | |||
func IsUnset(val interface{}) *bool { | |||
if val == nil { | |||
return tea.Bool(true) | |||
} | |||
v := reflect.ValueOf(val) | |||
if v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map { | |||
return tea.Bool(v.IsNil()) | |||
} | |||
valType := reflect.TypeOf(val) | |||
valZero := reflect.Zero(valType) | |||
return tea.Bool(valZero == v) | |||
} | |||
func ToBytes(a *string) []byte { | |||
return []byte(tea.StringValue(a)) | |||
} | |||
func AssertAsMap(a interface{}) map[string]interface{} { | |||
r := reflect.ValueOf(a) | |||
if r.Kind().String() != "map" { | |||
panic(fmt.Sprintf("%v is not a map[string]interface{}", a)) | |||
} | |||
res := make(map[string]interface{}) | |||
tmp := r.MapKeys() | |||
for _, key := range tmp { | |||
res[key.String()] = r.MapIndex(key).Interface() | |||
} | |||
return res | |||
} | |||
func AssertAsNumber(a interface{}) *int { | |||
res := 0 | |||
switch a.(type) { | |||
case int: | |||
tmp := a.(int) | |||
res = tmp | |||
case *int: | |||
tmp := a.(*int) | |||
res = tea.IntValue(tmp) | |||
default: | |||
panic(fmt.Sprintf("%v is not a int", a)) | |||
} | |||
return tea.Int(res) | |||
} | |||
func AssertAsBoolean(a interface{}) *bool { | |||
res := false | |||
switch a.(type) { | |||
case bool: | |||
tmp := a.(bool) | |||
res = tmp | |||
case *bool: | |||
tmp := a.(*bool) | |||
res = tea.BoolValue(tmp) | |||
default: | |||
panic(fmt.Sprintf("%v is not a bool", a)) | |||
} | |||
return tea.Bool(res) | |||
} | |||
func AssertAsString(a interface{}) *string { | |||
res := "" | |||
switch a.(type) { | |||
case string: | |||
tmp := a.(string) | |||
res = tmp | |||
case *string: | |||
tmp := a.(*string) | |||
res = tea.StringValue(tmp) | |||
default: | |||
panic(fmt.Sprintf("%v is not a string", a)) | |||
} | |||
return tea.String(res) | |||
} | |||
func AssertAsBytes(a interface{}) []byte { | |||
res, ok := a.([]byte) | |||
if !ok { | |||
panic(fmt.Sprintf("%v is not []byte", a)) | |||
} | |||
return res | |||
} | |||
func AssertAsReadable(a interface{}) io.Reader { | |||
res, ok := a.(io.Reader) | |||
if !ok { | |||
panic(fmt.Sprintf("%v is not reader", a)) | |||
} | |||
return res | |||
} | |||
func AssertAsArray(a interface{}) []interface{} { | |||
r := reflect.ValueOf(a) | |||
if r.Kind().String() != "array" && r.Kind().String() != "slice" { | |||
panic(fmt.Sprintf("%v is not a [x]interface{}", a)) | |||
} | |||
aLen := r.Len() | |||
res := make([]interface{}, 0) | |||
for i := 0; i < aLen; i++ { | |||
res = append(res, r.Index(i).Interface()) | |||
} | |||
return res | |||
} | |||
func ParseJSON(a *string) interface{} { | |||
mapTmp := make(map[string]interface{}) | |||
d := json.NewDecoder(bytes.NewReader([]byte(tea.StringValue(a)))) | |||
d.UseNumber() | |||
err := d.Decode(&mapTmp) | |||
if err == nil { | |||
return mapTmp | |||
} | |||
sliceTmp := make([]interface{}, 0) | |||
d = json.NewDecoder(bytes.NewReader([]byte(tea.StringValue(a)))) | |||
d.UseNumber() | |||
err = d.Decode(&sliceTmp) | |||
if err == nil { | |||
return sliceTmp | |||
} | |||
if num, err := strconv.Atoi(tea.StringValue(a)); err == nil { | |||
return num | |||
} | |||
if ok, err := strconv.ParseBool(tea.StringValue(a)); err == nil { | |||
return ok | |||
} | |||
if floa64tVal, err := strconv.ParseFloat(tea.StringValue(a), 64); err == nil { | |||
return floa64tVal | |||
} | |||
return nil | |||
} | |||
func ToString(a []byte) *string { | |||
return tea.String(string(a)) | |||
} | |||
func ToMap(in interface{}) map[string]interface{} { | |||
if in == nil { | |||
return nil | |||
} | |||
res := tea.ToMap(in) | |||
return res | |||
} | |||
func ToFormString(a map[string]interface{}) *string { | |||
if a == nil { | |||
return tea.String("") | |||
} | |||
res := "" | |||
urlEncoder := url.Values{} | |||
for key, value := range a { | |||
v := fmt.Sprintf("%v", value) | |||
urlEncoder.Add(key, v) | |||
} | |||
res = urlEncoder.Encode() | |||
return tea.String(res) | |||
} | |||
func GetDateUTCString() *string { | |||
return tea.String(time.Now().UTC().Format(http.TimeFormat)) | |||
} | |||
func GetUserAgent(userAgent *string) *string { | |||
if userAgent != nil && tea.StringValue(userAgent) != "" { | |||
return tea.String(defaultUserAgent + " " + tea.StringValue(userAgent)) | |||
} | |||
return tea.String(defaultUserAgent) | |||
} | |||
func Is2xx(code *int) *bool { | |||
tmp := tea.IntValue(code) | |||
return tea.Bool(tmp >= 200 && tmp < 300) | |||
} | |||
func Is3xx(code *int) *bool { | |||
tmp := tea.IntValue(code) | |||
return tea.Bool(tmp >= 300 && tmp < 400) | |||
} | |||
func Is4xx(code *int) *bool { | |||
tmp := tea.IntValue(code) | |||
return tea.Bool(tmp >= 400 && tmp < 500) | |||
} | |||
func Is5xx(code *int) *bool { | |||
tmp := tea.IntValue(code) | |||
return tea.Bool(tmp >= 500 && tmp < 600) | |||
} | |||
func Sleep(millisecond *int) error { | |||
ms := tea.IntValue(millisecond) | |||
time.Sleep(time.Duration(ms) * time.Millisecond) | |||
return nil | |||
} | |||
func ToArray(in interface{}) []map[string]interface{} { | |||
if tea.BoolValue(IsUnset(in)) { | |||
return nil | |||
} | |||
tmp := make([]map[string]interface{}, 0) | |||
byt, _ := json.Marshal(in) | |||
d := json.NewDecoder(bytes.NewReader(byt)) | |||
d.UseNumber() | |||
err := d.Decode(&tmp) | |||
if err != nil { | |||
return nil | |||
} | |||
return tmp | |||
} |
@@ -0,0 +1,52 @@ | |||
package service | |||
import ( | |||
"crypto/md5" | |||
"crypto/rand" | |||
"encoding/hex" | |||
"hash" | |||
rand2 "math/rand" | |||
) | |||
type UUID [16]byte | |||
const numBytes = "1234567890" | |||
func getUUID() (uuidHex string) { | |||
uuid := newUUID() | |||
uuidHex = hex.EncodeToString(uuid[:]) | |||
return | |||
} | |||
func randStringBytes(n int) string { | |||
b := make([]byte, n) | |||
for i := range b { | |||
b[i] = numBytes[rand2.Intn(len(numBytes))] | |||
} | |||
return string(b) | |||
} | |||
func newUUID() UUID { | |||
ns := UUID{} | |||
safeRandom(ns[:]) | |||
u := newFromHash(md5.New(), ns, randStringBytes(16)) | |||
u[6] = (u[6] & 0x0f) | (byte(2) << 4) | |||
u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) | |||
return u | |||
} | |||
func newFromHash(h hash.Hash, ns UUID, name string) UUID { | |||
u := UUID{} | |||
h.Write(ns[:]) | |||
h.Write([]byte(name)) | |||
copy(u[:], h.Sum(nil)) | |||
return u | |||
} | |||
func safeRandom(dest []byte) { | |||
if _, err := rand.Read(dest); err != nil { | |||
panic(err) | |||
} | |||
} |
@@ -0,0 +1,105 @@ | |||
package service | |||
import ( | |||
"bytes" | |||
"encoding/xml" | |||
"fmt" | |||
"reflect" | |||
"strings" | |||
"github.com/alibabacloud-go/tea/tea" | |||
v2 "github.com/clbanning/mxj/v2" | |||
) | |||
func ToXML(obj map[string]interface{}) *string { | |||
return tea.String(mapToXML(obj)) | |||
} | |||
func ParseXml(val *string, result interface{}) map[string]interface{} { | |||
resp := make(map[string]interface{}) | |||
start := getStartElement([]byte(tea.StringValue(val))) | |||
if result == nil { | |||
vm, err := v2.NewMapXml([]byte(tea.StringValue(val))) | |||
if err != nil { | |||
return nil | |||
} | |||
return vm | |||
} | |||
out, err := xmlUnmarshal([]byte(tea.StringValue(val)), result) | |||
if err != nil { | |||
return resp | |||
} | |||
resp[start] = out | |||
return resp | |||
} | |||
func mapToXML(val map[string]interface{}) string { | |||
res := "" | |||
for key, value := range val { | |||
switch value.(type) { | |||
case []interface{}: | |||
for _, v := range value.([]interface{}) { | |||
switch v.(type) { | |||
case map[string]interface{}: | |||
res += `<` + key + `>` | |||
res += mapToXML(v.(map[string]interface{})) | |||
res += `</` + key + `>` | |||
default: | |||
if fmt.Sprintf("%v", v) != `<nil>` { | |||
res += `<` + key + `>` | |||
res += fmt.Sprintf("%v", v) | |||
res += `</` + key + `>` | |||
} | |||
} | |||
} | |||
case map[string]interface{}: | |||
res += `<` + key + `>` | |||
res += mapToXML(value.(map[string]interface{})) | |||
res += `</` + key + `>` | |||
default: | |||
if fmt.Sprintf("%v", value) != `<nil>` { | |||
res += `<` + key + `>` | |||
res += fmt.Sprintf("%v", value) | |||
res += `</` + key + `>` | |||
} | |||
} | |||
} | |||
return res | |||
} | |||
func getStartElement(body []byte) string { | |||
d := xml.NewDecoder(bytes.NewReader(body)) | |||
for { | |||
tok, err := d.Token() | |||
if err != nil { | |||
return "" | |||
} | |||
if t, ok := tok.(xml.StartElement); ok { | |||
return t.Name.Local | |||
} | |||
} | |||
} | |||
func xmlUnmarshal(body []byte, result interface{}) (interface{}, error) { | |||
start := getStartElement(body) | |||
dataValue := reflect.ValueOf(result).Elem() | |||
dataType := dataValue.Type() | |||
for i := 0; i < dataType.NumField(); i++ { | |||
field := dataType.Field(i) | |||
name, containsNameTag := field.Tag.Lookup("xml") | |||
name = strings.Replace(name, ",omitempty", "", -1) | |||
if containsNameTag { | |||
if name == start { | |||
realType := dataValue.Field(i).Type() | |||
realValue := reflect.New(realType).Interface() | |||
err := xml.Unmarshal(body, realValue) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return realValue, nil | |||
} | |||
} | |||
} | |||
return nil, nil | |||
} |
@@ -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 (c) 2009-present, Alibaba Cloud All rights reserved. | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. |
@@ -0,0 +1,333 @@ | |||
package tea | |||
import ( | |||
"encoding/json" | |||
"io" | |||
"math" | |||
"reflect" | |||
"strconv" | |||
"strings" | |||
"unsafe" | |||
jsoniter "github.com/json-iterator/go" | |||
"github.com/modern-go/reflect2" | |||
) | |||
const maxUint = ^uint(0) | |||
const maxInt = int(maxUint >> 1) | |||
const minInt = -maxInt - 1 | |||
var jsonParser jsoniter.API | |||
func init() { | |||
jsonParser = jsoniter.Config{ | |||
EscapeHTML: true, | |||
SortMapKeys: true, | |||
ValidateJsonRawMessage: true, | |||
CaseSensitive: true, | |||
}.Froze() | |||
jsonParser.RegisterExtension(newBetterFuzzyExtension()) | |||
} | |||
func newBetterFuzzyExtension() jsoniter.DecoderExtension { | |||
return jsoniter.DecoderExtension{ | |||
reflect2.DefaultTypeOfKind(reflect.String): &nullableFuzzyStringDecoder{}, | |||
reflect2.DefaultTypeOfKind(reflect.Bool): &fuzzyBoolDecoder{}, | |||
reflect2.DefaultTypeOfKind(reflect.Float32): &nullableFuzzyFloat32Decoder{}, | |||
reflect2.DefaultTypeOfKind(reflect.Float64): &nullableFuzzyFloat64Decoder{}, | |||
reflect2.DefaultTypeOfKind(reflect.Int): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(maxInt) || val < float64(minInt) { | |||
iter.ReportError("fuzzy decode int", "exceed range") | |||
return | |||
} | |||
*((*int)(ptr)) = int(val) | |||
} else { | |||
*((*int)(ptr)) = iter.ReadInt() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Uint): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(maxUint) || val < 0 { | |||
iter.ReportError("fuzzy decode uint", "exceed range") | |||
return | |||
} | |||
*((*uint)(ptr)) = uint(val) | |||
} else { | |||
*((*uint)(ptr)) = iter.ReadUint() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Int8): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(math.MaxInt8) || val < float64(math.MinInt8) { | |||
iter.ReportError("fuzzy decode int8", "exceed range") | |||
return | |||
} | |||
*((*int8)(ptr)) = int8(val) | |||
} else { | |||
*((*int8)(ptr)) = iter.ReadInt8() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Uint8): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(math.MaxUint8) || val < 0 { | |||
iter.ReportError("fuzzy decode uint8", "exceed range") | |||
return | |||
} | |||
*((*uint8)(ptr)) = uint8(val) | |||
} else { | |||
*((*uint8)(ptr)) = iter.ReadUint8() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Int16): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(math.MaxInt16) || val < float64(math.MinInt16) { | |||
iter.ReportError("fuzzy decode int16", "exceed range") | |||
return | |||
} | |||
*((*int16)(ptr)) = int16(val) | |||
} else { | |||
*((*int16)(ptr)) = iter.ReadInt16() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Uint16): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(math.MaxUint16) || val < 0 { | |||
iter.ReportError("fuzzy decode uint16", "exceed range") | |||
return | |||
} | |||
*((*uint16)(ptr)) = uint16(val) | |||
} else { | |||
*((*uint16)(ptr)) = iter.ReadUint16() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Int32): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(math.MaxInt32) || val < float64(math.MinInt32) { | |||
iter.ReportError("fuzzy decode int32", "exceed range") | |||
return | |||
} | |||
*((*int32)(ptr)) = int32(val) | |||
} else { | |||
*((*int32)(ptr)) = iter.ReadInt32() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Uint32): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(math.MaxUint32) || val < 0 { | |||
iter.ReportError("fuzzy decode uint32", "exceed range") | |||
return | |||
} | |||
*((*uint32)(ptr)) = uint32(val) | |||
} else { | |||
*((*uint32)(ptr)) = iter.ReadUint32() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Int64): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(math.MaxInt64) || val < float64(math.MinInt64) { | |||
iter.ReportError("fuzzy decode int64", "exceed range") | |||
return | |||
} | |||
*((*int64)(ptr)) = int64(val) | |||
} else { | |||
*((*int64)(ptr)) = iter.ReadInt64() | |||
} | |||
}}, | |||
reflect2.DefaultTypeOfKind(reflect.Uint64): &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
if isFloat { | |||
val := iter.ReadFloat64() | |||
if val > float64(math.MaxUint64) || val < 0 { | |||
iter.ReportError("fuzzy decode uint64", "exceed range") | |||
return | |||
} | |||
*((*uint64)(ptr)) = uint64(val) | |||
} else { | |||
*((*uint64)(ptr)) = iter.ReadUint64() | |||
} | |||
}}, | |||
} | |||
} | |||
type nullableFuzzyStringDecoder struct { | |||
} | |||
func (decoder *nullableFuzzyStringDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
valueType := iter.WhatIsNext() | |||
switch valueType { | |||
case jsoniter.NumberValue: | |||
var number json.Number | |||
iter.ReadVal(&number) | |||
*((*string)(ptr)) = string(number) | |||
case jsoniter.StringValue: | |||
*((*string)(ptr)) = iter.ReadString() | |||
case jsoniter.BoolValue: | |||
*((*string)(ptr)) = strconv.FormatBool(iter.ReadBool()) | |||
case jsoniter.NilValue: | |||
iter.ReadNil() | |||
*((*string)(ptr)) = "" | |||
default: | |||
iter.ReportError("fuzzyStringDecoder", "not number or string or bool") | |||
} | |||
} | |||
type fuzzyBoolDecoder struct { | |||
} | |||
func (decoder *fuzzyBoolDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
valueType := iter.WhatIsNext() | |||
switch valueType { | |||
case jsoniter.BoolValue: | |||
*((*bool)(ptr)) = iter.ReadBool() | |||
case jsoniter.NumberValue: | |||
var number json.Number | |||
iter.ReadVal(&number) | |||
num, err := number.Int64() | |||
if err != nil { | |||
iter.ReportError("fuzzyBoolDecoder", "get value from json.number failed") | |||
} | |||
if num == 0 { | |||
*((*bool)(ptr)) = false | |||
} else { | |||
*((*bool)(ptr)) = true | |||
} | |||
case jsoniter.StringValue: | |||
strValue := strings.ToLower(iter.ReadString()) | |||
if strValue == "true" { | |||
*((*bool)(ptr)) = true | |||
} else if strValue == "false" || strValue == "" { | |||
*((*bool)(ptr)) = false | |||
} else { | |||
iter.ReportError("fuzzyBoolDecoder", "unsupported bool value: "+strValue) | |||
} | |||
case jsoniter.NilValue: | |||
iter.ReadNil() | |||
*((*bool)(ptr)) = false | |||
default: | |||
iter.ReportError("fuzzyBoolDecoder", "not number or string or nil") | |||
} | |||
} | |||
type nullableFuzzyIntegerDecoder struct { | |||
fun func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) | |||
} | |||
func (decoder *nullableFuzzyIntegerDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
valueType := iter.WhatIsNext() | |||
var str string | |||
switch valueType { | |||
case jsoniter.NumberValue: | |||
var number json.Number | |||
iter.ReadVal(&number) | |||
str = string(number) | |||
case jsoniter.StringValue: | |||
str = iter.ReadString() | |||
// support empty string | |||
if str == "" { | |||
str = "0" | |||
} | |||
case jsoniter.BoolValue: | |||
if iter.ReadBool() { | |||
str = "1" | |||
} else { | |||
str = "0" | |||
} | |||
case jsoniter.NilValue: | |||
iter.ReadNil() | |||
str = "0" | |||
default: | |||
iter.ReportError("fuzzyIntegerDecoder", "not number or string") | |||
} | |||
newIter := iter.Pool().BorrowIterator([]byte(str)) | |||
defer iter.Pool().ReturnIterator(newIter) | |||
isFloat := strings.IndexByte(str, '.') != -1 | |||
decoder.fun(isFloat, ptr, newIter) | |||
if newIter.Error != nil && newIter.Error != io.EOF { | |||
iter.Error = newIter.Error | |||
} | |||
} | |||
type nullableFuzzyFloat32Decoder struct { | |||
} | |||
func (decoder *nullableFuzzyFloat32Decoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
valueType := iter.WhatIsNext() | |||
var str string | |||
switch valueType { | |||
case jsoniter.NumberValue: | |||
*((*float32)(ptr)) = iter.ReadFloat32() | |||
case jsoniter.StringValue: | |||
str = iter.ReadString() | |||
// support empty string | |||
if str == "" { | |||
*((*float32)(ptr)) = 0 | |||
return | |||
} | |||
newIter := iter.Pool().BorrowIterator([]byte(str)) | |||
defer iter.Pool().ReturnIterator(newIter) | |||
*((*float32)(ptr)) = newIter.ReadFloat32() | |||
if newIter.Error != nil && newIter.Error != io.EOF { | |||
iter.Error = newIter.Error | |||
} | |||
case jsoniter.BoolValue: | |||
// support bool to float32 | |||
if iter.ReadBool() { | |||
*((*float32)(ptr)) = 1 | |||
} else { | |||
*((*float32)(ptr)) = 0 | |||
} | |||
case jsoniter.NilValue: | |||
iter.ReadNil() | |||
*((*float32)(ptr)) = 0 | |||
default: | |||
iter.ReportError("nullableFuzzyFloat32Decoder", "not number or string") | |||
} | |||
} | |||
type nullableFuzzyFloat64Decoder struct { | |||
} | |||
func (decoder *nullableFuzzyFloat64Decoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { | |||
valueType := iter.WhatIsNext() | |||
var str string | |||
switch valueType { | |||
case jsoniter.NumberValue: | |||
*((*float64)(ptr)) = iter.ReadFloat64() | |||
case jsoniter.StringValue: | |||
str = iter.ReadString() | |||
// support empty string | |||
if str == "" { | |||
*((*float64)(ptr)) = 0 | |||
return | |||
} | |||
newIter := iter.Pool().BorrowIterator([]byte(str)) | |||
defer iter.Pool().ReturnIterator(newIter) | |||
*((*float64)(ptr)) = newIter.ReadFloat64() | |||
if newIter.Error != nil && newIter.Error != io.EOF { | |||
iter.Error = newIter.Error | |||
} | |||
case jsoniter.BoolValue: | |||
// support bool to float64 | |||
if iter.ReadBool() { | |||
*((*float64)(ptr)) = 1 | |||
} else { | |||
*((*float64)(ptr)) = 0 | |||
} | |||
case jsoniter.NilValue: | |||
// support empty string | |||
iter.ReadNil() | |||
*((*float64)(ptr)) = 0 | |||
default: | |||
iter.ReportError("nullableFuzzyFloat64Decoder", "not number or string") | |||
} | |||
} |
@@ -0,0 +1,1121 @@ | |||
package tea | |||
import ( | |||
"bytes" | |||
"context" | |||
"crypto/tls" | |||
"crypto/x509" | |||
"encoding/base64" | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"io" | |||
"math" | |||
"math/rand" | |||
"net" | |||
"net/http" | |||
"net/url" | |||
"os" | |||
"reflect" | |||
"regexp" | |||
"strconv" | |||
"strings" | |||
"sync" | |||
"time" | |||
"github.com/alibabacloud-go/debug/debug" | |||
"github.com/alibabacloud-go/tea/utils" | |||
"golang.org/x/net/proxy" | |||
) | |||
var debugLog = debug.Init("tea") | |||
var hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) { | |||
return fn | |||
} | |||
var basicTypes = []string{ | |||
"int", "int16", "int64", "int32", "float32", "float64", "string", "bool", "uint64", "uint32", "uint16", | |||
} | |||
// Verify whether the parameters meet the requirements | |||
var validateParams = []string{"require", "pattern", "maxLength", "minLength", "maximum", "minimum", "maxItems", "minItems"} | |||
// CastError is used for cast type fails | |||
type CastError struct { | |||
Message *string | |||
} | |||
// Request is used wrap http request | |||
type Request struct { | |||
Protocol *string | |||
Port *int | |||
Method *string | |||
Pathname *string | |||
Domain *string | |||
Headers map[string]*string | |||
Query map[string]*string | |||
Body io.Reader | |||
} | |||
// Response is use d wrap http response | |||
type Response struct { | |||
Body io.ReadCloser | |||
StatusCode *int | |||
StatusMessage *string | |||
Headers map[string]*string | |||
} | |||
// SDKError struct is used save error code and message | |||
type SDKError struct { | |||
Code *string | |||
StatusCode *int | |||
Message *string | |||
Data *string | |||
Stack *string | |||
errMsg *string | |||
} | |||
// RuntimeObject is used for converting http configuration | |||
type RuntimeObject struct { | |||
IgnoreSSL *bool `json:"ignoreSSL" xml:"ignoreSSL"` | |||
ReadTimeout *int `json:"readTimeout" xml:"readTimeout"` | |||
ConnectTimeout *int `json:"connectTimeout" xml:"connectTimeout"` | |||
LocalAddr *string `json:"localAddr" xml:"localAddr"` | |||
HttpProxy *string `json:"httpProxy" xml:"httpProxy"` | |||
HttpsProxy *string `json:"httpsProxy" xml:"httpsProxy"` | |||
NoProxy *string `json:"noProxy" xml:"noProxy"` | |||
MaxIdleConns *int `json:"maxIdleConns" xml:"maxIdleConns"` | |||
Key *string `json:"key" xml:"key"` | |||
Cert *string `json:"cert" xml:"cert"` | |||
CA *string `json:"ca" xml:"ca"` | |||
Socks5Proxy *string `json:"socks5Proxy" xml:"socks5Proxy"` | |||
Socks5NetWork *string `json:"socks5NetWork" xml:"socks5NetWork"` | |||
Listener utils.ProgressListener `json:"listener" xml:"listener"` | |||
Tracker *utils.ReaderTracker `json:"tracker" xml:"tracker"` | |||
Logger *utils.Logger `json:"logger" xml:"logger"` | |||
} | |||
type teaClient struct { | |||
sync.Mutex | |||
httpClient *http.Client | |||
ifInit bool | |||
} | |||
var clientPool = &sync.Map{} | |||
func (r *RuntimeObject) getClientTag(domain string) string { | |||
return strconv.FormatBool(BoolValue(r.IgnoreSSL)) + strconv.Itoa(IntValue(r.ReadTimeout)) + | |||
strconv.Itoa(IntValue(r.ConnectTimeout)) + StringValue(r.LocalAddr) + StringValue(r.HttpProxy) + | |||
StringValue(r.HttpsProxy) + StringValue(r.NoProxy) + StringValue(r.Socks5Proxy) + StringValue(r.Socks5NetWork) + domain | |||
} | |||
// NewRuntimeObject is used for shortly create runtime object | |||
func NewRuntimeObject(runtime map[string]interface{}) *RuntimeObject { | |||
if runtime == nil { | |||
return &RuntimeObject{} | |||
} | |||
runtimeObject := &RuntimeObject{ | |||
IgnoreSSL: TransInterfaceToBool(runtime["ignoreSSL"]), | |||
ReadTimeout: TransInterfaceToInt(runtime["readTimeout"]), | |||
ConnectTimeout: TransInterfaceToInt(runtime["connectTimeout"]), | |||
LocalAddr: TransInterfaceToString(runtime["localAddr"]), | |||
HttpProxy: TransInterfaceToString(runtime["httpProxy"]), | |||
HttpsProxy: TransInterfaceToString(runtime["httpsProxy"]), | |||
NoProxy: TransInterfaceToString(runtime["noProxy"]), | |||
MaxIdleConns: TransInterfaceToInt(runtime["maxIdleConns"]), | |||
Socks5Proxy: TransInterfaceToString(runtime["socks5Proxy"]), | |||
Socks5NetWork: TransInterfaceToString(runtime["socks5NetWork"]), | |||
Key: TransInterfaceToString(runtime["key"]), | |||
Cert: TransInterfaceToString(runtime["cert"]), | |||
CA: TransInterfaceToString(runtime["ca"]), | |||
} | |||
if runtime["listener"] != nil { | |||
runtimeObject.Listener = runtime["listener"].(utils.ProgressListener) | |||
} | |||
if runtime["tracker"] != nil { | |||
runtimeObject.Tracker = runtime["tracker"].(*utils.ReaderTracker) | |||
} | |||
if runtime["logger"] != nil { | |||
runtimeObject.Logger = runtime["logger"].(*utils.Logger) | |||
} | |||
return runtimeObject | |||
} | |||
// NewCastError is used for cast type fails | |||
func NewCastError(message *string) (err error) { | |||
return &CastError{ | |||
Message: message, | |||
} | |||
} | |||
// NewRequest is used shortly create Request | |||
func NewRequest() (req *Request) { | |||
return &Request{ | |||
Headers: map[string]*string{}, | |||
Query: map[string]*string{}, | |||
} | |||
} | |||
// NewResponse is create response with http response | |||
func NewResponse(httpResponse *http.Response) (res *Response) { | |||
res = &Response{} | |||
res.Body = httpResponse.Body | |||
res.Headers = make(map[string]*string) | |||
res.StatusCode = Int(httpResponse.StatusCode) | |||
res.StatusMessage = String(httpResponse.Status) | |||
return | |||
} | |||
// NewSDKError is used for shortly create SDKError object | |||
func NewSDKError(obj map[string]interface{}) *SDKError { | |||
err := &SDKError{} | |||
if val, ok := obj["code"].(int); ok { | |||
err.Code = String(strconv.Itoa(val)) | |||
} else if val, ok := obj["code"].(string); ok { | |||
err.Code = String(val) | |||
} | |||
if statusCode, ok := obj["statusCode"].(int); ok { | |||
err.StatusCode = Int(statusCode) | |||
} else if status, ok := obj["statusCode"].(string); ok { | |||
statusCode, err2 := strconv.Atoi(status) | |||
if err2 == nil { | |||
err.StatusCode = Int(statusCode) | |||
} | |||
} | |||
if obj["message"] != nil { | |||
err.Message = String(obj["message"].(string)) | |||
} | |||
if data := obj["data"]; data != nil { | |||
byt, _ := json.Marshal(data) | |||
err.Data = String(string(byt)) | |||
} | |||
return err | |||
} | |||
// Set ErrMsg by msg | |||
func (err *SDKError) SetErrMsg(msg string) { | |||
err.errMsg = String(msg) | |||
} | |||
func (err *SDKError) Error() string { | |||
if err.errMsg == nil { | |||
str := fmt.Sprintf("SDKError:\n StatusCode: %d\n Code: %s\n Message: %s\n Data: %s\n", | |||
IntValue(err.StatusCode), StringValue(err.Code), StringValue(err.Message), StringValue(err.Data)) | |||
err.SetErrMsg(str) | |||
} | |||
return StringValue(err.errMsg) | |||
} | |||
// Return message of CastError | |||
func (err *CastError) Error() string { | |||
return StringValue(err.Message) | |||
} | |||
// Convert is use convert map[string]interface object to struct | |||
func Convert(in interface{}, out interface{}) error { | |||
byt, _ := json.Marshal(in) | |||
decoder := jsonParser.NewDecoder(bytes.NewReader(byt)) | |||
decoder.UseNumber() | |||
err := decoder.Decode(&out) | |||
return err | |||
} | |||
// Convert is use convert map[string]interface object to struct | |||
func Recover(in interface{}) error { | |||
if in == nil { | |||
return nil | |||
} | |||
return errors.New(fmt.Sprint(in)) | |||
} | |||
// ReadBody is used read response body | |||
func (response *Response) ReadBody() (body []byte, err error) { | |||
defer response.Body.Close() | |||
var buffer [512]byte | |||
result := bytes.NewBuffer(nil) | |||
for { | |||
n, err := response.Body.Read(buffer[0:]) | |||
result.Write(buffer[0:n]) | |||
if err != nil && err == io.EOF { | |||
break | |||
} else if err != nil { | |||
return nil, err | |||
} | |||
} | |||
return result.Bytes(), nil | |||
} | |||
func getTeaClient(tag string) *teaClient { | |||
client, ok := clientPool.Load(tag) | |||
if client == nil && !ok { | |||
client = &teaClient{ | |||
httpClient: &http.Client{}, | |||
ifInit: false, | |||
} | |||
clientPool.Store(tag, client) | |||
} | |||
return client.(*teaClient) | |||
} | |||
// DoRequest is used send request to server | |||
func DoRequest(request *Request, requestRuntime map[string]interface{}) (response *Response, err error) { | |||
runtimeObject := NewRuntimeObject(requestRuntime) | |||
fieldMap := make(map[string]string) | |||
utils.InitLogMsg(fieldMap) | |||
defer func() { | |||
if runtimeObject.Logger != nil { | |||
runtimeObject.Logger.PrintLog(fieldMap, err) | |||
} | |||
}() | |||
if request.Method == nil { | |||
request.Method = String("GET") | |||
} | |||
if request.Protocol == nil { | |||
request.Protocol = String("http") | |||
} else { | |||
request.Protocol = String(strings.ToLower(StringValue(request.Protocol))) | |||
} | |||
requestURL := "" | |||
request.Domain = request.Headers["host"] | |||
requestURL = fmt.Sprintf("%s://%s%s", StringValue(request.Protocol), StringValue(request.Domain), StringValue(request.Pathname)) | |||
queryParams := request.Query | |||
// sort QueryParams by key | |||
q := url.Values{} | |||
for key, value := range queryParams { | |||
q.Add(key, StringValue(value)) | |||
} | |||
querystring := q.Encode() | |||
if len(querystring) > 0 { | |||
if strings.Contains(requestURL, "?") { | |||
requestURL = fmt.Sprintf("%s&%s", requestURL, querystring) | |||
} else { | |||
requestURL = fmt.Sprintf("%s?%s", requestURL, querystring) | |||
} | |||
} | |||
debugLog("> %s %s", StringValue(request.Method), requestURL) | |||
httpRequest, err := http.NewRequest(StringValue(request.Method), requestURL, request.Body) | |||
if err != nil { | |||
return | |||
} | |||
httpRequest.Host = StringValue(request.Domain) | |||
client := getTeaClient(runtimeObject.getClientTag(StringValue(request.Domain))) | |||
client.Lock() | |||
if !client.ifInit { | |||
trans, err := getHttpTransport(request, runtimeObject) | |||
if err != nil { | |||
return nil, err | |||
} | |||
client.httpClient.Timeout = time.Duration(IntValue(runtimeObject.ReadTimeout)) * time.Millisecond | |||
client.httpClient.Transport = trans | |||
client.ifInit = true | |||
} | |||
client.Unlock() | |||
for key, value := range request.Headers { | |||
if value == nil || key == "content-length" { | |||
continue | |||
} else if key == "host" { | |||
httpRequest.Header["Host"] = []string{*value} | |||
delete(httpRequest.Header, "host") | |||
} else if key == "user-agent" { | |||
httpRequest.Header["User-Agent"] = []string{*value} | |||
delete(httpRequest.Header, "user-agent") | |||
} else { | |||
httpRequest.Header[key] = []string{*value} | |||
} | |||
debugLog("> %s: %s", key, StringValue(value)) | |||
} | |||
contentlength, _ := strconv.Atoi(StringValue(request.Headers["content-length"])) | |||
event := utils.NewProgressEvent(utils.TransferStartedEvent, 0, int64(contentlength), 0) | |||
utils.PublishProgress(runtimeObject.Listener, event) | |||
putMsgToMap(fieldMap, httpRequest) | |||
startTime := time.Now() | |||
fieldMap["{start_time}"] = startTime.Format("2006-01-02 15:04:05") | |||
res, err := hookDo(client.httpClient.Do)(httpRequest) | |||
fieldMap["{cost}"] = time.Since(startTime).String() | |||
completedBytes := int64(0) | |||
if runtimeObject.Tracker != nil { | |||
completedBytes = runtimeObject.Tracker.CompletedBytes | |||
} | |||
if err != nil { | |||
event = utils.NewProgressEvent(utils.TransferFailedEvent, completedBytes, int64(contentlength), 0) | |||
utils.PublishProgress(runtimeObject.Listener, event) | |||
return | |||
} | |||
event = utils.NewProgressEvent(utils.TransferCompletedEvent, completedBytes, int64(contentlength), 0) | |||
utils.PublishProgress(runtimeObject.Listener, event) | |||
response = NewResponse(res) | |||
fieldMap["{code}"] = strconv.Itoa(res.StatusCode) | |||
fieldMap["{res_headers}"] = transToString(res.Header) | |||
debugLog("< HTTP/1.1 %s", res.Status) | |||
for key, value := range res.Header { | |||
debugLog("< %s: %s", key, strings.Join(value, "")) | |||
if len(value) != 0 { | |||
response.Headers[strings.ToLower(key)] = String(value[0]) | |||
} | |||
} | |||
return | |||
} | |||
func getHttpTransport(req *Request, runtime *RuntimeObject) (*http.Transport, error) { | |||
trans := new(http.Transport) | |||
httpProxy, err := getHttpProxy(StringValue(req.Protocol), StringValue(req.Domain), runtime) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if strings.ToLower(*req.Protocol) == "https" && | |||
runtime.Key != nil && runtime.Cert != nil { | |||
cert, err := tls.X509KeyPair([]byte(StringValue(runtime.Cert)), []byte(StringValue(runtime.Key))) | |||
if err != nil { | |||
return nil, err | |||
} | |||
trans.TLSClientConfig = &tls.Config{ | |||
Certificates: []tls.Certificate{cert}, | |||
InsecureSkipVerify: BoolValue(runtime.IgnoreSSL), | |||
} | |||
if runtime.CA != nil { | |||
clientCertPool := x509.NewCertPool() | |||
ok := clientCertPool.AppendCertsFromPEM([]byte(StringValue(runtime.CA))) | |||
if !ok { | |||
return nil, errors.New("Failed to parse root certificate") | |||
} | |||
trans.TLSClientConfig.RootCAs = clientCertPool | |||
} | |||
} else { | |||
trans.TLSClientConfig = &tls.Config{ | |||
InsecureSkipVerify: BoolValue(runtime.IgnoreSSL), | |||
} | |||
} | |||
if httpProxy != nil { | |||
trans.Proxy = http.ProxyURL(httpProxy) | |||
if httpProxy.User != nil { | |||
password, _ := httpProxy.User.Password() | |||
auth := httpProxy.User.Username() + ":" + password | |||
basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) | |||
req.Headers["Proxy-Authorization"] = String(basic) | |||
} | |||
} | |||
if runtime.Socks5Proxy != nil && StringValue(runtime.Socks5Proxy) != "" { | |||
socks5Proxy, err := getSocks5Proxy(runtime) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if socks5Proxy != nil { | |||
var auth *proxy.Auth | |||
if socks5Proxy.User != nil { | |||
password, _ := socks5Proxy.User.Password() | |||
auth = &proxy.Auth{ | |||
User: socks5Proxy.User.Username(), | |||
Password: password, | |||
} | |||
} | |||
dialer, err := proxy.SOCKS5(strings.ToLower(StringValue(runtime.Socks5NetWork)), socks5Proxy.String(), auth, | |||
&net.Dialer{ | |||
Timeout: time.Duration(IntValue(runtime.ConnectTimeout)) * time.Millisecond, | |||
DualStack: true, | |||
LocalAddr: getLocalAddr(StringValue(runtime.LocalAddr)), | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
trans.Dial = dialer.Dial | |||
} | |||
} else { | |||
trans.DialContext = setDialContext(runtime) | |||
} | |||
return trans, nil | |||
} | |||
func transToString(object interface{}) string { | |||
byt, _ := json.Marshal(object) | |||
return string(byt) | |||
} | |||
func putMsgToMap(fieldMap map[string]string, request *http.Request) { | |||
fieldMap["{host}"] = request.Host | |||
fieldMap["{method}"] = request.Method | |||
fieldMap["{uri}"] = request.URL.RequestURI() | |||
fieldMap["{pid}"] = strconv.Itoa(os.Getpid()) | |||
fieldMap["{version}"] = strings.Split(request.Proto, "/")[1] | |||
hostname, _ := os.Hostname() | |||
fieldMap["{hostname}"] = hostname | |||
fieldMap["{req_headers}"] = transToString(request.Header) | |||
fieldMap["{target}"] = request.URL.Path + request.URL.RawQuery | |||
} | |||
func getNoProxy(protocol string, runtime *RuntimeObject) []string { | |||
var urls []string | |||
if runtime.NoProxy != nil && StringValue(runtime.NoProxy) != "" { | |||
urls = strings.Split(StringValue(runtime.NoProxy), ",") | |||
} else if rawurl := os.Getenv("NO_PROXY"); rawurl != "" { | |||
urls = strings.Split(rawurl, ",") | |||
} else if rawurl := os.Getenv("no_proxy"); rawurl != "" { | |||
urls = strings.Split(rawurl, ",") | |||
} | |||
return urls | |||
} | |||
func ToReader(obj interface{}) io.Reader { | |||
switch obj.(type) { | |||
case *string: | |||
tmp := obj.(*string) | |||
return strings.NewReader(StringValue(tmp)) | |||
case []byte: | |||
return strings.NewReader(string(obj.([]byte))) | |||
case io.Reader: | |||
return obj.(io.Reader) | |||
default: | |||
panic("Invalid Body. Please set a valid Body.") | |||
} | |||
} | |||
func ToString(val interface{}) string { | |||
return fmt.Sprintf("%v", val) | |||
} | |||
func getHttpProxy(protocol, host string, runtime *RuntimeObject) (proxy *url.URL, err error) { | |||
urls := getNoProxy(protocol, runtime) | |||
for _, url := range urls { | |||
if url == host { | |||
return nil, nil | |||
} | |||
} | |||
if protocol == "https" { | |||
if runtime.HttpsProxy != nil && StringValue(runtime.HttpsProxy) != "" { | |||
proxy, err = url.Parse(StringValue(runtime.HttpsProxy)) | |||
} else if rawurl := os.Getenv("HTTPS_PROXY"); rawurl != "" { | |||
proxy, err = url.Parse(rawurl) | |||
} else if rawurl := os.Getenv("https_proxy"); rawurl != "" { | |||
proxy, err = url.Parse(rawurl) | |||
} | |||
} else { | |||
if runtime.HttpProxy != nil && StringValue(runtime.HttpProxy) != "" { | |||
proxy, err = url.Parse(StringValue(runtime.HttpProxy)) | |||
} else if rawurl := os.Getenv("HTTP_PROXY"); rawurl != "" { | |||
proxy, err = url.Parse(rawurl) | |||
} else if rawurl := os.Getenv("http_proxy"); rawurl != "" { | |||
proxy, err = url.Parse(rawurl) | |||
} | |||
} | |||
return proxy, err | |||
} | |||
func getSocks5Proxy(runtime *RuntimeObject) (proxy *url.URL, err error) { | |||
if runtime.Socks5Proxy != nil && StringValue(runtime.Socks5Proxy) != "" { | |||
proxy, err = url.Parse(StringValue(runtime.Socks5Proxy)) | |||
} | |||
return proxy, err | |||
} | |||
func getLocalAddr(localAddr string) (addr *net.TCPAddr) { | |||
if localAddr != "" { | |||
addr = &net.TCPAddr{ | |||
IP: []byte(localAddr), | |||
} | |||
} | |||
return addr | |||
} | |||
func setDialContext(runtime *RuntimeObject) func(cxt context.Context, net, addr string) (c net.Conn, err error) { | |||
return func(ctx context.Context, network, address string) (net.Conn, error) { | |||
if runtime.LocalAddr != nil && StringValue(runtime.LocalAddr) != "" { | |||
netAddr := &net.TCPAddr{ | |||
IP: []byte(StringValue(runtime.LocalAddr)), | |||
} | |||
return (&net.Dialer{ | |||
Timeout: time.Duration(IntValue(runtime.ConnectTimeout)) * time.Second, | |||
DualStack: true, | |||
LocalAddr: netAddr, | |||
}).DialContext(ctx, network, address) | |||
} | |||
return (&net.Dialer{ | |||
Timeout: time.Duration(IntValue(runtime.ConnectTimeout)) * time.Second, | |||
DualStack: true, | |||
}).DialContext(ctx, network, address) | |||
} | |||
} | |||
func ToObject(obj interface{}) map[string]interface{} { | |||
result := make(map[string]interface{}) | |||
byt, _ := json.Marshal(obj) | |||
err := json.Unmarshal(byt, &result) | |||
if err != nil { | |||
return nil | |||
} | |||
return result | |||
} | |||
func AllowRetry(retry interface{}, retryTimes *int) *bool { | |||
if IntValue(retryTimes) == 0 { | |||
return Bool(true) | |||
} | |||
retryMap, ok := retry.(map[string]interface{}) | |||
if !ok { | |||
return Bool(false) | |||
} | |||
retryable, ok := retryMap["retryable"].(bool) | |||
if !ok || !retryable { | |||
return Bool(false) | |||
} | |||
maxAttempts, ok := retryMap["maxAttempts"].(int) | |||
if !ok || maxAttempts < IntValue(retryTimes) { | |||
return Bool(false) | |||
} | |||
return Bool(true) | |||
} | |||
func Merge(args ...interface{}) map[string]*string { | |||
finalArg := make(map[string]*string) | |||
for _, obj := range args { | |||
switch obj.(type) { | |||
case map[string]*string: | |||
arg := obj.(map[string]*string) | |||
for key, value := range arg { | |||
if value != nil { | |||
finalArg[key] = value | |||
} | |||
} | |||
default: | |||
byt, _ := json.Marshal(obj) | |||
arg := make(map[string]string) | |||
err := json.Unmarshal(byt, &arg) | |||
if err != nil { | |||
return finalArg | |||
} | |||
for key, value := range arg { | |||
if value != "" { | |||
finalArg[key] = String(value) | |||
} | |||
} | |||
} | |||
} | |||
return finalArg | |||
} | |||
func isNil(a interface{}) bool { | |||
defer func() { | |||
recover() | |||
}() | |||
vi := reflect.ValueOf(a) | |||
return vi.IsNil() | |||
} | |||
func ToMap(args ...interface{}) map[string]interface{} { | |||
isNotNil := false | |||
finalArg := make(map[string]interface{}) | |||
for _, obj := range args { | |||
if obj == nil { | |||
continue | |||
} | |||
if isNil(obj) { | |||
continue | |||
} | |||
isNotNil = true | |||
switch obj.(type) { | |||
case map[string]*string: | |||
arg := obj.(map[string]*string) | |||
for key, value := range arg { | |||
if value != nil { | |||
finalArg[key] = StringValue(value) | |||
} | |||
} | |||
case map[string]interface{}: | |||
arg := obj.(map[string]interface{}) | |||
for key, value := range arg { | |||
if value != nil { | |||
finalArg[key] = value | |||
} | |||
} | |||
case *string: | |||
str := obj.(*string) | |||
arg := make(map[string]interface{}) | |||
err := json.Unmarshal([]byte(StringValue(str)), &arg) | |||
if err == nil { | |||
for key, value := range arg { | |||
if value != nil { | |||
finalArg[key] = value | |||
} | |||
} | |||
} | |||
tmp := make(map[string]string) | |||
err = json.Unmarshal([]byte(StringValue(str)), &tmp) | |||
if err == nil { | |||
for key, value := range arg { | |||
if value != "" { | |||
finalArg[key] = value | |||
} | |||
} | |||
} | |||
case []byte: | |||
byt := obj.([]byte) | |||
arg := make(map[string]interface{}) | |||
err := json.Unmarshal(byt, &arg) | |||
if err == nil { | |||
for key, value := range arg { | |||
if value != nil { | |||
finalArg[key] = value | |||
} | |||
} | |||
break | |||
} | |||
default: | |||
val := reflect.ValueOf(obj) | |||
res := structToMap(val) | |||
for key, value := range res { | |||
if value != nil { | |||
finalArg[key] = value | |||
} | |||
} | |||
} | |||
} | |||
if !isNotNil { | |||
return nil | |||
} | |||
return finalArg | |||
} | |||
func structToMap(dataValue reflect.Value) map[string]interface{} { | |||
out := make(map[string]interface{}) | |||
if !dataValue.IsValid() { | |||
return out | |||
} | |||
if dataValue.Kind().String() == "ptr" { | |||
if dataValue.IsNil() { | |||
return out | |||
} | |||
dataValue = dataValue.Elem() | |||
} | |||
if !dataValue.IsValid() { | |||
return out | |||
} | |||
dataType := dataValue.Type() | |||
if dataType.Kind().String() != "struct" { | |||
return out | |||
} | |||
for i := 0; i < dataType.NumField(); i++ { | |||
field := dataType.Field(i) | |||
name, containsNameTag := field.Tag.Lookup("json") | |||
if !containsNameTag { | |||
name = field.Name | |||
} else { | |||
strs := strings.Split(name, ",") | |||
name = strs[0] | |||
} | |||
fieldValue := dataValue.FieldByName(field.Name) | |||
if !fieldValue.IsValid() || fieldValue.IsNil() { | |||
continue | |||
} | |||
if field.Type.String() == "io.Reader" || field.Type.String() == "io.Writer" { | |||
continue | |||
} else if field.Type.Kind().String() == "struct" { | |||
out[name] = structToMap(fieldValue) | |||
} else if field.Type.Kind().String() == "ptr" && | |||
field.Type.Elem().Kind().String() == "struct" { | |||
if fieldValue.Elem().IsValid() { | |||
out[name] = structToMap(fieldValue) | |||
} | |||
} else if field.Type.Kind().String() == "ptr" { | |||
if fieldValue.IsValid() && !fieldValue.IsNil() { | |||
out[name] = fieldValue.Elem().Interface() | |||
} | |||
} else if field.Type.Kind().String() == "slice" { | |||
tmp := make([]interface{}, 0) | |||
num := fieldValue.Len() | |||
for i := 0; i < num; i++ { | |||
value := fieldValue.Index(i) | |||
if !value.IsValid() { | |||
continue | |||
} | |||
if value.Type().Kind().String() == "ptr" && | |||
value.Type().Elem().Kind().String() == "struct" { | |||
if value.IsValid() && !value.IsNil() { | |||
tmp = append(tmp, structToMap(value)) | |||
} | |||
} else if value.Type().Kind().String() == "struct" { | |||
tmp = append(tmp, structToMap(value)) | |||
} else if value.Type().Kind().String() == "ptr" { | |||
if value.IsValid() && !value.IsNil() { | |||
tmp = append(tmp, value.Elem().Interface()) | |||
} | |||
} else { | |||
tmp = append(tmp, value.Interface()) | |||
} | |||
} | |||
if len(tmp) > 0 { | |||
out[name] = tmp | |||
} | |||
} else { | |||
out[name] = fieldValue.Interface() | |||
} | |||
} | |||
return out | |||
} | |||
func Retryable(err error) *bool { | |||
if err == nil { | |||
return Bool(false) | |||
} | |||
if realErr, ok := err.(*SDKError); ok { | |||
if realErr.StatusCode == nil { | |||
return Bool(false) | |||
} | |||
code := IntValue(realErr.StatusCode) | |||
return Bool(code >= http.StatusInternalServerError) | |||
} | |||
return Bool(true) | |||
} | |||
func GetBackoffTime(backoff interface{}, retrytimes *int) *int { | |||
backoffMap, ok := backoff.(map[string]interface{}) | |||
if !ok { | |||
return Int(0) | |||
} | |||
policy, ok := backoffMap["policy"].(string) | |||
if !ok || policy == "no" { | |||
return Int(0) | |||
} | |||
period, ok := backoffMap["period"].(int) | |||
if !ok || period == 0 { | |||
return Int(0) | |||
} | |||
maxTime := math.Pow(2.0, float64(IntValue(retrytimes))) | |||
return Int(rand.Intn(int(maxTime-1)) * period) | |||
} | |||
func Sleep(backoffTime *int) { | |||
sleeptime := time.Duration(IntValue(backoffTime)) * time.Second | |||
time.Sleep(sleeptime) | |||
} | |||
func Validate(params interface{}) error { | |||
if params == nil { | |||
return nil | |||
} | |||
requestValue := reflect.ValueOf(params) | |||
if requestValue.IsNil() { | |||
return nil | |||
} | |||
err := validate(requestValue.Elem()) | |||
return err | |||
} | |||
// Verify whether the parameters meet the requirements | |||
func validate(dataValue reflect.Value) error { | |||
if strings.HasPrefix(dataValue.Type().String(), "*") { // Determines whether the input is a structure object or a pointer object | |||
if dataValue.IsNil() { | |||
return nil | |||
} | |||
dataValue = dataValue.Elem() | |||
} | |||
dataType := dataValue.Type() | |||
for i := 0; i < dataType.NumField(); i++ { | |||
field := dataType.Field(i) | |||
valueField := dataValue.Field(i) | |||
for _, value := range validateParams { | |||
err := validateParam(field, valueField, value) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
func validateParam(field reflect.StructField, valueField reflect.Value, tagName string) error { | |||
tag, containsTag := field.Tag.Lookup(tagName) // Take out the checked regular expression | |||
if containsTag && tagName == "require" { | |||
err := checkRequire(field, valueField) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
if strings.HasPrefix(field.Type.String(), "[]") { // Verify the parameters of the array type | |||
err := validateSlice(field, valueField, containsTag, tag, tagName) | |||
if err != nil { | |||
return err | |||
} | |||
} else if valueField.Kind() == reflect.Ptr { // Determines whether it is a pointer object | |||
err := validatePtr(field, valueField, containsTag, tag, tagName) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
func validateSlice(field reflect.StructField, valueField reflect.Value, containsregexpTag bool, tag, tagName string) error { | |||
if valueField.IsValid() && !valueField.IsNil() { // Determines whether the parameter has a value | |||
if containsregexpTag { | |||
if tagName == "maxItems" { | |||
err := checkMaxItems(field, valueField, tag) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
if tagName == "minItems" { | |||
err := checkMinItems(field, valueField, tag) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
for m := 0; m < valueField.Len(); m++ { | |||
elementValue := valueField.Index(m) | |||
if elementValue.Type().Kind() == reflect.Ptr { // Determines whether the child elements of an array are of a basic type | |||
err := validatePtr(field, elementValue, containsregexpTag, tag, tagName) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
func validatePtr(field reflect.StructField, elementValue reflect.Value, containsregexpTag bool, tag, tagName string) error { | |||
if elementValue.IsNil() { | |||
return nil | |||
} | |||
if isFilterType(elementValue.Elem().Type().String(), basicTypes) { | |||
if containsregexpTag { | |||
if tagName == "pattern" { | |||
err := checkPattern(field, elementValue.Elem(), tag) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
if tagName == "maxLength" { | |||
err := checkMaxLength(field, elementValue.Elem(), tag) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
if tagName == "minLength" { | |||
err := checkMinLength(field, elementValue.Elem(), tag) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
if tagName == "maximum" { | |||
err := checkMaximum(field, elementValue.Elem(), tag) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
if tagName == "minimum" { | |||
err := checkMinimum(field, elementValue.Elem(), tag) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
} else { | |||
err := validate(elementValue) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
func checkRequire(field reflect.StructField, valueField reflect.Value) error { | |||
name, _ := field.Tag.Lookup("json") | |||
strs := strings.Split(name, ",") | |||
name = strs[0] | |||
if !valueField.IsNil() && valueField.IsValid() { | |||
return nil | |||
} | |||
return errors.New(name + " should be setted") | |||
} | |||
func checkPattern(field reflect.StructField, valueField reflect.Value, tag string) error { | |||
if valueField.IsValid() && valueField.String() != "" { | |||
value := valueField.String() | |||
r, _ := regexp.Compile("^" + tag + "$") | |||
if match := r.MatchString(value); !match { // Determines whether the parameter value satisfies the regular expression or not, and throws an error | |||
return errors.New(value + " is not matched " + tag) | |||
} | |||
} | |||
return nil | |||
} | |||
func checkMaxItems(field reflect.StructField, valueField reflect.Value, tag string) error { | |||
if valueField.IsValid() && valueField.String() != "" { | |||
maxItems, err := strconv.Atoi(tag) | |||
if err != nil { | |||
return err | |||
} | |||
length := valueField.Len() | |||
if maxItems < length { | |||
errMsg := fmt.Sprintf("The length of %s is %d which is more than %d", field.Name, length, maxItems) | |||
return errors.New(errMsg) | |||
} | |||
} | |||
return nil | |||
} | |||
func checkMinItems(field reflect.StructField, valueField reflect.Value, tag string) error { | |||
if valueField.IsValid() { | |||
minItems, err := strconv.Atoi(tag) | |||
if err != nil { | |||
return err | |||
} | |||
length := valueField.Len() | |||
if minItems > length { | |||
errMsg := fmt.Sprintf("The length of %s is %d which is less than %d", field.Name, length, minItems) | |||
return errors.New(errMsg) | |||
} | |||
} | |||
return nil | |||
} | |||
func checkMaxLength(field reflect.StructField, valueField reflect.Value, tag string) error { | |||
if valueField.IsValid() && valueField.String() != "" { | |||
maxLength, err := strconv.Atoi(tag) | |||
if err != nil { | |||
return err | |||
} | |||
length := valueField.Len() | |||
if valueField.Kind().String() == "string" { | |||
length = strings.Count(valueField.String(), "") - 1 | |||
} | |||
if maxLength < length { | |||
errMsg := fmt.Sprintf("The length of %s is %d which is more than %d", field.Name, length, maxLength) | |||
return errors.New(errMsg) | |||
} | |||
} | |||
return nil | |||
} | |||
func checkMinLength(field reflect.StructField, valueField reflect.Value, tag string) error { | |||
if valueField.IsValid() { | |||
minLength, err := strconv.Atoi(tag) | |||
if err != nil { | |||
return err | |||
} | |||
length := valueField.Len() | |||
if valueField.Kind().String() == "string" { | |||
length = strings.Count(valueField.String(), "") - 1 | |||
} | |||
if minLength > length { | |||
errMsg := fmt.Sprintf("The length of %s is %d which is less than %d", field.Name, length, minLength) | |||
return errors.New(errMsg) | |||
} | |||
} | |||
return nil | |||
} | |||
func checkMaximum(field reflect.StructField, valueField reflect.Value, tag string) error { | |||
if valueField.IsValid() && valueField.String() != "" { | |||
maximum, err := strconv.ParseFloat(tag, 64) | |||
if err != nil { | |||
return err | |||
} | |||
byt, _ := json.Marshal(valueField.Interface()) | |||
num, err := strconv.ParseFloat(string(byt), 64) | |||
if err != nil { | |||
return err | |||
} | |||
if maximum < num { | |||
errMsg := fmt.Sprintf("The size of %s is %f which is greater than %f", field.Name, num, maximum) | |||
return errors.New(errMsg) | |||
} | |||
} | |||
return nil | |||
} | |||
func checkMinimum(field reflect.StructField, valueField reflect.Value, tag string) error { | |||
if valueField.IsValid() && valueField.String() != "" { | |||
minimum, err := strconv.ParseFloat(tag, 64) | |||
if err != nil { | |||
return err | |||
} | |||
byt, _ := json.Marshal(valueField.Interface()) | |||
num, err := strconv.ParseFloat(string(byt), 64) | |||
if err != nil { | |||
return err | |||
} | |||
if minimum > num { | |||
errMsg := fmt.Sprintf("The size of %s is %f which is less than %f", field.Name, num, minimum) | |||
return errors.New(errMsg) | |||
} | |||
} | |||
return nil | |||
} | |||
// Determines whether realType is in filterTypes | |||
func isFilterType(realType string, filterTypes []string) bool { | |||
for _, value := range filterTypes { | |||
if value == realType { | |||
return true | |||
} | |||
} | |||
return false | |||
} | |||
func TransInterfaceToBool(val interface{}) *bool { | |||
if val == nil { | |||
return nil | |||
} | |||
return Bool(val.(bool)) | |||
} | |||
func TransInterfaceToInt(val interface{}) *int { | |||
if val == nil { | |||
return nil | |||
} | |||
return Int(val.(int)) | |||
} | |||
func TransInterfaceToString(val interface{}) *string { | |||
if val == nil { | |||
return nil | |||
} | |||
return String(val.(string)) | |||
} | |||
func Prettify(i interface{}) string { | |||
resp, _ := json.MarshalIndent(i, "", " ") | |||
return string(resp) | |||
} | |||
func ToInt(a *int32) *int { | |||
return Int(int(Int32Value(a))) | |||
} | |||
func ToInt32(a *int) *int32 { | |||
return Int32(int32(IntValue(a))) | |||
} |
@@ -0,0 +1,491 @@ | |||
package tea | |||
func String(a string) *string { | |||
return &a | |||
} | |||
func StringValue(a *string) string { | |||
if a == nil { | |||
return "" | |||
} | |||
return *a | |||
} | |||
func Int(a int) *int { | |||
return &a | |||
} | |||
func IntValue(a *int) int { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Int8(a int8) *int8 { | |||
return &a | |||
} | |||
func Int8Value(a *int8) int8 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Int16(a int16) *int16 { | |||
return &a | |||
} | |||
func Int16Value(a *int16) int16 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Int32(a int32) *int32 { | |||
return &a | |||
} | |||
func Int32Value(a *int32) int32 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Int64(a int64) *int64 { | |||
return &a | |||
} | |||
func Int64Value(a *int64) int64 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Bool(a bool) *bool { | |||
return &a | |||
} | |||
func BoolValue(a *bool) bool { | |||
if a == nil { | |||
return false | |||
} | |||
return *a | |||
} | |||
func Uint(a uint) *uint { | |||
return &a | |||
} | |||
func UintValue(a *uint) uint { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Uint8(a uint8) *uint8 { | |||
return &a | |||
} | |||
func Uint8Value(a *uint8) uint8 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Uint16(a uint16) *uint16 { | |||
return &a | |||
} | |||
func Uint16Value(a *uint16) uint16 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Uint32(a uint32) *uint32 { | |||
return &a | |||
} | |||
func Uint32Value(a *uint32) uint32 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Uint64(a uint64) *uint64 { | |||
return &a | |||
} | |||
func Uint64Value(a *uint64) uint64 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Float32(a float32) *float32 { | |||
return &a | |||
} | |||
func Float32Value(a *float32) float32 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func Float64(a float64) *float64 { | |||
return &a | |||
} | |||
func Float64Value(a *float64) float64 { | |||
if a == nil { | |||
return 0 | |||
} | |||
return *a | |||
} | |||
func IntSlice(a []int) []*int { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*int, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func IntValueSlice(a []*int) []int { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]int, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Int8Slice(a []int8) []*int8 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*int8, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Int8ValueSlice(a []*int8) []int8 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]int8, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Int16Slice(a []int16) []*int16 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*int16, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Int16ValueSlice(a []*int16) []int16 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]int16, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Int32Slice(a []int32) []*int32 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*int32, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Int32ValueSlice(a []*int32) []int32 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]int32, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Int64Slice(a []int64) []*int64 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*int64, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Int64ValueSlice(a []*int64) []int64 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]int64, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func UintSlice(a []uint) []*uint { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*uint, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func UintValueSlice(a []*uint) []uint { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]uint, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Uint8Slice(a []uint8) []*uint8 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*uint8, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Uint8ValueSlice(a []*uint8) []uint8 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]uint8, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Uint16Slice(a []uint16) []*uint16 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*uint16, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Uint16ValueSlice(a []*uint16) []uint16 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]uint16, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Uint32Slice(a []uint32) []*uint32 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*uint32, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Uint32ValueSlice(a []*uint32) []uint32 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]uint32, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Uint64Slice(a []uint64) []*uint64 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*uint64, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Uint64ValueSlice(a []*uint64) []uint64 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]uint64, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Float32Slice(a []float32) []*float32 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*float32, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Float32ValueSlice(a []*float32) []float32 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]float32, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func Float64Slice(a []float64) []*float64 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*float64, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func Float64ValueSlice(a []*float64) []float64 { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]float64, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func StringSlice(a []string) []*string { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*string, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func StringSliceValue(a []*string) []string { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]string, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} | |||
func BoolSlice(a []bool) []*bool { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]*bool, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
res[i] = &a[i] | |||
} | |||
return res | |||
} | |||
func BoolSliceValue(a []*bool) []bool { | |||
if a == nil { | |||
return nil | |||
} | |||
res := make([]bool, len(a)) | |||
for i := 0; i < len(a); i++ { | |||
if a[i] != nil { | |||
res[i] = *a[i] | |||
} | |||
} | |||
return res | |||
} |
@@ -0,0 +1,64 @@ | |||
package utils | |||
import ( | |||
"reflect" | |||
"strings" | |||
"testing" | |||
) | |||
func isNil(object interface{}) bool { | |||
if object == nil { | |||
return true | |||
} | |||
value := reflect.ValueOf(object) | |||
kind := value.Kind() | |||
isNilableKind := containsKind( | |||
[]reflect.Kind{ | |||
reflect.Chan, reflect.Func, | |||
reflect.Interface, reflect.Map, | |||
reflect.Ptr, reflect.Slice}, | |||
kind) | |||
if isNilableKind && value.IsNil() { | |||
return true | |||
} | |||
return false | |||
} | |||
func containsKind(kinds []reflect.Kind, kind reflect.Kind) bool { | |||
for i := 0; i < len(kinds); i++ { | |||
if kind == kinds[i] { | |||
return true | |||
} | |||
} | |||
return false | |||
} | |||
func AssertEqual(t *testing.T, a, b interface{}) { | |||
if !reflect.DeepEqual(a, b) { | |||
t.Errorf("%v != %v", a, b) | |||
} | |||
} | |||
func AssertNil(t *testing.T, object interface{}) { | |||
if !isNil(object) { | |||
t.Errorf("%v is not nil", object) | |||
} | |||
} | |||
func AssertNotNil(t *testing.T, object interface{}) { | |||
if isNil(object) { | |||
t.Errorf("%v is nil", object) | |||
} | |||
} | |||
func AssertContains(t *testing.T, contains string, msgAndArgs ...string) { | |||
for _, value := range msgAndArgs { | |||
if ok := strings.Contains(contains, value); !ok { | |||
t.Errorf("%s does not contain %s", contains, value) | |||
} | |||
} | |||
} |
@@ -0,0 +1,109 @@ | |||
package utils | |||
import ( | |||
"io" | |||
"log" | |||
"strings" | |||
"time" | |||
) | |||
type Logger struct { | |||
*log.Logger | |||
formatTemplate string | |||
isOpen bool | |||
lastLogMsg string | |||
} | |||
var defaultLoggerTemplate = `{time} {channel}: "{method} {uri} HTTP/{version}" {code} {cost} {hostname}` | |||
var loggerParam = []string{"{time}", "{start_time}", "{ts}", "{channel}", "{pid}", "{host}", "{method}", "{uri}", "{version}", "{target}", "{hostname}", "{code}", "{error}", "{req_headers}", "{res_body}", "{res_headers}", "{cost}"} | |||
var logChannel string | |||
func InitLogMsg(fieldMap map[string]string) { | |||
for _, value := range loggerParam { | |||
fieldMap[value] = "" | |||
} | |||
} | |||
func (logger *Logger) SetFormatTemplate(template string) { | |||
logger.formatTemplate = template | |||
} | |||
func (logger *Logger) GetFormatTemplate() string { | |||
return logger.formatTemplate | |||
} | |||
func NewLogger(level string, channel string, out io.Writer, template string) *Logger { | |||
if level == "" { | |||
level = "info" | |||
} | |||
logChannel = "AlibabaCloud" | |||
if channel != "" { | |||
logChannel = channel | |||
} | |||
log := log.New(out, "["+strings.ToUpper(level)+"]", log.Lshortfile) | |||
if template == "" { | |||
template = defaultLoggerTemplate | |||
} | |||
return &Logger{ | |||
Logger: log, | |||
formatTemplate: template, | |||
isOpen: true, | |||
} | |||
} | |||
func (logger *Logger) OpenLogger() { | |||
logger.isOpen = true | |||
} | |||
func (logger *Logger) CloseLogger() { | |||
logger.isOpen = false | |||
} | |||
func (logger *Logger) SetIsopen(isopen bool) { | |||
logger.isOpen = isopen | |||
} | |||
func (logger *Logger) GetIsopen() bool { | |||
return logger.isOpen | |||
} | |||
func (logger *Logger) SetLastLogMsg(lastLogMsg string) { | |||
logger.lastLogMsg = lastLogMsg | |||
} | |||
func (logger *Logger) GetLastLogMsg() string { | |||
return logger.lastLogMsg | |||
} | |||
func SetLogChannel(channel string) { | |||
logChannel = channel | |||
} | |||
func (logger *Logger) PrintLog(fieldMap map[string]string, err error) { | |||
if err != nil { | |||
fieldMap["{error}"] = err.Error() | |||
} | |||
fieldMap["{time}"] = time.Now().Format("2006-01-02 15:04:05") | |||
fieldMap["{ts}"] = getTimeInFormatISO8601() | |||
fieldMap["{channel}"] = logChannel | |||
if logger != nil { | |||
logMsg := logger.formatTemplate | |||
for key, value := range fieldMap { | |||
logMsg = strings.Replace(logMsg, key, value, -1) | |||
} | |||
logger.lastLogMsg = logMsg | |||
if logger.isOpen == true { | |||
logger.Output(2, logMsg) | |||
} | |||
} | |||
} | |||
func getTimeInFormatISO8601() (timeStr string) { | |||
gmt := time.FixedZone("GMT", 0) | |||
return time.Now().In(gmt).Format("2006-01-02T15:04:05Z") | |||
} |
@@ -0,0 +1,60 @@ | |||
package utils | |||
// ProgressEventType defines transfer progress event type | |||
type ProgressEventType int | |||
const ( | |||
// TransferStartedEvent transfer started, set TotalBytes | |||
TransferStartedEvent ProgressEventType = 1 + iota | |||
// TransferDataEvent transfer data, set ConsumedBytes anmd TotalBytes | |||
TransferDataEvent | |||
// TransferCompletedEvent transfer completed | |||
TransferCompletedEvent | |||
// TransferFailedEvent transfer encounters an error | |||
TransferFailedEvent | |||
) | |||
// ProgressEvent defines progress event | |||
type ProgressEvent struct { | |||
ConsumedBytes int64 | |||
TotalBytes int64 | |||
RwBytes int64 | |||
EventType ProgressEventType | |||
} | |||
// ProgressListener listens progress change | |||
type ProgressListener interface { | |||
ProgressChanged(event *ProgressEvent) | |||
} | |||
// -------------------- Private -------------------- | |||
func NewProgressEvent(eventType ProgressEventType, consumed, total int64, rwBytes int64) *ProgressEvent { | |||
return &ProgressEvent{ | |||
ConsumedBytes: consumed, | |||
TotalBytes: total, | |||
RwBytes: rwBytes, | |||
EventType: eventType} | |||
} | |||
// publishProgress | |||
func PublishProgress(listener ProgressListener, event *ProgressEvent) { | |||
if listener != nil && event != nil { | |||
listener.ProgressChanged(event) | |||
} | |||
} | |||
func GetProgressListener(obj interface{}) ProgressListener { | |||
if obj == nil { | |||
return nil | |||
} | |||
listener, ok := obj.(ProgressListener) | |||
if !ok { | |||
return nil | |||
} | |||
return listener | |||
} | |||
type ReaderTracker struct { | |||
CompletedBytes int64 | |||
} |
@@ -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 (c) 2009-present, Alibaba Cloud All rights reserved. | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. |
@@ -0,0 +1,41 @@ | |||
package credentials | |||
import "github.com/alibabacloud-go/tea/tea" | |||
// AccessKeyCredential is a kind of credential | |||
type AccessKeyCredential struct { | |||
AccessKeyId string | |||
AccessKeySecret string | |||
} | |||
func newAccessKeyCredential(accessKeyId, accessKeySecret string) *AccessKeyCredential { | |||
return &AccessKeyCredential{ | |||
AccessKeyId: accessKeyId, | |||
AccessKeySecret: accessKeySecret, | |||
} | |||
} | |||
// GetAccessKeyId reutrns AccessKeyCreential's AccessKeyId | |||
func (a *AccessKeyCredential) GetAccessKeyId() (*string, error) { | |||
return tea.String(a.AccessKeyId), nil | |||
} | |||
// GetAccessSecret reutrns AccessKeyCreential's AccessKeySecret | |||
func (a *AccessKeyCredential) GetAccessKeySecret() (*string, error) { | |||
return tea.String(a.AccessKeySecret), nil | |||
} | |||
// GetSecurityToken is useless for AccessKeyCreential | |||
func (a *AccessKeyCredential) GetSecurityToken() (*string, error) { | |||
return tea.String(""), nil | |||
} | |||
// GetBearerToken is useless for AccessKeyCreential | |||
func (a *AccessKeyCredential) GetBearerToken() *string { | |||
return tea.String("") | |||
} | |||
// GetType reutrns AccessKeyCreential's type | |||
func (a *AccessKeyCredential) GetType() *string { | |||
return tea.String("access_key") | |||
} |
@@ -0,0 +1,40 @@ | |||
package credentials | |||
import "github.com/alibabacloud-go/tea/tea" | |||
// BearerTokenCredential is a kind of credential | |||
type BearerTokenCredential struct { | |||
BearerToken string | |||
} | |||
// newBearerTokenCredential return a BearerTokenCredential object | |||
func newBearerTokenCredential(token string) *BearerTokenCredential { | |||
return &BearerTokenCredential{ | |||
BearerToken: token, | |||
} | |||
} | |||
// GetAccessKeyId is useless for BearerTokenCredential | |||
func (b *BearerTokenCredential) GetAccessKeyId() (*string, error) { | |||
return tea.String(""), nil | |||
} | |||
// GetAccessSecret is useless for BearerTokenCredential | |||
func (b *BearerTokenCredential) GetAccessKeySecret() (*string, error) { | |||
return tea.String(("")), nil | |||
} | |||
// GetSecurityToken is useless for BearerTokenCredential | |||
func (b *BearerTokenCredential) GetSecurityToken() (*string, error) { | |||
return tea.String(""), nil | |||
} | |||
// GetBearerToken reutrns BearerTokenCredential's BearerToken | |||
func (b *BearerTokenCredential) GetBearerToken() *string { | |||
return tea.String(b.BearerToken) | |||
} | |||
// GetType reutrns BearerTokenCredential's type | |||
func (b *BearerTokenCredential) GetType() *string { | |||
return tea.String("bearer") | |||
} |
@@ -0,0 +1,349 @@ | |||
package credentials | |||
import ( | |||
"bufio" | |||
"errors" | |||
"fmt" | |||
"net/http" | |||
"net/url" | |||
"os" | |||
"strings" | |||
"time" | |||
"github.com/alibabacloud-go/debug/debug" | |||
"github.com/alibabacloud-go/tea/tea" | |||
"github.com/aliyun/credentials-go/credentials/request" | |||
"github.com/aliyun/credentials-go/credentials/response" | |||
"github.com/aliyun/credentials-go/credentials/utils" | |||
) | |||
var debuglog = debug.Init("credential") | |||
var hookParse = func(err error) error { | |||
return err | |||
} | |||
// Credential is an interface for getting actual credential | |||
type Credential interface { | |||
GetAccessKeyId() (*string, error) | |||
GetAccessKeySecret() (*string, error) | |||
GetSecurityToken() (*string, error) | |||
GetBearerToken() *string | |||
GetType() *string | |||
} | |||
// Config is important when call NewCredential | |||
type Config struct { | |||
Type *string `json:"type"` | |||
AccessKeyId *string `json:"access_key_id"` | |||
AccessKeySecret *string `json:"access_key_secret"` | |||
RoleArn *string `json:"role_arn"` | |||
RoleSessionName *string `json:"role_session_name"` | |||
PublicKeyId *string `json:"public_key_id"` | |||
RoleName *string `json:"role_name"` | |||
SessionExpiration *int `json:"session_expiration"` | |||
PrivateKeyFile *string `json:"private_key_file"` | |||
BearerToken *string `json:"bearer_token"` | |||
SecurityToken *string `json:"security_token"` | |||
RoleSessionExpiration *int `json:"role_session_expiratioon"` | |||
Policy *string `json:"policy"` | |||
Host *string `json:"host"` | |||
Timeout *int `json:"timeout"` | |||
ConnectTimeout *int `json:"connect_timeout"` | |||
Proxy *string `json:"proxy"` | |||
} | |||
func (s Config) String() string { | |||
return tea.Prettify(s) | |||
} | |||
func (s Config) GoString() string { | |||
return s.String() | |||
} | |||
func (s *Config) SetAccessKeyId(v string) *Config { | |||
s.AccessKeyId = &v | |||
return s | |||
} | |||
func (s *Config) SetAccessKeySecret(v string) *Config { | |||
s.AccessKeySecret = &v | |||
return s | |||
} | |||
func (s *Config) SetSecurityToken(v string) *Config { | |||
s.SecurityToken = &v | |||
return s | |||
} | |||
func (s *Config) SetRoleArn(v string) *Config { | |||
s.RoleArn = &v | |||
return s | |||
} | |||
func (s *Config) SetRoleSessionName(v string) *Config { | |||
s.RoleSessionName = &v | |||
return s | |||
} | |||
func (s *Config) SetPublicKeyId(v string) *Config { | |||
s.PublicKeyId = &v | |||
return s | |||
} | |||
func (s *Config) SetRoleName(v string) *Config { | |||
s.RoleName = &v | |||
return s | |||
} | |||
func (s *Config) SetSessionExpiration(v int) *Config { | |||
s.SessionExpiration = &v | |||
return s | |||
} | |||
func (s *Config) SetPrivateKeyFile(v string) *Config { | |||
s.PrivateKeyFile = &v | |||
return s | |||
} | |||
func (s *Config) SetBearerToken(v string) *Config { | |||
s.BearerToken = &v | |||
return s | |||
} | |||
func (s *Config) SetRoleSessionExpiration(v int) *Config { | |||
s.RoleSessionExpiration = &v | |||
return s | |||
} | |||
func (s *Config) SetPolicy(v string) *Config { | |||
s.Policy = &v | |||
return s | |||
} | |||
func (s *Config) SetHost(v string) *Config { | |||
s.Host = &v | |||
return s | |||
} | |||
func (s *Config) SetTimeout(v int) *Config { | |||
s.Timeout = &v | |||
return s | |||
} | |||
func (s *Config) SetConnectTimeout(v int) *Config { | |||
s.ConnectTimeout = &v | |||
return s | |||
} | |||
func (s *Config) SetProxy(v string) *Config { | |||
s.Proxy = &v | |||
return s | |||
} | |||
func (s *Config) SetType(v string) *Config { | |||
s.Type = &v | |||
return s | |||
} | |||
// NewCredential return a credential according to the type in config. | |||
// if config is nil, the function will use default provider chain to get credential. | |||
// please see README.md for detail. | |||
func NewCredential(config *Config) (credential Credential, err error) { | |||
if config == nil { | |||
config, err = defaultChain.resolve() | |||
if err != nil { | |||
return | |||
} | |||
return NewCredential(config) | |||
} | |||
switch tea.StringValue(config.Type) { | |||
case "access_key": | |||
err = checkAccessKey(config) | |||
if err != nil { | |||
return | |||
} | |||
credential = newAccessKeyCredential(tea.StringValue(config.AccessKeyId), tea.StringValue(config.AccessKeySecret)) | |||
case "sts": | |||
err = checkSTS(config) | |||
if err != nil { | |||
return | |||
} | |||
credential = newStsTokenCredential(tea.StringValue(config.AccessKeyId), tea.StringValue(config.AccessKeySecret), tea.StringValue(config.SecurityToken)) | |||
case "ecs_ram_role": | |||
checkEcsRAMRole(config) | |||
runtime := &utils.Runtime{ | |||
Host: tea.StringValue(config.Host), | |||
Proxy: tea.StringValue(config.Proxy), | |||
ReadTimeout: tea.IntValue(config.Timeout), | |||
ConnectTimeout: tea.IntValue(config.ConnectTimeout), | |||
} | |||
credential = newEcsRAMRoleCredential(tea.StringValue(config.RoleName), runtime) | |||
case "ram_role_arn": | |||
err = checkRAMRoleArn(config) | |||
if err != nil { | |||
return | |||
} | |||
runtime := &utils.Runtime{ | |||
Host: tea.StringValue(config.Host), | |||
Proxy: tea.StringValue(config.Proxy), | |||
ReadTimeout: tea.IntValue(config.Timeout), | |||
ConnectTimeout: tea.IntValue(config.ConnectTimeout), | |||
} | |||
credential = newRAMRoleArnCredential(tea.StringValue(config.AccessKeyId), tea.StringValue(config.AccessKeySecret), tea.StringValue(config.RoleArn), tea.StringValue(config.RoleSessionName), tea.StringValue(config.Policy), tea.IntValue(config.RoleSessionExpiration), runtime) | |||
case "rsa_key_pair": | |||
err = checkRSAKeyPair(config) | |||
if err != nil { | |||
return | |||
} | |||
file, err1 := os.Open(tea.StringValue(config.PrivateKeyFile)) | |||
if err1 != nil { | |||
err = fmt.Errorf("InvalidPath: Can not open PrivateKeyFile, err is %s", err1.Error()) | |||
return | |||
} | |||
defer file.Close() | |||
var privateKey string | |||
scan := bufio.NewScanner(file) | |||
for scan.Scan() { | |||
if strings.HasPrefix(scan.Text(), "----") { | |||
continue | |||
} | |||
privateKey += scan.Text() + "\n" | |||
} | |||
runtime := &utils.Runtime{ | |||
Host: tea.StringValue(config.Host), | |||
Proxy: tea.StringValue(config.Proxy), | |||
ReadTimeout: tea.IntValue(config.Timeout), | |||
ConnectTimeout: tea.IntValue(config.ConnectTimeout), | |||
} | |||
credential = newRsaKeyPairCredential(privateKey, tea.StringValue(config.PublicKeyId), tea.IntValue(config.SessionExpiration), runtime) | |||
case "bearer": | |||
if tea.StringValue(config.BearerToken) == "" { | |||
err = errors.New("BearerToken cannot be empty") | |||
return | |||
} | |||
credential = newBearerTokenCredential(tea.StringValue(config.BearerToken)) | |||
default: | |||
err = errors.New("Invalid type option, support: access_key, sts, ecs_ram_role, ram_role_arn, rsa_key_pair") | |||
return | |||
} | |||
return credential, nil | |||
} | |||
func checkRSAKeyPair(config *Config) (err error) { | |||
if tea.StringValue(config.PrivateKeyFile) == "" { | |||
err = errors.New("PrivateKeyFile cannot be empty") | |||
return | |||
} | |||
if tea.StringValue(config.PublicKeyId) == "" { | |||
err = errors.New("PublicKeyId cannot be empty") | |||
return | |||
} | |||
return | |||
} | |||
func checkRAMRoleArn(config *Config) (err error) { | |||
if tea.StringValue(config.AccessKeySecret) == "" { | |||
err = errors.New("AccessKeySecret cannot be empty") | |||
return | |||
} | |||
if tea.StringValue(config.RoleArn) == "" { | |||
err = errors.New("RoleArn cannot be empty") | |||
return | |||
} | |||
if tea.StringValue(config.RoleSessionName) == "" { | |||
err = errors.New("RoleSessionName cannot be empty") | |||
return | |||
} | |||
if tea.StringValue(config.AccessKeyId) == "" { | |||
err = errors.New("AccessKeyId cannot be empty") | |||
return | |||
} | |||
return | |||
} | |||
func checkEcsRAMRole(config *Config) (err error) { | |||
return | |||
} | |||
func checkSTS(config *Config) (err error) { | |||
if tea.StringValue(config.AccessKeyId) == "" { | |||
err = errors.New("AccessKeyId cannot be empty") | |||
return | |||
} | |||
if tea.StringValue(config.AccessKeySecret) == "" { | |||
err = errors.New("AccessKeySecret cannot be empty") | |||
return | |||
} | |||
if tea.StringValue(config.SecurityToken) == "" { | |||
err = errors.New("SecurityToken cannot be empty") | |||
return | |||
} | |||
return | |||
} | |||
func checkAccessKey(config *Config) (err error) { | |||
if tea.StringValue(config.AccessKeyId) == "" { | |||
err = errors.New("AccessKeyId cannot be empty") | |||
return | |||
} | |||
if tea.StringValue(config.AccessKeySecret) == "" { | |||
err = errors.New("AccessKeySecret cannot be empty") | |||
return | |||
} | |||
return | |||
} | |||
func doAction(request *request.CommonRequest, runtime *utils.Runtime) (content []byte, err error) { | |||
httpRequest, err := http.NewRequest(request.Method, request.URL, strings.NewReader("")) | |||
if err != nil { | |||
return | |||
} | |||
httpRequest.Proto = "HTTP/1.1" | |||
httpRequest.Host = request.Domain | |||
debuglog("> %s %s %s", httpRequest.Method, httpRequest.URL.RequestURI(), httpRequest.Proto) | |||
debuglog("> Host: %s", httpRequest.Host) | |||
for key, value := range request.Headers { | |||
if value != "" { | |||
debuglog("> %s: %s", key, value) | |||
httpRequest.Header[key] = []string{value} | |||
} | |||
} | |||
debuglog(">") | |||
httpClient := &http.Client{} | |||
httpClient.Timeout = time.Duration(runtime.ReadTimeout) * time.Second | |||
proxy := &url.URL{} | |||
if runtime.Proxy != "" { | |||
proxy, err = url.Parse(runtime.Proxy) | |||
if err != nil { | |||
return | |||
} | |||
} | |||
trans := &http.Transport{} | |||
if proxy != nil && runtime.Proxy != "" { | |||
trans.Proxy = http.ProxyURL(proxy) | |||
} | |||
trans.DialContext = utils.Timeout(time.Duration(runtime.ConnectTimeout) * time.Second) | |||
httpClient.Transport = trans | |||
httpResponse, err := hookDo(httpClient.Do)(httpRequest) | |||
if err != nil { | |||
return | |||
} | |||
debuglog("< %s %s", httpResponse.Proto, httpResponse.Status) | |||
for key, value := range httpResponse.Header { | |||
debuglog("< %s: %v", key, strings.Join(value, "")) | |||
} | |||
debuglog("<") | |||
resp := &response.CommonResponse{} | |||
err = hookParse(resp.ParseFromHTTPResponse(httpResponse)) | |||
if err != nil { | |||
return | |||
} | |||
debuglog("%s", resp.GetHTTPContentString()) | |||
if resp.GetHTTPStatus() != http.StatusOK { | |||
err = fmt.Errorf("httpStatus: %d, message = %s", resp.GetHTTPStatus(), resp.GetHTTPContentString()) | |||
return | |||
} | |||
return resp.GetHTTPContentBytes(), nil | |||
} |
@@ -0,0 +1,25 @@ | |||
package credentials | |||
import ( | |||
"net/http" | |||
"time" | |||
) | |||
const defaultInAdvanceScale = 0.95 | |||
var hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) { | |||
return fn | |||
} | |||
type credentialUpdater struct { | |||
credentialExpiration int | |||
lastUpdateTimestamp int64 | |||
inAdvanceScale float64 | |||
} | |||
func (updater *credentialUpdater) needUpdateCredential() (result bool) { | |||
if updater.inAdvanceScale == 0 { | |||
updater.inAdvanceScale = defaultInAdvanceScale | |||
} | |||
return time.Now().Unix()-updater.lastUpdateTimestamp >= int64(float64(updater.credentialExpiration)*updater.inAdvanceScale) | |||
} |
@@ -0,0 +1,136 @@ | |||
package credentials | |||
import ( | |||
"encoding/json" | |||
"fmt" | |||
"time" | |||
"github.com/alibabacloud-go/tea/tea" | |||
"github.com/aliyun/credentials-go/credentials/request" | |||
"github.com/aliyun/credentials-go/credentials/utils" | |||
) | |||
var securityCredURL = "http://100.100.100.200/latest/meta-data/ram/security-credentials/" | |||
// EcsRAMRoleCredential is a kind of credential | |||
type EcsRAMRoleCredential struct { | |||
*credentialUpdater | |||
RoleName string | |||
sessionCredential *sessionCredential | |||
runtime *utils.Runtime | |||
} | |||
type ecsRAMRoleResponse struct { | |||
Code string `json:"Code" xml:"Code"` | |||
AccessKeyId string `json:"AccessKeyId" xml:"AccessKeyId"` | |||
AccessKeySecret string `json:"AccessKeySecret" xml:"AccessKeySecret"` | |||
SecurityToken string `json:"SecurityToken" xml:"SecurityToken"` | |||
Expiration string `json:"Expiration" xml:"Expiration"` | |||
} | |||
func newEcsRAMRoleCredential(roleName string, runtime *utils.Runtime) *EcsRAMRoleCredential { | |||
return &EcsRAMRoleCredential{ | |||
RoleName: roleName, | |||
credentialUpdater: new(credentialUpdater), | |||
runtime: runtime, | |||
} | |||
} | |||
// GetAccessKeyId reutrns EcsRAMRoleCredential's AccessKeyId | |||
// if AccessKeyId is not exist or out of date, the function will update it. | |||
func (e *EcsRAMRoleCredential) GetAccessKeyId() (*string, error) { | |||
if e.sessionCredential == nil || e.needUpdateCredential() { | |||
err := e.updateCredential() | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
} | |||
return tea.String(e.sessionCredential.AccessKeyId), nil | |||
} | |||
// GetAccessSecret reutrns EcsRAMRoleCredential's AccessKeySecret | |||
// if AccessKeySecret is not exist or out of date, the function will update it. | |||
func (e *EcsRAMRoleCredential) GetAccessKeySecret() (*string, error) { | |||
if e.sessionCredential == nil || e.needUpdateCredential() { | |||
err := e.updateCredential() | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
} | |||
return tea.String(e.sessionCredential.AccessKeySecret), nil | |||
} | |||
// GetSecurityToken reutrns EcsRAMRoleCredential's SecurityToken | |||
// if SecurityToken is not exist or out of date, the function will update it. | |||
func (e *EcsRAMRoleCredential) GetSecurityToken() (*string, error) { | |||
if e.sessionCredential == nil || e.needUpdateCredential() { | |||
err := e.updateCredential() | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
} | |||
return tea.String(e.sessionCredential.SecurityToken), nil | |||
} | |||
// GetBearerToken is useless for EcsRAMRoleCredential | |||
func (e *EcsRAMRoleCredential) GetBearerToken() *string { | |||
return tea.String("") | |||
} | |||
// GetType reutrns EcsRAMRoleCredential's type | |||
func (e *EcsRAMRoleCredential) GetType() *string { | |||
return tea.String("ecs_ram_role") | |||
} | |||
func getRoleName() (string, error) { | |||
runtime := utils.NewRuntime(1, 1, "", "") | |||
request := request.NewCommonRequest() | |||
request.URL = securityCredURL | |||
request.Method = "GET" | |||
content, err := doAction(request, runtime) | |||
if err != nil { | |||
return "", err | |||
} | |||
return string(content), nil | |||
} | |||
func (e *EcsRAMRoleCredential) updateCredential() (err error) { | |||
if e.runtime == nil { | |||
e.runtime = new(utils.Runtime) | |||
} | |||
request := request.NewCommonRequest() | |||
if e.RoleName == "" { | |||
e.RoleName, err = getRoleName() | |||
if err != nil { | |||
return fmt.Errorf("refresh Ecs sts token err: %s", err.Error()) | |||
} | |||
} | |||
request.URL = securityCredURL + e.RoleName | |||
request.Method = "GET" | |||
content, err := doAction(request, e.runtime) | |||
if err != nil { | |||
return fmt.Errorf("refresh Ecs sts token err: %s", err.Error()) | |||
} | |||
var resp *ecsRAMRoleResponse | |||
err = json.Unmarshal(content, &resp) | |||
if err != nil { | |||
return fmt.Errorf("refresh Ecs sts token err: Json Unmarshal fail: %s", err.Error()) | |||
} | |||
if resp.Code != "Success" { | |||
return fmt.Errorf("refresh Ecs sts token err: Code is not Success") | |||
} | |||
if resp.AccessKeyId == "" || resp.AccessKeySecret == "" || resp.SecurityToken == "" || resp.Expiration == "" { | |||
return fmt.Errorf("refresh Ecs sts token err: AccessKeyId: %s, AccessKeySecret: %s, SecurityToken: %s, Expiration: %s", resp.AccessKeyId, resp.AccessKeySecret, resp.SecurityToken, resp.Expiration) | |||
} | |||
expirationTime, err := time.Parse("2006-01-02T15:04:05Z", resp.Expiration) | |||
e.lastUpdateTimestamp = time.Now().Unix() | |||
e.credentialExpiration = int(expirationTime.Unix() - time.Now().Unix()) | |||
e.sessionCredential = &sessionCredential{ | |||
AccessKeyId: resp.AccessKeyId, | |||
AccessKeySecret: resp.AccessKeySecret, | |||
SecurityToken: resp.SecurityToken, | |||
} | |||
return | |||
} |
@@ -0,0 +1,43 @@ | |||
package credentials | |||
import ( | |||
"errors" | |||
"os" | |||
"github.com/alibabacloud-go/tea/tea" | |||
) | |||
type envProvider struct{} | |||
var providerEnv = new(envProvider) | |||
const ( | |||
// EnvVarAccessKeyId is a name of ALIBABA_CLOUD_ACCESS_KEY_Id | |||
EnvVarAccessKeyId = "ALIBABA_CLOUD_ACCESS_KEY_Id" | |||
// EnvVarAccessKeySecret is a name of ALIBABA_CLOUD_ACCESS_KEY_SECRET | |||
EnvVarAccessKeySecret = "ALIBABA_CLOUD_ACCESS_KEY_SECRET" | |||
) | |||
func newEnvProvider() Provider { | |||
return &envProvider{} | |||
} | |||
func (p *envProvider) resolve() (*Config, error) { | |||
accessKeyId, ok1 := os.LookupEnv(EnvVarAccessKeyId) | |||
accessKeySecret, ok2 := os.LookupEnv(EnvVarAccessKeySecret) | |||
if !ok1 || !ok2 { | |||
return nil, nil | |||
} | |||
if accessKeyId == "" { | |||
return nil, errors.New(EnvVarAccessKeyId + " cannot be empty") | |||
} | |||
if accessKeySecret == "" { | |||
return nil, errors.New(EnvVarAccessKeySecret + " cannot be empty") | |||
} | |||
config := &Config{ | |||
Type: tea.String("access_key"), | |||
AccessKeyId: tea.String(accessKeyId), | |||
AccessKeySecret: tea.String(accessKeySecret), | |||
} | |||
return config, nil | |||
} |
@@ -0,0 +1,28 @@ | |||
package credentials | |||
import ( | |||
"os" | |||
"github.com/alibabacloud-go/tea/tea" | |||
) | |||
type instanceCredentialsProvider struct{} | |||
var providerInstance = new(instanceCredentialsProvider) | |||
func newInstanceCredentialsProvider() Provider { | |||
return &instanceCredentialsProvider{} | |||
} | |||
func (p *instanceCredentialsProvider) resolve() (*Config, error) { | |||
roleName, ok := os.LookupEnv(ENVEcsMetadata) | |||
if !ok { | |||
return nil, nil | |||
} | |||
config := &Config{ | |||
Type: tea.String("ecs_ram_role"), | |||
RoleName: tea.String(roleName), | |||
} | |||
return config, nil | |||
} |
@@ -0,0 +1,350 @@ | |||
package credentials | |||
import ( | |||
"errors" | |||
"fmt" | |||
"os" | |||
"runtime" | |||
"strings" | |||
"github.com/alibabacloud-go/tea/tea" | |||
ini "gopkg.in/ini.v1" | |||
) | |||
type profileProvider struct { | |||
Profile string | |||
} | |||
var providerProfile = newProfileProvider() | |||
var hookOS = func(goos string) string { | |||
return goos | |||
} | |||
var hookState = func(info os.FileInfo, err error) (os.FileInfo, error) { | |||
return info, err | |||
} | |||
// NewProfileProvider receive zero or more parameters, | |||
// when length of name is 0, the value of field Profile will be "default", | |||
// and when there are multiple inputs, the function will take the | |||
// first one and discard the other values. | |||
func newProfileProvider(name ...string) Provider { | |||
p := new(profileProvider) | |||
if len(name) == 0 { | |||
p.Profile = "default" | |||
} else { | |||
p.Profile = name[0] | |||
} | |||
return p | |||
} | |||
// resolve implements the Provider interface | |||
// when credential type is rsa_key_pair, the content of private_key file | |||
// must be able to be parsed directly into the required string | |||
// that NewRsaKeyPairCredential function needed | |||
func (p *profileProvider) resolve() (*Config, error) { | |||
path, ok := os.LookupEnv(ENVCredentialFile) | |||
if !ok { | |||
path, err := checkDefaultPath() | |||
if err != nil { | |||
return nil, err | |||
} | |||
if path == "" { | |||
return nil, nil | |||
} | |||
} else if path == "" { | |||
return nil, errors.New(ENVCredentialFile + " cannot be empty") | |||
} | |||
value, section, err := getType(path, p.Profile) | |||
if err != nil { | |||
return nil, err | |||
} | |||
switch value.String() { | |||
case "access_key": | |||
config, err := getAccessKey(section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
case "sts": | |||
config, err := getSTS(section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
case "bearer": | |||
config, err := getBearerToken(section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
case "ecs_ram_role": | |||
config, err := getEcsRAMRole(section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
case "ram_role_arn": | |||
config, err := getRAMRoleArn(section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
case "rsa_key_pair": | |||
config, err := getRSAKeyPair(section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
default: | |||
return nil, errors.New("Invalid type option, support: access_key, sts, ecs_ram_role, ram_role_arn, rsa_key_pair") | |||
} | |||
} | |||
func getRSAKeyPair(section *ini.Section) (*Config, error) { | |||
publicKeyId, err := section.GetKey("public_key_id") | |||
if err != nil { | |||
return nil, errors.New("Missing required public_key_id option in profile for rsa_key_pair") | |||
} | |||
if publicKeyId.String() == "" { | |||
return nil, errors.New("public_key_id cannot be empty") | |||
} | |||
privateKeyFile, err := section.GetKey("private_key_file") | |||
if err != nil { | |||
return nil, errors.New("Missing required private_key_file option in profile for rsa_key_pair") | |||
} | |||
if privateKeyFile.String() == "" { | |||
return nil, errors.New("private_key_file cannot be empty") | |||
} | |||
sessionExpiration, _ := section.GetKey("session_expiration") | |||
expiration := 0 | |||
if sessionExpiration != nil { | |||
expiration, err = sessionExpiration.Int() | |||
if err != nil { | |||
return nil, errors.New("session_expiration must be an int") | |||
} | |||
} | |||
config := &Config{ | |||
Type: tea.String("rsa_key_pair"), | |||
PublicKeyId: tea.String(publicKeyId.String()), | |||
PrivateKeyFile: tea.String(privateKeyFile.String()), | |||
SessionExpiration: tea.Int(expiration), | |||
} | |||
err = setRuntimeToConfig(config, section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
} | |||
func getRAMRoleArn(section *ini.Section) (*Config, error) { | |||
accessKeyId, err := section.GetKey("access_key_id") | |||
if err != nil { | |||
return nil, errors.New("Missing required access_key_id option in profile for ram_role_arn") | |||
} | |||
if accessKeyId.String() == "" { | |||
return nil, errors.New("access_key_id cannot be empty") | |||
} | |||
accessKeySecret, err := section.GetKey("access_key_secret") | |||
if err != nil { | |||
return nil, errors.New("Missing required access_key_secret option in profile for ram_role_arn") | |||
} | |||
if accessKeySecret.String() == "" { | |||
return nil, errors.New("access_key_secret cannot be empty") | |||
} | |||
roleArn, err := section.GetKey("role_arn") | |||
if err != nil { | |||
return nil, errors.New("Missing required role_arn option in profile for ram_role_arn") | |||
} | |||
if roleArn.String() == "" { | |||
return nil, errors.New("role_arn cannot be empty") | |||
} | |||
roleSessionName, err := section.GetKey("role_session_name") | |||
if err != nil { | |||
return nil, errors.New("Missing required role_session_name option in profile for ram_role_arn") | |||
} | |||
if roleSessionName.String() == "" { | |||
return nil, errors.New("role_session_name cannot be empty") | |||
} | |||
roleSessionExpiration, _ := section.GetKey("role_session_expiration") | |||
expiration := 0 | |||
if roleSessionExpiration != nil { | |||
expiration, err = roleSessionExpiration.Int() | |||
if err != nil { | |||
return nil, errors.New("role_session_expiration must be an int") | |||
} | |||
} | |||
config := &Config{ | |||
Type: tea.String("ram_role_arn"), | |||
AccessKeyId: tea.String(accessKeyId.String()), | |||
AccessKeySecret: tea.String(accessKeySecret.String()), | |||
RoleArn: tea.String(roleArn.String()), | |||
RoleSessionName: tea.String(roleSessionName.String()), | |||
RoleSessionExpiration: tea.Int(expiration), | |||
} | |||
err = setRuntimeToConfig(config, section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
} | |||
func getEcsRAMRole(section *ini.Section) (*Config, error) { | |||
roleName, _ := section.GetKey("role_name") | |||
config := &Config{ | |||
Type: tea.String("ecs_ram_role"), | |||
} | |||
if roleName != nil { | |||
config.RoleName = tea.String(roleName.String()) | |||
} | |||
err := setRuntimeToConfig(config, section) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return config, nil | |||
} | |||
func getBearerToken(section *ini.Section) (*Config, error) { | |||
bearerToken, err := section.GetKey("bearer_token") | |||
if err != nil { | |||
return nil, errors.New("Missing required bearer_token option in profile for bearer") | |||
} | |||
if bearerToken.String() == "" { | |||
return nil, errors.New("bearer_token cannot be empty") | |||
} | |||
config := &Config{ | |||
Type: tea.String("bearer"), | |||
BearerToken: tea.String(bearerToken.String()), | |||
} | |||
return config, nil | |||
} | |||
func getSTS(section *ini.Section) (*Config, error) { | |||
accesskeyid, err := section.GetKey("access_key_id") | |||
if err != nil { | |||
return nil, errors.New("Missing required access_key_id option in profile for sts") | |||
} | |||
if accesskeyid.String() == "" { | |||
return nil, errors.New("access_key_id cannot be empty") | |||
} | |||
accessKeySecret, err := section.GetKey("access_key_secret") | |||
if err != nil { | |||
return nil, errors.New("Missing required access_key_secret option in profile for sts") | |||
} | |||
if accessKeySecret.String() == "" { | |||
return nil, errors.New("access_key_secret cannot be empty") | |||
} | |||
securityToken, err := section.GetKey("security_token") | |||
if err != nil { | |||
return nil, errors.New("Missing required security_token option in profile for sts") | |||
} | |||
if securityToken.String() == "" { | |||
return nil, errors.New("security_token cannot be empty") | |||
} | |||
config := &Config{ | |||
Type: tea.String("sts"), | |||
AccessKeyId: tea.String(accesskeyid.String()), | |||
AccessKeySecret: tea.String(accessKeySecret.String()), | |||
SecurityToken: tea.String(securityToken.String()), | |||
} | |||
return config, nil | |||
} | |||
func getAccessKey(section *ini.Section) (*Config, error) { | |||
accesskeyid, err := section.GetKey("access_key_id") | |||
if err != nil { | |||
return nil, errors.New("Missing required access_key_id option in profile for access_key") | |||
} | |||
if accesskeyid.String() == "" { | |||
return nil, errors.New("access_key_id cannot be empty") | |||
} | |||
accessKeySecret, err := section.GetKey("access_key_secret") | |||
if err != nil { | |||
return nil, errors.New("Missing required access_key_secret option in profile for access_key") | |||
} | |||
if accessKeySecret.String() == "" { | |||
return nil, errors.New("access_key_secret cannot be empty") | |||
} | |||
config := &Config{ | |||
Type: tea.String("access_key"), | |||
AccessKeyId: tea.String(accesskeyid.String()), | |||
AccessKeySecret: tea.String(accessKeySecret.String()), | |||
} | |||
return config, nil | |||
} | |||
func getType(path, profile string) (*ini.Key, *ini.Section, error) { | |||
ini, err := ini.Load(path) | |||
if err != nil { | |||
return nil, nil, errors.New("ERROR: Can not open file " + err.Error()) | |||
} | |||
section, err := ini.GetSection(profile) | |||
if err != nil { | |||
return nil, nil, errors.New("ERROR: Can not load section " + err.Error()) | |||
} | |||
value, err := section.GetKey("type") | |||
if err != nil { | |||
return nil, nil, errors.New("Missing required type option " + err.Error()) | |||
} | |||
return value, section, nil | |||
} | |||
func getHomePath() string { | |||
if hookOS(runtime.GOOS) == "windows" { | |||
path, ok := os.LookupEnv("USERPROFILE") | |||
if !ok { | |||
return "" | |||
} | |||
return path | |||
} | |||
path, ok := os.LookupEnv("HOME") | |||
if !ok { | |||
return "" | |||
} | |||
return path | |||
} | |||
func checkDefaultPath() (path string, err error) { | |||
path = getHomePath() | |||
if path == "" { | |||
return "", errors.New("The default credential file path is invalid") | |||
} | |||
path = strings.Replace("~/.alibabacloud/credentials", "~", path, 1) | |||
_, err = hookState(os.Stat(path)) | |||
if err != nil { | |||
return "", nil | |||
} | |||
return path, nil | |||
} | |||
func setRuntimeToConfig(config *Config, section *ini.Section) error { | |||
rawTimeout, _ := section.GetKey("timeout") | |||
rawConnectTimeout, _ := section.GetKey("connect_timeout") | |||
rawProxy, _ := section.GetKey("proxy") | |||
rawHost, _ := section.GetKey("host") | |||
if rawProxy != nil { | |||
config.Proxy = tea.String(rawProxy.String()) | |||
} | |||
if rawConnectTimeout != nil { | |||
connectTimeout, err := rawConnectTimeout.Int() | |||
if err != nil { | |||
return fmt.Errorf("Please set connect_timeout with an int value") | |||
} | |||
config.ConnectTimeout = tea.Int(connectTimeout) | |||
} | |||
if rawTimeout != nil { | |||
timeout, err := rawTimeout.Int() | |||
if err != nil { | |||
return fmt.Errorf("Please set timeout with an int value") | |||
} | |||
config.Timeout = tea.Int(timeout) | |||
} | |||
if rawHost != nil { | |||
config.Host = tea.String(rawHost.String()) | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,13 @@ | |||
package credentials | |||
//Environmental virables that may be used by the provider | |||
const ( | |||
ENVCredentialFile = "ALIBABA_CLOUD_CREDENTIALS_FILE" | |||
ENVEcsMetadata = "ALIBABA_CLOUD_ECS_METADATA" | |||
PATHCredentialFile = "~/.alibabacloud/credentials" | |||
) | |||
// Provider will be implemented When you want to customize the provider. | |||
type Provider interface { | |||
resolve() (*Config, error) | |||
} |
@@ -0,0 +1,32 @@ | |||
package credentials | |||
import ( | |||
"errors" | |||
) | |||
type providerChain struct { | |||
Providers []Provider | |||
} | |||
var defaultproviders = []Provider{providerEnv, providerProfile, providerInstance} | |||
var defaultChain = newProviderChain(defaultproviders) | |||
func newProviderChain(providers []Provider) Provider { | |||
return &providerChain{ | |||
Providers: providers, | |||
} | |||
} | |||
func (p *providerChain) resolve() (*Config, error) { | |||
for _, provider := range p.Providers { | |||
config, err := provider.resolve() | |||
if err != nil { | |||
return nil, err | |||
} else if config == nil { | |||
continue | |||
} | |||
return config, err | |||
} | |||
return nil, errors.New("No credential found") | |||
} |
@@ -0,0 +1,59 @@ | |||
package request | |||
import ( | |||
"fmt" | |||
"net/url" | |||
"strings" | |||
"time" | |||
"github.com/aliyun/credentials-go/credentials/utils" | |||
) | |||
// CommonRequest is for requesting credential | |||
type CommonRequest struct { | |||
Scheme string | |||
Method string | |||
Domain string | |||
RegionId string | |||
URL string | |||
ReadTimeout time.Duration | |||
ConnectTimeout time.Duration | |||
isInsecure *bool | |||
userAgent map[string]string | |||
QueryParams map[string]string | |||
Headers map[string]string | |||
queries string | |||
} | |||
// NewCommonRequest returns a CommonRequest | |||
func NewCommonRequest() *CommonRequest { | |||
return &CommonRequest{ | |||
QueryParams: make(map[string]string), | |||
Headers: make(map[string]string), | |||
} | |||
} | |||
// BuildURL returns a url | |||
func (request *CommonRequest) BuildURL() string { | |||
url := fmt.Sprintf("%s://%s", strings.ToLower(request.Scheme), request.Domain) | |||
request.queries = "/?" + utils.GetURLFormedMap(request.QueryParams) | |||
return url + request.queries | |||
} | |||
// BuildStringToSign returns BuildStringToSign | |||
func (request *CommonRequest) BuildStringToSign() (stringToSign string) { | |||
signParams := make(map[string]string) | |||
for key, value := range request.QueryParams { | |||
signParams[key] = value | |||
} | |||
stringToSign = utils.GetURLFormedMap(signParams) | |||
stringToSign = strings.Replace(stringToSign, "+", "%20", -1) | |||
stringToSign = strings.Replace(stringToSign, "*", "%2A", -1) | |||
stringToSign = strings.Replace(stringToSign, "%7E", "~", -1) | |||
stringToSign = url.QueryEscape(stringToSign) | |||
stringToSign = request.Method + "&%2F&" + stringToSign | |||
return | |||
} |
@@ -0,0 +1,53 @@ | |||
package response | |||
import ( | |||
"io" | |||
"io/ioutil" | |||
"net/http" | |||
) | |||
var hookReadAll = func(fn func(r io.Reader) (b []byte, err error)) func(r io.Reader) (b []byte, err error) { | |||
return fn | |||
} | |||
// CommonResponse is for storing message of httpResponse | |||
type CommonResponse struct { | |||
httpStatus int | |||
httpHeaders map[string][]string | |||
httpContentString string | |||
httpContentBytes []byte | |||
} | |||
// ParseFromHTTPResponse assigns for CommonResponse, returns err when body is too large. | |||
func (resp *CommonResponse) ParseFromHTTPResponse(httpResponse *http.Response) (err error) { | |||
defer httpResponse.Body.Close() | |||
body, err := hookReadAll(ioutil.ReadAll)(httpResponse.Body) | |||
if err != nil { | |||
return | |||
} | |||
resp.httpStatus = httpResponse.StatusCode | |||
resp.httpHeaders = httpResponse.Header | |||
resp.httpContentBytes = body | |||
resp.httpContentString = string(body) | |||
return | |||
} | |||
// GetHTTPStatus returns httpStatus | |||
func (resp *CommonResponse) GetHTTPStatus() int { | |||
return resp.httpStatus | |||
} | |||
// GetHTTPHeaders returns httpresponse's headers | |||
func (resp *CommonResponse) GetHTTPHeaders() map[string][]string { | |||
return resp.httpHeaders | |||
} | |||
// GetHTTPContentString return body content as string | |||
func (resp *CommonResponse) GetHTTPContentString() string { | |||
return resp.httpContentString | |||
} | |||
// GetHTTPContentBytes return body content as []byte | |||
func (resp *CommonResponse) GetHTTPContentBytes() []byte { | |||
return resp.httpContentBytes | |||
} |
@@ -0,0 +1,145 @@ | |||
package credentials | |||
import ( | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"strconv" | |||
"time" | |||
"github.com/alibabacloud-go/tea/tea" | |||
"github.com/aliyun/credentials-go/credentials/request" | |||
"github.com/aliyun/credentials-go/credentials/utils" | |||
) | |||
// RsaKeyPairCredential is a kind of credentials | |||
type RsaKeyPairCredential struct { | |||
*credentialUpdater | |||
PrivateKey string | |||
PublicKeyId string | |||
SessionExpiration int | |||
sessionCredential *sessionCredential | |||
runtime *utils.Runtime | |||
} | |||
type rsaKeyPairResponse struct { | |||
SessionAccessKey *sessionAccessKey `json:"SessionAccessKey" xml:"SessionAccessKey"` | |||
} | |||
type sessionAccessKey struct { | |||
SessionAccessKeyId string `json:"SessionAccessKeyId" xml:"SessionAccessKeyId"` | |||
SessionAccessKeySecret string `json:"SessionAccessKeySecret" xml:"SessionAccessKeySecret"` | |||
Expiration string `json:"Expiration" xml:"Expiration"` | |||
} | |||
func newRsaKeyPairCredential(privateKey, publicKeyId string, sessionExpiration int, runtime *utils.Runtime) *RsaKeyPairCredential { | |||
return &RsaKeyPairCredential{ | |||
PrivateKey: privateKey, | |||
PublicKeyId: publicKeyId, | |||
SessionExpiration: sessionExpiration, | |||
credentialUpdater: new(credentialUpdater), | |||
runtime: runtime, | |||
} | |||
} | |||
// GetAccessKeyId reutrns RsaKeyPairCredential's AccessKeyId | |||
// if AccessKeyId is not exist or out of date, the function will update it. | |||
func (r *RsaKeyPairCredential) GetAccessKeyId() (*string, error) { | |||
if r.sessionCredential == nil || r.needUpdateCredential() { | |||
err := r.updateCredential() | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
} | |||
return tea.String(r.sessionCredential.AccessKeyId), nil | |||
} | |||
// GetAccessSecret reutrns RsaKeyPairCredential's AccessKeySecret | |||
// if AccessKeySecret is not exist or out of date, the function will update it. | |||
func (r *RsaKeyPairCredential) GetAccessKeySecret() (*string, error) { | |||
if r.sessionCredential == nil || r.needUpdateCredential() { | |||
err := r.updateCredential() | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
} | |||
return tea.String(r.sessionCredential.AccessKeySecret), nil | |||
} | |||
// GetSecurityToken is useless RsaKeyPairCredential | |||
func (r *RsaKeyPairCredential) GetSecurityToken() (*string, error) { | |||
return tea.String(""), nil | |||
} | |||
// GetBearerToken is useless for RsaKeyPairCredential | |||
func (r *RsaKeyPairCredential) GetBearerToken() *string { | |||
return tea.String("") | |||
} | |||
// GetType reutrns RsaKeyPairCredential's type | |||
func (r *RsaKeyPairCredential) GetType() *string { | |||
return tea.String("rsa_key_pair") | |||
} | |||
func (r *RsaKeyPairCredential) updateCredential() (err error) { | |||
if r.runtime == nil { | |||
r.runtime = new(utils.Runtime) | |||
} | |||
request := request.NewCommonRequest() | |||
request.Domain = "sts.aliyuncs.com" | |||
if r.runtime.Host != "" { | |||
request.Domain = r.runtime.Host | |||
} | |||
request.Scheme = "HTTPS" | |||
request.Method = "GET" | |||
request.QueryParams["AccessKeyId"] = r.PublicKeyId | |||
request.QueryParams["Action"] = "GenerateSessionAccessKey" | |||
request.QueryParams["Format"] = "JSON" | |||
if r.SessionExpiration > 0 { | |||
if r.SessionExpiration >= 900 && r.SessionExpiration <= 3600 { | |||
request.QueryParams["DurationSeconds"] = strconv.Itoa(r.SessionExpiration) | |||
} else { | |||
err = errors.New("[InvalidParam]:Key Pair session duration should be in the range of 15min - 1Hr") | |||
return | |||
} | |||
} else { | |||
request.QueryParams["DurationSeconds"] = strconv.Itoa(defaultDurationSeconds) | |||
} | |||
request.QueryParams["SignatureMethod"] = "SHA256withRSA" | |||
request.QueryParams["SignatureType"] = "PRIVATEKEY" | |||
request.QueryParams["SignatureVersion"] = "1.0" | |||
request.QueryParams["Version"] = "2015-04-01" | |||
request.QueryParams["Timestamp"] = utils.GetTimeInFormatISO8601() | |||
request.QueryParams["SignatureNonce"] = utils.GetUUID() | |||
signature := utils.Sha256WithRsa(request.BuildStringToSign(), r.PrivateKey) | |||
request.QueryParams["Signature"] = signature | |||
request.Headers["Host"] = request.Domain | |||
request.Headers["Accept-Encoding"] = "identity" | |||
request.URL = request.BuildURL() | |||
content, err := doAction(request, r.runtime) | |||
if err != nil { | |||
return fmt.Errorf("refresh KeyPair err: %s", err.Error()) | |||
} | |||
var resp *rsaKeyPairResponse | |||
err = json.Unmarshal(content, &resp) | |||
if err != nil { | |||
return fmt.Errorf("refresh KeyPair err: Json Unmarshal fail: %s", err.Error()) | |||
} | |||
if resp == nil || resp.SessionAccessKey == nil { | |||
return fmt.Errorf("refresh KeyPair err: SessionAccessKey is empty") | |||
} | |||
sessionAccessKey := resp.SessionAccessKey | |||
if sessionAccessKey.SessionAccessKeyId == "" || sessionAccessKey.SessionAccessKeySecret == "" || sessionAccessKey.Expiration == "" { | |||
return fmt.Errorf("refresh KeyPair err: SessionAccessKeyId: %v, SessionAccessKeySecret: %v, Expiration: %v", sessionAccessKey.SessionAccessKeyId, sessionAccessKey.SessionAccessKeySecret, sessionAccessKey.Expiration) | |||
} | |||
expirationTime, err := time.Parse("2006-01-02T15:04:05Z", sessionAccessKey.Expiration) | |||
r.lastUpdateTimestamp = time.Now().Unix() | |||
r.credentialExpiration = int(expirationTime.Unix() - time.Now().Unix()) | |||
r.sessionCredential = &sessionCredential{ | |||
AccessKeyId: sessionAccessKey.SessionAccessKeyId, | |||
AccessKeySecret: sessionAccessKey.SessionAccessKeySecret, | |||
} | |||
return | |||
} |
@@ -0,0 +1,7 @@ | |||
package credentials | |||
type sessionCredential struct { | |||
AccessKeyId string | |||
AccessKeySecret string | |||
SecurityToken string | |||
} |
@@ -0,0 +1,43 @@ | |||
package credentials | |||
import "github.com/alibabacloud-go/tea/tea" | |||
// StsTokenCredential is a kind of credentials | |||
type StsTokenCredential struct { | |||
AccessKeyId string | |||
AccessKeySecret string | |||
SecurityToken string | |||
} | |||
func newStsTokenCredential(accessKeyId, accessKeySecret, securityToken string) *StsTokenCredential { | |||
return &StsTokenCredential{ | |||
AccessKeyId: accessKeyId, | |||
AccessKeySecret: accessKeySecret, | |||
SecurityToken: securityToken, | |||
} | |||
} | |||
// GetAccessKeyId reutrns StsTokenCredential's AccessKeyId | |||
func (s *StsTokenCredential) GetAccessKeyId() (*string, error) { | |||
return tea.String(s.AccessKeyId), nil | |||
} | |||
// GetAccessSecret reutrns StsTokenCredential's AccessKeySecret | |||
func (s *StsTokenCredential) GetAccessKeySecret() (*string, error) { | |||
return tea.String(s.AccessKeySecret), nil | |||
} | |||
// GetSecurityToken reutrns StsTokenCredential's SecurityToken | |||
func (s *StsTokenCredential) GetSecurityToken() (*string, error) { | |||
return tea.String(s.SecurityToken), nil | |||
} | |||
// GetBearerToken is useless StsTokenCredential | |||
func (s *StsTokenCredential) GetBearerToken() *string { | |||
return tea.String("") | |||
} | |||
// GetType reutrns StsTokenCredential's type | |||
func (s *StsTokenCredential) GetType() *string { | |||
return tea.String("sts") | |||
} |
@@ -0,0 +1,163 @@ | |||
package credentials | |||
import ( | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"strconv" | |||
"time" | |||
"github.com/alibabacloud-go/tea/tea" | |||
"github.com/aliyun/credentials-go/credentials/request" | |||
"github.com/aliyun/credentials-go/credentials/utils" | |||
) | |||
const defaultDurationSeconds = 3600 | |||
// RAMRoleArnCredential is a kind of credentials | |||
type RAMRoleArnCredential struct { | |||
*credentialUpdater | |||
AccessKeyId string | |||
AccessKeySecret string | |||
RoleArn string | |||
RoleSessionName string | |||
RoleSessionExpiration int | |||
Policy string | |||
sessionCredential *sessionCredential | |||
runtime *utils.Runtime | |||
} | |||
type ramRoleArnResponse struct { | |||
Credentials *credentialsInResponse `json:"Credentials" xml:"Credentials"` | |||
} | |||
type credentialsInResponse struct { | |||
AccessKeyId string `json:"AccessKeyId" xml:"AccessKeyId"` | |||
AccessKeySecret string `json:"AccessKeySecret" xml:"AccessKeySecret"` | |||
SecurityToken string `json:"SecurityToken" xml:"SecurityToken"` | |||
Expiration string `json:"Expiration" xml:"Expiration"` | |||
} | |||
func newRAMRoleArnCredential(accessKeyId, accessKeySecret, roleArn, roleSessionName, policy string, roleSessionExpiration int, runtime *utils.Runtime) *RAMRoleArnCredential { | |||
return &RAMRoleArnCredential{ | |||
AccessKeyId: accessKeyId, | |||
AccessKeySecret: accessKeySecret, | |||
RoleArn: roleArn, | |||
RoleSessionName: roleSessionName, | |||
RoleSessionExpiration: roleSessionExpiration, | |||
Policy: policy, | |||
credentialUpdater: new(credentialUpdater), | |||
runtime: runtime, | |||
} | |||
} | |||
// GetAccessKeyId reutrns RamRoleArnCredential's AccessKeyId | |||
// if AccessKeyId is not exist or out of date, the function will update it. | |||
func (r *RAMRoleArnCredential) GetAccessKeyId() (*string, error) { | |||
if r.sessionCredential == nil || r.needUpdateCredential() { | |||
err := r.updateCredential() | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
} | |||
return tea.String(r.sessionCredential.AccessKeyId), nil | |||
} | |||
// GetAccessSecret reutrns RamRoleArnCredential's AccessKeySecret | |||
// if AccessKeySecret is not exist or out of date, the function will update it. | |||
func (r *RAMRoleArnCredential) GetAccessKeySecret() (*string, error) { | |||
if r.sessionCredential == nil || r.needUpdateCredential() { | |||
err := r.updateCredential() | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
} | |||
return tea.String(r.sessionCredential.AccessKeySecret), nil | |||
} | |||
// GetSecurityToken reutrns RamRoleArnCredential's SecurityToken | |||
// if SecurityToken is not exist or out of date, the function will update it. | |||
func (r *RAMRoleArnCredential) GetSecurityToken() (*string, error) { | |||
if r.sessionCredential == nil || r.needUpdateCredential() { | |||
err := r.updateCredential() | |||
if err != nil { | |||
return tea.String(""), err | |||
} | |||
} | |||
return tea.String(r.sessionCredential.SecurityToken), nil | |||
} | |||
// GetBearerToken is useless RamRoleArnCredential | |||
func (r *RAMRoleArnCredential) GetBearerToken() *string { | |||
return tea.String("") | |||
} | |||
// GetType reutrns RamRoleArnCredential's type | |||
func (r *RAMRoleArnCredential) GetType() *string { | |||
return tea.String("ram_role_arn") | |||
} | |||
func (r *RAMRoleArnCredential) updateCredential() (err error) { | |||
if r.runtime == nil { | |||
r.runtime = new(utils.Runtime) | |||
} | |||
request := request.NewCommonRequest() | |||
request.Domain = "sts.aliyuncs.com" | |||
request.Scheme = "HTTPS" | |||
request.Method = "GET" | |||
request.QueryParams["AccessKeyId"] = r.AccessKeyId | |||
request.QueryParams["Action"] = "AssumeRole" | |||
request.QueryParams["Format"] = "JSON" | |||
if r.RoleSessionExpiration > 0 { | |||
if r.RoleSessionExpiration >= 900 && r.RoleSessionExpiration <= 3600 { | |||
request.QueryParams["DurationSeconds"] = strconv.Itoa(r.RoleSessionExpiration) | |||
} else { | |||
err = errors.New("[InvalidParam]:Assume Role session duration should be in the range of 15min - 1Hr") | |||
return | |||
} | |||
} else { | |||
request.QueryParams["DurationSeconds"] = strconv.Itoa(defaultDurationSeconds) | |||
} | |||
request.QueryParams["RoleArn"] = r.RoleArn | |||
if r.Policy != "" { | |||
request.QueryParams["Policy"] = r.Policy | |||
} | |||
request.QueryParams["RoleSessionName"] = r.RoleSessionName | |||
request.QueryParams["SignatureMethod"] = "HMAC-SHA1" | |||
request.QueryParams["SignatureVersion"] = "1.0" | |||
request.QueryParams["Version"] = "2015-04-01" | |||
request.QueryParams["Timestamp"] = utils.GetTimeInFormatISO8601() | |||
request.QueryParams["SignatureNonce"] = utils.GetUUID() | |||
signature := utils.ShaHmac1(request.BuildStringToSign(), r.AccessKeySecret+"&") | |||
request.QueryParams["Signature"] = signature | |||
request.Headers["Host"] = request.Domain | |||
request.Headers["Accept-Encoding"] = "identity" | |||
request.URL = request.BuildURL() | |||
content, err := doAction(request, r.runtime) | |||
if err != nil { | |||
return fmt.Errorf("refresh RoleArn sts token err: %s", err.Error()) | |||
} | |||
var resp *ramRoleArnResponse | |||
err = json.Unmarshal(content, &resp) | |||
if err != nil { | |||
return fmt.Errorf("refresh RoleArn sts token err: Json.Unmarshal fail: %s", err.Error()) | |||
} | |||
if resp == nil || resp.Credentials == nil { | |||
return fmt.Errorf("refresh RoleArn sts token err: Credentials is empty") | |||
} | |||
respCredentials := resp.Credentials | |||
if respCredentials.AccessKeyId == "" || respCredentials.AccessKeySecret == "" || respCredentials.SecurityToken == "" || respCredentials.Expiration == "" { | |||
return fmt.Errorf("refresh RoleArn sts token err: AccessKeyId: %s, AccessKeySecret: %s, SecurityToken: %s, Expiration: %s", respCredentials.AccessKeyId, respCredentials.AccessKeySecret, respCredentials.SecurityToken, respCredentials.Expiration) | |||
} | |||
expirationTime, err := time.Parse("2006-01-02T15:04:05Z", respCredentials.Expiration) | |||
r.lastUpdateTimestamp = time.Now().Unix() | |||
r.credentialExpiration = int(expirationTime.Unix() - time.Now().Unix()) | |||
r.sessionCredential = &sessionCredential{ | |||
AccessKeyId: respCredentials.AccessKeyId, | |||
AccessKeySecret: respCredentials.AccessKeySecret, | |||
SecurityToken: respCredentials.SecurityToken, | |||
} | |||
return | |||
} |
@@ -0,0 +1,35 @@ | |||
package utils | |||
import ( | |||
"context" | |||
"net" | |||
"time" | |||
) | |||
// Runtime is for setting timeout, proxy and host | |||
type Runtime struct { | |||
ReadTimeout int | |||
ConnectTimeout int | |||
Proxy string | |||
Host string | |||
} | |||
// NewRuntime returns a Runtime | |||
func NewRuntime(readTimeout, connectTimeout int, proxy string, host string) *Runtime { | |||
return &Runtime{ | |||
ReadTimeout: readTimeout, | |||
ConnectTimeout: connectTimeout, | |||
Proxy: proxy, | |||
Host: host, | |||
} | |||
} | |||
// Timeout is for connect Timeout | |||
func Timeout(connectTimeout time.Duration) func(cxt context.Context, net, addr string) (c net.Conn, err error) { | |||
return func(ctx context.Context, network, address string) (net.Conn, error) { | |||
return (&net.Dialer{ | |||
Timeout: connectTimeout, | |||
DualStack: true, | |||
}).DialContext(ctx, network, address) | |||
} | |||
} |
@@ -0,0 +1,146 @@ | |||
package utils | |||
import ( | |||
"crypto" | |||
"crypto/hmac" | |||
"crypto/md5" | |||
"crypto/rand" | |||
"crypto/rsa" | |||
"crypto/sha1" | |||
"crypto/x509" | |||
"encoding/base64" | |||
"encoding/hex" | |||
"hash" | |||
"io" | |||
rand2 "math/rand" | |||
"net/url" | |||
"time" | |||
) | |||
type uuid [16]byte | |||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | |||
var hookRead = func(fn func(p []byte) (n int, err error)) func(p []byte) (n int, err error) { | |||
return fn | |||
} | |||
var hookRSA = func(fn func(rand io.Reader, priv *rsa.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error)) func(rand io.Reader, priv *rsa.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { | |||
return fn | |||
} | |||
// GetUUID returns a uuid | |||
func GetUUID() (uuidHex string) { | |||
uuid := newUUID() | |||
uuidHex = hex.EncodeToString(uuid[:]) | |||
return | |||
} | |||
// RandStringBytes returns a rand string | |||
func RandStringBytes(n int) string { | |||
b := make([]byte, n) | |||
for i := range b { | |||
b[i] = letterBytes[rand2.Intn(len(letterBytes))] | |||
} | |||
return string(b) | |||
} | |||
// ShaHmac1 return a string which has been hashed | |||
func ShaHmac1(source, secret string) string { | |||
key := []byte(secret) | |||
hmac := hmac.New(sha1.New, key) | |||
hmac.Write([]byte(source)) | |||
signedBytes := hmac.Sum(nil) | |||
signedString := base64.StdEncoding.EncodeToString(signedBytes) | |||
return signedString | |||
} | |||
// Sha256WithRsa return a string which has been hashed with Rsa | |||
func Sha256WithRsa(source, secret string) string { | |||
decodeString, err := base64.StdEncoding.DecodeString(secret) | |||
if err != nil { | |||
panic(err) | |||
} | |||
private, err := x509.ParsePKCS8PrivateKey(decodeString) | |||
if err != nil { | |||
panic(err) | |||
} | |||
h := crypto.Hash.New(crypto.SHA256) | |||
h.Write([]byte(source)) | |||
hashed := h.Sum(nil) | |||
signature, err := hookRSA(rsa.SignPKCS1v15)(rand.Reader, private.(*rsa.PrivateKey), | |||
crypto.SHA256, hashed) | |||
if err != nil { | |||
panic(err) | |||
} | |||
return base64.StdEncoding.EncodeToString(signature) | |||
} | |||
// GetMD5Base64 returns a string which has been base64 | |||
func GetMD5Base64(bytes []byte) (base64Value string) { | |||
md5Ctx := md5.New() | |||
md5Ctx.Write(bytes) | |||
md5Value := md5Ctx.Sum(nil) | |||
base64Value = base64.StdEncoding.EncodeToString(md5Value) | |||
return | |||
} | |||
// GetTimeInFormatISO8601 returns a time string | |||
func GetTimeInFormatISO8601() (timeStr string) { | |||
gmt := time.FixedZone("GMT", 0) | |||
return time.Now().In(gmt).Format("2006-01-02T15:04:05Z") | |||
} | |||
// GetURLFormedMap returns a url encoded string | |||
func GetURLFormedMap(source map[string]string) (urlEncoded string) { | |||
urlEncoder := url.Values{} | |||
for key, value := range source { | |||
urlEncoder.Add(key, value) | |||
} | |||
urlEncoded = urlEncoder.Encode() | |||
return | |||
} | |||
func newUUID() uuid { | |||
ns := uuid{} | |||
safeRandom(ns[:]) | |||
u := newFromHash(md5.New(), ns, RandStringBytes(16)) | |||
u[6] = (u[6] & 0x0f) | (byte(2) << 4) | |||
u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) | |||
return u | |||
} | |||
func newFromHash(h hash.Hash, ns uuid, name string) uuid { | |||
u := uuid{} | |||
h.Write(ns[:]) | |||
h.Write([]byte(name)) | |||
copy(u[:], h.Sum(nil)) | |||
return u | |||
} | |||
func safeRandom(dest []byte) { | |||
if _, err := hookRead(rand.Read)(dest); err != nil { | |||
panic(err) | |||
} | |||
} | |||
func (u uuid) String() string { | |||
buf := make([]byte, 36) | |||
hex.Encode(buf[0:8], u[0:4]) | |||
buf[8] = '-' | |||
hex.Encode(buf[9:13], u[4:6]) | |||
buf[13] = '-' | |||
hex.Encode(buf[14:18], u[6:8]) | |||
buf[18] = '-' | |||
hex.Encode(buf[19:23], u[8:10]) | |||
buf[23] = '-' | |||
hex.Encode(buf[24:], u[10:]) | |||
return string(buf) | |||
} |
@@ -0,0 +1,4 @@ | |||
language: go | |||
go: | |||
- 1.x |
@@ -0,0 +1,22 @@ | |||
Copyright (c) 2012-2021 Charles Banning <clbanning@gmail.com>. All rights reserved. | |||
The MIT License (MIT) | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. | |||
@@ -0,0 +1,201 @@ | |||
package mxj | |||
import ( | |||
"bytes" | |||
"encoding/xml" | |||
"reflect" | |||
) | |||
const ( | |||
DefaultElementTag = "element" | |||
) | |||
// Encode arbitrary value as XML. | |||
// | |||
// Note: unmarshaling the resultant | |||
// XML may not return the original value, since tag labels may have been injected | |||
// to create the XML representation of the value. | |||
/* | |||
Encode an arbitrary JSON object. | |||
package main | |||
import ( | |||
"encoding/json" | |||
"fmt" | |||
"github.com/clbanning/mxj" | |||
) | |||
func main() { | |||
jsondata := []byte(`[ | |||
{ "somekey":"somevalue" }, | |||
"string", | |||
3.14159265, | |||
true | |||
]`) | |||
var i interface{} | |||
err := json.Unmarshal(jsondata, &i) | |||
if err != nil { | |||
// do something | |||
} | |||
x, err := mxj.AnyXmlIndent(i, "", " ", "mydoc") | |||
if err != nil { | |||
// do something else | |||
} | |||
fmt.Println(string(x)) | |||
} | |||
output: | |||
<mydoc> | |||
<somekey>somevalue</somekey> | |||
<element>string</element> | |||
<element>3.14159265</element> | |||
<element>true</element> | |||
</mydoc> | |||
An extreme example is available in examples/goofy_map.go. | |||
*/ | |||
// Alternative values for DefaultRootTag and DefaultElementTag can be set as: | |||
// AnyXml( v, myRootTag, myElementTag). | |||
func AnyXml(v interface{}, tags ...string) ([]byte, error) { | |||
var rt, et string | |||
if len(tags) == 1 || len(tags) == 2 { | |||
rt = tags[0] | |||
} else { | |||
rt = DefaultRootTag | |||
} | |||
if len(tags) == 2 { | |||
et = tags[1] | |||
} else { | |||
et = DefaultElementTag | |||
} | |||
if v == nil { | |||
if useGoXmlEmptyElemSyntax { | |||
return []byte("<" + rt + "></" + rt + ">"), nil | |||
} | |||
return []byte("<" + rt + "/>"), nil | |||
} | |||
if reflect.TypeOf(v).Kind() == reflect.Struct { | |||
return xml.Marshal(v) | |||
} | |||
var err error | |||
s := new(bytes.Buffer) | |||
p := new(pretty) | |||
var b []byte | |||
switch v.(type) { | |||
case []interface{}: | |||
if _, err = s.WriteString("<" + rt + ">"); err != nil { | |||
return nil, err | |||
} | |||
for _, vv := range v.([]interface{}) { | |||
switch vv.(type) { | |||
case map[string]interface{}: | |||
m := vv.(map[string]interface{}) | |||
if len(m) == 1 { | |||
for tag, val := range m { | |||
err = marshalMapToXmlIndent(false, s, tag, val, p) | |||
} | |||
} else { | |||
err = marshalMapToXmlIndent(false, s, et, vv, p) | |||
} | |||
default: | |||
err = marshalMapToXmlIndent(false, s, et, vv, p) | |||
} | |||
if err != nil { | |||
break | |||
} | |||
} | |||
if _, err = s.WriteString("</" + rt + ">"); err != nil { | |||
return nil, err | |||
} | |||
b = s.Bytes() | |||
case map[string]interface{}: | |||
m := Map(v.(map[string]interface{})) | |||
b, err = m.Xml(rt) | |||
default: | |||
err = marshalMapToXmlIndent(false, s, rt, v, p) | |||
b = s.Bytes() | |||
} | |||
return b, err | |||
} | |||
// Encode an arbitrary value as a pretty XML string. | |||
// Alternative values for DefaultRootTag and DefaultElementTag can be set as: | |||
// AnyXmlIndent( v, "", " ", myRootTag, myElementTag). | |||
func AnyXmlIndent(v interface{}, prefix, indent string, tags ...string) ([]byte, error) { | |||
var rt, et string | |||
if len(tags) == 1 || len(tags) == 2 { | |||
rt = tags[0] | |||
} else { | |||
rt = DefaultRootTag | |||
} | |||
if len(tags) == 2 { | |||
et = tags[1] | |||
} else { | |||
et = DefaultElementTag | |||
} | |||
if v == nil { | |||
if useGoXmlEmptyElemSyntax { | |||
return []byte(prefix + "<" + rt + "></" + rt + ">"), nil | |||
} | |||
return []byte(prefix + "<" + rt + "/>"), nil | |||
} | |||
if reflect.TypeOf(v).Kind() == reflect.Struct { | |||
return xml.MarshalIndent(v, prefix, indent) | |||
} | |||
var err error | |||
s := new(bytes.Buffer) | |||
p := new(pretty) | |||
p.indent = indent | |||
p.padding = prefix | |||
var b []byte | |||
switch v.(type) { | |||
case []interface{}: | |||
if _, err = s.WriteString("<" + rt + ">\n"); err != nil { | |||
return nil, err | |||
} | |||
p.Indent() | |||
for _, vv := range v.([]interface{}) { | |||
switch vv.(type) { | |||
case map[string]interface{}: | |||
m := vv.(map[string]interface{}) | |||
if len(m) == 1 { | |||
for tag, val := range m { | |||
err = marshalMapToXmlIndent(true, s, tag, val, p) | |||
} | |||
} else { | |||
p.start = 1 // we 1 tag in | |||
err = marshalMapToXmlIndent(true, s, et, vv, p) | |||
// *s += "\n" | |||
if _, err = s.WriteString("\n"); err != nil { | |||
return nil, err | |||
} | |||
} | |||
default: | |||
p.start = 0 // in case trailing p.start = 1 | |||
err = marshalMapToXmlIndent(true, s, et, vv, p) | |||
} | |||
if err != nil { | |||
break | |||
} | |||
} | |||
if _, err = s.WriteString(`</` + rt + `>`); err != nil { | |||
return nil, err | |||
} | |||
b = s.Bytes() | |||
case map[string]interface{}: | |||
m := Map(v.(map[string]interface{})) | |||
b, err = m.XmlIndent(prefix, indent, rt) | |||
default: | |||
err = marshalMapToXmlIndent(true, s, rt, v, p) | |||
b = s.Bytes() | |||
} | |||
return b, err | |||
} |
@@ -0,0 +1,54 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld<></name></author><entry><title>rietveld: an attempt at pubsubhubbub | |||
</title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html"> | |||
An attempt at adding pubsubhubbub support to Rietveld. | |||
http://code.google.com/p/pubsubhubbub | |||
http://code.google.com/p/rietveld/issues/detail?id=155 | |||
The server side of the protocol is trivial: | |||
1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all | |||
feeds that will be pubsubhubbubbed. | |||
2. every time one of those feeds changes, tell the hub | |||
with a simple POST request. | |||
I have tested this by adding debug prints to a local hub | |||
server and checking that the server got the right publish | |||
requests. | |||
I can&#39;t quite get the server to work, but I think the bug | |||
is not in my code. I think that the server expects to be | |||
able to grab the feed and see the feed&#39;s actual URL in | |||
the link rel=&quot;self&quot;, but the default value for that drops | |||
the :port from the URL, and I cannot for the life of me | |||
figure out how to get the Atom generator deep inside | |||
django not to do that, or even where it is doing that, | |||
or even what code is running to generate the Atom feed. | |||
(I thought I knew but I added some assert False statements | |||
and it kept running!) | |||
Ignoring that particular problem, I would appreciate | |||
feedback on the right way to get the two values at | |||
the top of feeds.py marked NOTE(rsc). | |||
</summary></entry><entry><title>rietveld: correct tab handling | |||
</title><link href="http://codereview.appspot.com/124106" rel="alternate"></link><updated>2009-10-03T23:02:17+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:0a2a4f19bb815101f0ba2904aed7c35a</id><summary type="html"> | |||
This fixes the buggy tab rendering that can be seen at | |||
http://codereview.appspot.com/116075/diff/1/2 | |||
The fundamental problem was that the tab code was | |||
not being told what column the text began in, so it | |||
didn&#39;t know where to put the tab stops. Another problem | |||
was that some of the code assumed that string byte | |||
offsets were the same as column offsets, which is only | |||
true if there are no tabs. | |||
In the process of fixing this, I cleaned up the arguments | |||
to Fold and ExpandTabs and renamed them Break and | |||
_ExpandTabs so that I could be sure that I found all the | |||
call sites. I also wanted to verify that ExpandTabs was | |||
not being used from outside intra_region_diff.py. | |||
</summary></entry></feed> ` | |||
@@ -0,0 +1,138 @@ | |||
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities. | |||
// Copyright 2012-2019, Charles Banning. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file | |||
/* | |||
Marshal/Unmarshal XML to/from map[string]interface{} values (and JSON); extract/modify values from maps by key or key-path, including wildcards. | |||
mxj supplants the legacy x2j and j2x packages. The subpackage x2j-wrapper is provided to facilitate migrating from the x2j package. The x2j and j2x subpackages provide similar functionality of the old packages but are not function-name compatible with them. | |||
Note: this library was designed for processing ad hoc anonymous messages. Bulk processing large data sets may be much more efficiently performed using the encoding/xml or encoding/json packages from Go's standard library directly. | |||
Related Packages: | |||
checkxml: github.com/clbanning/checkxml provides functions for validating XML data. | |||
Notes: | |||
2020.05.01: v2.2 - optimize map to XML encoding for large XML docs. | |||
2019.07.04: v2.0 - remove unnecessary methods - mv.XmlWriterRaw, mv.XmlIndentWriterRaw - for Map and MapSeq. | |||
2019.07.04: Add MapSeq type and move associated functions and methods from Map to MapSeq. | |||
2019.01.21: DecodeSimpleValuesAsMap - decode to map[<tag>:map["#text":<value>]] rather than map[<tag>:<value>]. | |||
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc. | |||
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps. | |||
2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package. | |||
2017.02.22: LeafNode paths can use ".N" syntax rather than "[N]" for list member indexing. | |||
2017.02.21: github.com/clbanning/checkxml provides functions for validating XML data. | |||
2017.02.10: SetFieldSeparator changes field separator for args in UpdateValuesForPath, ValuesFor... methods. | |||
2017.02.06: Support XMPP stream processing - HandleXMPPStreamTag(). | |||
2016.11.07: Preserve name space prefix syntax in XmlSeq parser - NewMapXmlSeq(), etc. | |||
2016.06.25: Support overriding default XML attribute prefix, "-", in Map keys - SetAttrPrefix(). | |||
2016.05.26: Support customization of xml.Decoder by exposing CustomDecoder variable. | |||
2016.03.19: Escape invalid chars when encoding XML attribute and element values - XMLEscapeChars(). | |||
2016.03.02: By default decoding XML with float64 and bool value casting will not cast "NaN", "Inf", and "-Inf". | |||
To cast them to float64, first set flag with CastNanInf(true). | |||
2016.02.22: New mv.Root(), mv.Elements(), mv.Attributes methods let you examine XML document structure. | |||
2016.02.16: Add CoerceKeysToLower() option to handle tags with mixed capitalization. | |||
2016.02.12: Seek for first xml.StartElement token; only return error if io.EOF is reached first (handles BOM). | |||
2015-12-02: NewMapXmlSeq() with mv.XmlSeq() & co. will try to preserve structure of XML doc when re-encoding. | |||
2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML. | |||
SUMMARY | |||
type Map map[string]interface{} | |||
Create a Map value, 'mv', from any map[string]interface{} value, 'v': | |||
mv := Map(v) | |||
Unmarshal / marshal XML as a Map value, 'mv': | |||
mv, err := NewMapXml(xmlValue) // unmarshal | |||
xmlValue, err := mv.Xml() // marshal | |||
Unmarshal XML from an io.Reader as a Map value, 'mv': | |||
mv, err := NewMapXmlReader(xmlReader) // repeated calls, as with an os.File Reader, will process stream | |||
mv, raw, err := NewMapXmlReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded | |||
Marshal Map value, 'mv', to an XML Writer (io.Writer): | |||
err := mv.XmlWriter(xmlWriter) | |||
raw, err := mv.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter | |||
Also, for prettified output: | |||
xmlValue, err := mv.XmlIndent(prefix, indent, ...) | |||
err := mv.XmlIndentWriter(xmlWriter, prefix, indent, ...) | |||
raw, err := mv.XmlIndentWriterRaw(xmlWriter, prefix, indent, ...) | |||
Bulk process XML with error handling (note: handlers must return a boolean value): | |||
err := HandleXmlReader(xmlReader, mapHandler(Map), errHandler(error)) | |||
err := HandleXmlReaderRaw(xmlReader, mapHandler(Map, []byte), errHandler(error, []byte)) | |||
Converting XML to JSON: see Examples for NewMapXml and HandleXmlReader. | |||
There are comparable functions and methods for JSON processing. | |||
Arbitrary structure values can be decoded to / encoded from Map values: | |||
mv, err := NewMapStruct(structVal) | |||
err := mv.Struct(structPointer) | |||
To work with XML tag values, JSON or Map key values or structure field values, decode the XML, JSON | |||
or structure to a Map value, 'mv', or cast a map[string]interface{} value to a Map value, 'mv', then: | |||
paths := mv.PathsForKey(key) | |||
path := mv.PathForKeyShortest(key) | |||
values, err := mv.ValuesForKey(key, subkeys) | |||
values, err := mv.ValuesForPath(path, subkeys) // 'path' can be dot-notation with wildcards and indexed arrays. | |||
count, err := mv.UpdateValuesForPath(newVal, path, subkeys) | |||
Get everything at once, irrespective of path depth: | |||
leafnodes := mv.LeafNodes() | |||
leafvalues := mv.LeafValues() | |||
A new Map with whatever keys are desired can be created from the current Map and then encoded in XML | |||
or JSON. (Note: keys can use dot-notation. 'oldKey' can also use wildcards and indexed arrays.) | |||
newMap, err := mv.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N") | |||
newMap, err := mv.NewMap("oldKey1", "oldKey3", "oldKey5") // a subset of 'mv'; see "examples/partial.go" | |||
newXml, err := newMap.Xml() // for example | |||
newJson, err := newMap.Json() // ditto | |||
XML PARSING CONVENTIONS | |||
Using NewMapXml() | |||
- Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`, | |||
to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)` or | |||
`SetAttrPrefix()`.) | |||
- If the element is a simple element and has attributes, the element value | |||
is given the key `#text` for its `map[string]interface{}` representation. (See | |||
the 'atomFeedString.xml' test data, below.) | |||
- XML comments, directives, and process instructions are ignored. | |||
- If CoerceKeysToLower() has been called, then the resultant keys will be lower case. | |||
Using NewMapXmlSeq() | |||
- Attributes are parsed to `map["#attr"]map[<attr_label>]map[string]interface{}`values | |||
where the `<attr_label>` value has "#text" and "#seq" keys - the "#text" key holds the | |||
value for `<attr_label>`. | |||
- All elements, except for the root, have a "#seq" key. | |||
- Comments, directives, and process instructions are unmarshalled into the Map using the | |||
keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more | |||
specifics.) | |||
- Name space syntax is preserved: | |||
- <ns:key>something</ns.key> parses to map["ns:key"]interface{}{"something"} | |||
- xmlns:ns="http://myns.com/ns" parses to map["xmlns:ns"]interface{}{"http://myns.com/ns"} | |||
Both | |||
- By default, "Nan", "Inf", and "-Inf" values are not cast to float64. If you want them | |||
to be cast, set a flag to cast them using CastNanInf(true). | |||
XML ENCODING CONVENTIONS | |||
- 'nil' Map values, which may represent 'null' JSON values, are encoded as "<tag/>". | |||
NOTE: the operation is not symmetric as "<tag/>" elements are decoded as 'tag:""' Map values, | |||
which, then, encode in JSON as '"tag":""' values.. | |||
- ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one. (Go | |||
randomizes the walk through map[string]interface{} values.) If you plan to re-encode the | |||
Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and | |||
mv.XmlSeq() - these try to preserve the element sequencing but with added complexity when | |||
working with the Map representation. | |||
*/ | |||
package mxj |
@@ -0,0 +1,93 @@ | |||
// Copyright 2016 Charles Banning. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file | |||
package mxj | |||
import ( | |||
"bytes" | |||
) | |||
var xmlEscapeChars bool | |||
// XMLEscapeChars(true) forces escaping invalid characters in attribute and element values. | |||
// NOTE: this is brute force with NO interrogation of '&' being escaped already; if it is | |||
// then '&' will be re-escaped as '&amp;'. | |||
// | |||
/* | |||
The values are: | |||
" " | |||
' ' | |||
< < | |||
> > | |||
& & | |||
*/ | |||
// | |||
// Note: if XMLEscapeCharsDecoder(true) has been called - or the default, 'false,' value | |||
// has been toggled to 'true' - then XMLEscapeChars(true) is ignored. If XMLEscapeChars(true) | |||
// has already been called before XMLEscapeCharsDecoder(true), XMLEscapeChars(false) is called | |||
// to turn escape encoding on mv.Xml, etc., to prevent double escaping ampersands, '&'. | |||
func XMLEscapeChars(b ...bool) { | |||
var bb bool | |||
if len(b) == 0 { | |||
bb = !xmlEscapeChars | |||
} else { | |||
bb = b[0] | |||
} | |||
if bb == true && xmlEscapeCharsDecoder == false { | |||
xmlEscapeChars = true | |||
} else { | |||
xmlEscapeChars = false | |||
} | |||
} | |||
// Scan for '&' first, since 's' may contain "&" that is parsed to "&amp;" | |||
// - or "<" that is parsed to "&lt;". | |||
var escapechars = [][2][]byte{ | |||
{[]byte(`&`), []byte(`&`)}, | |||
{[]byte(`<`), []byte(`<`)}, | |||
{[]byte(`>`), []byte(`>`)}, | |||
{[]byte(`"`), []byte(`"`)}, | |||
{[]byte(`'`), []byte(`'`)}, | |||
} | |||
func escapeChars(s string) string { | |||
if len(s) == 0 { | |||
return s | |||
} | |||
b := []byte(s) | |||
for _, v := range escapechars { | |||
n := bytes.Count(b, v[0]) | |||
if n == 0 { | |||
continue | |||
} | |||
b = bytes.Replace(b, v[0], v[1], n) | |||
} | |||
return string(b) | |||
} | |||
// per issue #84, escape CharData values from xml.Decoder | |||
var xmlEscapeCharsDecoder bool | |||
// XMLEscapeCharsDecoder(b ...bool) escapes XML characters in xml.CharData values | |||
// returned by Decoder.Token. Thus, the internal Map values will contain escaped | |||
// values, and you do not need to set XMLEscapeChars for proper encoding. | |||
// | |||
// By default, the Map values have the non-escaped values returned by Decoder.Token. | |||
// XMLEscapeCharsDecoder(true) - or, XMLEscapeCharsDecoder() - will toggle escape | |||
// encoding 'on.' | |||
// | |||
// Note: if XMLEscapeCharDecoder(true) is call then XMLEscapeChars(false) is | |||
// called to prevent re-escaping the values on encoding using mv.Xml, etc. | |||
func XMLEscapeCharsDecoder(b ...bool) { | |||
if len(b) == 0 { | |||
xmlEscapeCharsDecoder = !xmlEscapeCharsDecoder | |||
} else { | |||
xmlEscapeCharsDecoder = b[0] | |||
} | |||
if xmlEscapeCharsDecoder == true && xmlEscapeChars == true { | |||
xmlEscapeChars = false | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
package mxj | |||
// Checks whether the path exists. If err != nil then 'false' is returned | |||
// along with the error encountered parsing either the "path" or "subkeys" | |||
// argument. | |||
func (mv Map) Exists(path string, subkeys ...string) (bool, error) { | |||
v, err := mv.ValuesForPath(path, subkeys...) | |||
return (err == nil && len(v) > 0), err | |||
} |
@@ -0,0 +1,287 @@ | |||
package mxj | |||
import ( | |||
"fmt" | |||
"io" | |||
"os" | |||
) | |||
type Maps []Map | |||
func NewMaps() Maps { | |||
return make(Maps, 0) | |||
} | |||
type MapRaw struct { | |||
M Map | |||
R []byte | |||
} | |||
// NewMapsFromXmlFile - creates an array from a file of JSON values. | |||
func NewMapsFromJsonFile(name string) (Maps, error) { | |||
fi, err := os.Stat(name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if !fi.Mode().IsRegular() { | |||
return nil, fmt.Errorf("file %s is not a regular file", name) | |||
} | |||
fh, err := os.Open(name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
defer fh.Close() | |||
am := make([]Map, 0) | |||
for { | |||
m, raw, err := NewMapJsonReaderRaw(fh) | |||
if err != nil && err != io.EOF { | |||
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(raw)) | |||
} | |||
if len(m) > 0 { | |||
am = append(am, m) | |||
} | |||
if err == io.EOF { | |||
break | |||
} | |||
} | |||
return am, nil | |||
} | |||
// ReadMapsFromJsonFileRaw - creates an array of MapRaw from a file of JSON values. | |||
func NewMapsFromJsonFileRaw(name string) ([]MapRaw, error) { | |||
fi, err := os.Stat(name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if !fi.Mode().IsRegular() { | |||
return nil, fmt.Errorf("file %s is not a regular file", name) | |||
} | |||
fh, err := os.Open(name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
defer fh.Close() | |||
am := make([]MapRaw, 0) | |||
for { | |||
mr := new(MapRaw) | |||
mr.M, mr.R, err = NewMapJsonReaderRaw(fh) | |||
if err != nil && err != io.EOF { | |||
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(mr.R)) | |||
} | |||
if len(mr.M) > 0 { | |||
am = append(am, *mr) | |||
} | |||
if err == io.EOF { | |||
break | |||
} | |||
} | |||
return am, nil | |||
} | |||
// NewMapsFromXmlFile - creates an array from a file of XML values. | |||
func NewMapsFromXmlFile(name string) (Maps, error) { | |||
fi, err := os.Stat(name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if !fi.Mode().IsRegular() { | |||
return nil, fmt.Errorf("file %s is not a regular file", name) | |||
} | |||
fh, err := os.Open(name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
defer fh.Close() | |||
am := make([]Map, 0) | |||
for { | |||
m, raw, err := NewMapXmlReaderRaw(fh) | |||
if err != nil && err != io.EOF { | |||
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(raw)) | |||
} | |||
if len(m) > 0 { | |||
am = append(am, m) | |||
} | |||
if err == io.EOF { | |||
break | |||
} | |||
} | |||
return am, nil | |||
} | |||
// NewMapsFromXmlFileRaw - creates an array of MapRaw from a file of XML values. | |||
// NOTE: the slice with the raw XML is clean with no extra capacity - unlike NewMapXmlReaderRaw(). | |||
// It is slow at parsing a file from disk and is intended for relatively small utility files. | |||
func NewMapsFromXmlFileRaw(name string) ([]MapRaw, error) { | |||
fi, err := os.Stat(name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if !fi.Mode().IsRegular() { | |||
return nil, fmt.Errorf("file %s is not a regular file", name) | |||
} | |||
fh, err := os.Open(name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
defer fh.Close() | |||
am := make([]MapRaw, 0) | |||
for { | |||
mr := new(MapRaw) | |||
mr.M, mr.R, err = NewMapXmlReaderRaw(fh) | |||
if err != nil && err != io.EOF { | |||
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(mr.R)) | |||
} | |||
if len(mr.M) > 0 { | |||
am = append(am, *mr) | |||
} | |||
if err == io.EOF { | |||
break | |||
} | |||
} | |||
return am, nil | |||
} | |||
// ------------------------ Maps writing ------------------------- | |||
// These are handy-dandy methods for dumping configuration data, etc. | |||
// JsonString - analogous to mv.Json() | |||
func (mvs Maps) JsonString(safeEncoding ...bool) (string, error) { | |||
var s string | |||
for _, v := range mvs { | |||
j, err := v.Json() | |||
if err != nil { | |||
return s, err | |||
} | |||
s += string(j) | |||
} | |||
return s, nil | |||
} | |||
// JsonStringIndent - analogous to mv.JsonIndent() | |||
func (mvs Maps) JsonStringIndent(prefix, indent string, safeEncoding ...bool) (string, error) { | |||
var s string | |||
var haveFirst bool | |||
for _, v := range mvs { | |||
j, err := v.JsonIndent(prefix, indent) | |||
if err != nil { | |||
return s, err | |||
} | |||
if haveFirst { | |||
s += "\n" | |||
} else { | |||
haveFirst = true | |||
} | |||
s += string(j) | |||
} | |||
return s, nil | |||
} | |||
// XmlString - analogous to mv.Xml() | |||
func (mvs Maps) XmlString() (string, error) { | |||
var s string | |||
for _, v := range mvs { | |||
x, err := v.Xml() | |||
if err != nil { | |||
return s, err | |||
} | |||
s += string(x) | |||
} | |||
return s, nil | |||
} | |||
// XmlStringIndent - analogous to mv.XmlIndent() | |||
func (mvs Maps) XmlStringIndent(prefix, indent string) (string, error) { | |||
var s string | |||
for _, v := range mvs { | |||
x, err := v.XmlIndent(prefix, indent) | |||
if err != nil { | |||
return s, err | |||
} | |||
s += string(x) | |||
} | |||
return s, nil | |||
} | |||
// JsonFile - write Maps to named file as JSON | |||
// Note: the file will be created, if necessary; if it exists it will be truncated. | |||
// If you need to append to a file, open it and use JsonWriter method. | |||
func (mvs Maps) JsonFile(file string, safeEncoding ...bool) error { | |||
var encoding bool | |||
if len(safeEncoding) == 1 { | |||
encoding = safeEncoding[0] | |||
} | |||
s, err := mvs.JsonString(encoding) | |||
if err != nil { | |||
return err | |||
} | |||
fh, err := os.Create(file) | |||
if err != nil { | |||
return err | |||
} | |||
defer fh.Close() | |||
fh.WriteString(s) | |||
return nil | |||
} | |||
// JsonFileIndent - write Maps to named file as pretty JSON | |||
// Note: the file will be created, if necessary; if it exists it will be truncated. | |||
// If you need to append to a file, open it and use JsonIndentWriter method. | |||
func (mvs Maps) JsonFileIndent(file, prefix, indent string, safeEncoding ...bool) error { | |||
var encoding bool | |||
if len(safeEncoding) == 1 { | |||
encoding = safeEncoding[0] | |||
} | |||
s, err := mvs.JsonStringIndent(prefix, indent, encoding) | |||
if err != nil { | |||
return err | |||
} | |||
fh, err := os.Create(file) | |||
if err != nil { | |||
return err | |||
} | |||
defer fh.Close() | |||
fh.WriteString(s) | |||
return nil | |||
} | |||
// XmlFile - write Maps to named file as XML | |||
// Note: the file will be created, if necessary; if it exists it will be truncated. | |||
// If you need to append to a file, open it and use XmlWriter method. | |||
func (mvs Maps) XmlFile(file string) error { | |||
s, err := mvs.XmlString() | |||
if err != nil { | |||
return err | |||
} | |||
fh, err := os.Create(file) | |||
if err != nil { | |||
return err | |||
} | |||
defer fh.Close() | |||
fh.WriteString(s) | |||
return nil | |||
} | |||
// XmlFileIndent - write Maps to named file as pretty XML | |||
// Note: the file will be created,if necessary; if it exists it will be truncated. | |||
// If you need to append to a file, open it and use XmlIndentWriter method. | |||
func (mvs Maps) XmlFileIndent(file, prefix, indent string) error { | |||
s, err := mvs.XmlStringIndent(prefix, indent) | |||
if err != nil { | |||
return err | |||
} | |||
fh, err := os.Create(file) | |||
if err != nil { | |||
return err | |||
} | |||
defer fh.Close() | |||
fh.WriteString(s) | |||
return nil | |||
} |
@@ -0,0 +1,2 @@ | |||
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" } | |||
{ "with":"some", "bad":JSON, "in":"it" } |
@@ -0,0 +1,9 @@ | |||
<doc> | |||
<some>test</some> | |||
<data>for files.go</data> | |||
</doc> | |||
<msg> | |||
<just>some</just> | |||
<another>doc</other> | |||
<for>test case</for> | |||
</msg> |
@@ -0,0 +1,2 @@ | |||
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" } | |||
{ "with":"just", "two":2, "JSON":"values", "true":true } |
@@ -0,0 +1,9 @@ | |||
<doc> | |||
<some>test</some> | |||
<data>for files.go</data> | |||
</doc> | |||
<msg> | |||
<just>some</just> | |||
<another>doc</another> | |||
<for>test case</for> | |||
</msg> |
@@ -0,0 +1 @@ | |||
{"a":"test","file":"for","files_test.go":"case","this":"is"}{"JSON":"values","true":true,"two":2,"with":"just"} |
@@ -0,0 +1 @@ | |||
<doc><data>for files.go</data><some>test</some></doc><msg><another>doc</another><for>test case</for><just>some</just></msg> |
@@ -0,0 +1,12 @@ | |||
{ | |||
"a": "test", | |||
"file": "for", | |||
"files_test.go": "case", | |||
"this": "is" | |||
} | |||
{ | |||
"JSON": "values", | |||
"true": true, | |||
"two": 2, | |||
"with": "just" | |||
} |
@@ -0,0 +1,8 @@ | |||
<doc> | |||
<data>for files.go</data> | |||
<some>test</some> | |||
</doc><msg> | |||
<another>doc</another> | |||
<for>test case</for> | |||
<just>some</just> | |||
</msg> |
@@ -0,0 +1,3 @@ | |||
module github.com/clbanning/mxj/v2 | |||
go 1.15 |
@@ -0,0 +1,35 @@ | |||
// gob.go - Encode/Decode a Map into a gob object. | |||
package mxj | |||
import ( | |||
"bytes" | |||
"encoding/gob" | |||
) | |||
// NewMapGob returns a Map value for a gob object that has been | |||
// encoded from a map[string]interface{} (or compatible type) value. | |||
// It is intended to provide symmetric handling of Maps that have | |||
// been encoded using mv.Gob. | |||
func NewMapGob(gobj []byte) (Map, error) { | |||
m := make(map[string]interface{}, 0) | |||
if len(gobj) == 0 { | |||
return m, nil | |||
} | |||
r := bytes.NewReader(gobj) | |||
dec := gob.NewDecoder(r) | |||
if err := dec.Decode(&m); err != nil { | |||
return m, err | |||
} | |||
return m, nil | |||
} | |||
// Gob returns a gob-encoded value for the Map 'mv'. | |||
func (mv Map) Gob() ([]byte, error) { | |||
var buf bytes.Buffer | |||
enc := gob.NewEncoder(&buf) | |||
if err := enc.Encode(map[string]interface{}(mv)); err != nil { | |||
return nil, err | |||
} | |||
return buf.Bytes(), nil | |||
} |
@@ -0,0 +1,323 @@ | |||
// Copyright 2012-2014 Charles Banning. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file | |||
package mxj | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"io" | |||
"time" | |||
) | |||
// ------------------------------ write JSON ----------------------- | |||
// Just a wrapper on json.Marshal. | |||
// If option safeEncoding is'true' then safe encoding of '<', '>' and '&' | |||
// is preserved. (see encoding/json#Marshal, encoding/json#Encode) | |||
func (mv Map) Json(safeEncoding ...bool) ([]byte, error) { | |||
var s bool | |||
if len(safeEncoding) == 1 { | |||
s = safeEncoding[0] | |||
} | |||
b, err := json.Marshal(mv) | |||
if !s { | |||
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1) | |||
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1) | |||
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1) | |||
} | |||
return b, err | |||
} | |||
// Just a wrapper on json.MarshalIndent. | |||
// If option safeEncoding is'true' then safe encoding of '<' , '>' and '&' | |||
// is preserved. (see encoding/json#Marshal, encoding/json#Encode) | |||
func (mv Map) JsonIndent(prefix, indent string, safeEncoding ...bool) ([]byte, error) { | |||
var s bool | |||
if len(safeEncoding) == 1 { | |||
s = safeEncoding[0] | |||
} | |||
b, err := json.MarshalIndent(mv, prefix, indent) | |||
if !s { | |||
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1) | |||
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1) | |||
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1) | |||
} | |||
return b, err | |||
} | |||
// The following implementation is provided for symmetry with NewMapJsonReader[Raw] | |||
// The names will also provide a key for the number of return arguments. | |||
// Writes the Map as JSON on the Writer. | |||
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved. | |||
func (mv Map) JsonWriter(jsonWriter io.Writer, safeEncoding ...bool) error { | |||
b, err := mv.Json(safeEncoding...) | |||
if err != nil { | |||
return err | |||
} | |||
_, err = jsonWriter.Write(b) | |||
return err | |||
} | |||
// Writes the Map as JSON on the Writer. []byte is the raw JSON that was written. | |||
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved. | |||
func (mv Map) JsonWriterRaw(jsonWriter io.Writer, safeEncoding ...bool) ([]byte, error) { | |||
b, err := mv.Json(safeEncoding...) | |||
if err != nil { | |||
return b, err | |||
} | |||
_, err = jsonWriter.Write(b) | |||
return b, err | |||
} | |||
// Writes the Map as pretty JSON on the Writer. | |||
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved. | |||
func (mv Map) JsonIndentWriter(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) error { | |||
b, err := mv.JsonIndent(prefix, indent, safeEncoding...) | |||
if err != nil { | |||
return err | |||
} | |||
_, err = jsonWriter.Write(b) | |||
return err | |||
} | |||
// Writes the Map as pretty JSON on the Writer. []byte is the raw JSON that was written. | |||
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved. | |||
func (mv Map) JsonIndentWriterRaw(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) ([]byte, error) { | |||
b, err := mv.JsonIndent(prefix, indent, safeEncoding...) | |||
if err != nil { | |||
return b, err | |||
} | |||
_, err = jsonWriter.Write(b) | |||
return b, err | |||
} | |||
// --------------------------- read JSON ----------------------------- | |||
// Decode numericvalues as json.Number type Map values - see encoding/json#Number. | |||
// NOTE: this is for decoding JSON into a Map with NewMapJson(), NewMapJsonReader(), | |||
// etc.; it does not affect NewMapXml(), etc. The XML encoders mv.Xml() and mv.XmlIndent() | |||
// do recognize json.Number types; a JSON object can be decoded to a Map with json.Number | |||
// value types and the resulting Map can be correctly encoded into a XML object. | |||
var JsonUseNumber bool | |||
// Just a wrapper on json.Unmarshal | |||
// Converting JSON to XML is a simple as: | |||
// ... | |||
// mapVal, merr := mxj.NewMapJson(jsonVal) | |||
// if merr != nil { | |||
// // handle error | |||
// } | |||
// xmlVal, xerr := mapVal.Xml() | |||
// if xerr != nil { | |||
// // handle error | |||
// } | |||
// NOTE: as a special case, passing a list, e.g., [{"some-null-value":"", "a-non-null-value":"bar"}], | |||
// will be interpreted as having the root key 'object' prepended - {"object":[ ... ]} - to unmarshal to a Map. | |||
// See mxj/j2x/j2x_test.go. | |||
func NewMapJson(jsonVal []byte) (Map, error) { | |||
// empty or nil begets empty | |||
if len(jsonVal) == 0 { | |||
m := make(map[string]interface{}, 0) | |||
return m, nil | |||
} | |||
// handle a goofy case ... | |||
if jsonVal[0] == '[' { | |||
jsonVal = []byte(`{"object":` + string(jsonVal) + `}`) | |||
} | |||
m := make(map[string]interface{}) | |||
// err := json.Unmarshal(jsonVal, &m) | |||
buf := bytes.NewReader(jsonVal) | |||
dec := json.NewDecoder(buf) | |||
if JsonUseNumber { | |||
dec.UseNumber() | |||
} | |||
err := dec.Decode(&m) | |||
return m, err | |||
} | |||
// Retrieve a Map value from an io.Reader. | |||
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an | |||
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte | |||
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal | |||
// a JSON object. | |||
func NewMapJsonReader(jsonReader io.Reader) (Map, error) { | |||
jb, err := getJson(jsonReader) | |||
if err != nil || len(*jb) == 0 { | |||
return nil, err | |||
} | |||
// Unmarshal the 'presumed' JSON string | |||
return NewMapJson(*jb) | |||
} | |||
// Retrieve a Map value and raw JSON - []byte - from an io.Reader. | |||
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an | |||
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte | |||
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal | |||
// a JSON object and retrieve the raw JSON in a single call. | |||
func NewMapJsonReaderRaw(jsonReader io.Reader) (Map, []byte, error) { | |||
jb, err := getJson(jsonReader) | |||
if err != nil || len(*jb) == 0 { | |||
return nil, *jb, err | |||
} | |||
// Unmarshal the 'presumed' JSON string | |||
m, merr := NewMapJson(*jb) | |||
return m, *jb, merr | |||
} | |||
// Pull the next JSON string off the stream: just read from first '{' to its closing '}'. | |||
// Returning a pointer to the slice saves 16 bytes - maybe unnecessary, but internal to package. | |||
func getJson(rdr io.Reader) (*[]byte, error) { | |||
bval := make([]byte, 1) | |||
jb := make([]byte, 0) | |||
var inQuote, inJson bool | |||
var parenCnt int | |||
var previous byte | |||
// scan the input for a matched set of {...} | |||
// json.Unmarshal will handle syntax checking. | |||
for { | |||
_, err := rdr.Read(bval) | |||
if err != nil { | |||
if err == io.EOF && inJson && parenCnt > 0 { | |||
return &jb, fmt.Errorf("no closing } for JSON string: %s", string(jb)) | |||
} | |||
return &jb, err | |||
} | |||
switch bval[0] { | |||
case '{': | |||
if !inQuote { | |||
parenCnt++ | |||
inJson = true | |||
} | |||
case '}': | |||
if !inQuote { | |||
parenCnt-- | |||
} | |||
if parenCnt < 0 { | |||
return nil, fmt.Errorf("closing } without opening {: %s", string(jb)) | |||
} | |||
case '"': | |||
if inQuote { | |||
if previous == '\\' { | |||
break | |||
} | |||
inQuote = false | |||
} else { | |||
inQuote = true | |||
} | |||
case '\n', '\r', '\t', ' ': | |||
if !inQuote { | |||
continue | |||
} | |||
} | |||
if inJson { | |||
jb = append(jb, bval[0]) | |||
if parenCnt == 0 { | |||
break | |||
} | |||
} | |||
previous = bval[0] | |||
} | |||
return &jb, nil | |||
} | |||
// ------------------------------- JSON Reader handler via Map values ----------------------- | |||
// Default poll delay to keep Handler from spinning on an open stream | |||
// like sitting on os.Stdin waiting for imput. | |||
var jhandlerPollInterval = time.Duration(1e6) | |||
// While unnecessary, we make HandleJsonReader() have the same signature as HandleXmlReader(). | |||
// This avoids treating one or other as a special case and discussing the underlying stdlib logic. | |||
// Bulk process JSON using handlers that process a Map value. | |||
// 'rdr' is an io.Reader for the JSON (stream). | |||
// 'mapHandler' is the Map processing handler. Return of 'false' stops io.Reader processing. | |||
// 'errHandler' is the error processor. Return of 'false' stops io.Reader processing and returns the error. | |||
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized. | |||
// This means that you can stop reading the file on error or after processing a particular message. | |||
// To have reading and handling run concurrently, pass argument to a go routine in handler and return 'true'. | |||
func HandleJsonReader(jsonReader io.Reader, mapHandler func(Map) bool, errHandler func(error) bool) error { | |||
var n int | |||
for { | |||
m, merr := NewMapJsonReader(jsonReader) | |||
n++ | |||
// handle error condition with errhandler | |||
if merr != nil && merr != io.EOF { | |||
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error()) | |||
if ok := errHandler(merr); !ok { | |||
// caused reader termination | |||
return merr | |||
} | |||
continue | |||
} | |||
// pass to maphandler | |||
if len(m) != 0 { | |||
if ok := mapHandler(m); !ok { | |||
break | |||
} | |||
} else if merr != io.EOF { | |||
<-time.After(jhandlerPollInterval) | |||
} | |||
if merr == io.EOF { | |||
break | |||
} | |||
} | |||
return nil | |||
} | |||
// Bulk process JSON using handlers that process a Map value and the raw JSON. | |||
// 'rdr' is an io.Reader for the JSON (stream). | |||
// 'mapHandler' is the Map and raw JSON - []byte - processor. Return of 'false' stops io.Reader processing. | |||
// 'errHandler' is the error and raw JSON processor. Return of 'false' stops io.Reader processing and returns the error. | |||
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized. | |||
// This means that you can stop reading the file on error or after processing a particular message. | |||
// To have reading and handling run concurrently, pass argument(s) to a go routine in handler and return 'true'. | |||
func HandleJsonReaderRaw(jsonReader io.Reader, mapHandler func(Map, []byte) bool, errHandler func(error, []byte) bool) error { | |||
var n int | |||
for { | |||
m, raw, merr := NewMapJsonReaderRaw(jsonReader) | |||
n++ | |||
// handle error condition with errhandler | |||
if merr != nil && merr != io.EOF { | |||
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error()) | |||
if ok := errHandler(merr, raw); !ok { | |||
// caused reader termination | |||
return merr | |||
} | |||
continue | |||
} | |||
// pass to maphandler | |||
if len(m) != 0 { | |||
if ok := mapHandler(m, raw); !ok { | |||
break | |||
} | |||
} else if merr != io.EOF { | |||
<-time.After(jhandlerPollInterval) | |||
} | |||
if merr == io.EOF { | |||
break | |||
} | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,668 @@ | |||
// Copyright 2012-2014 Charles Banning. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file | |||
// keyvalues.go: Extract values from an arbitrary XML doc. Tag path can include wildcard characters. | |||
package mxj | |||
import ( | |||
"errors" | |||
"fmt" | |||
"strconv" | |||
"strings" | |||
) | |||
// ----------------------------- get everything FOR a single key ------------------------- | |||
const ( | |||
minArraySize = 32 | |||
) | |||
var defaultArraySize int = minArraySize | |||
// SetArraySize adjust the buffers for expected number of values to return from ValuesForKey() and ValuesForPath(). | |||
// This can have the effect of significantly reducing memory allocation-copy functions for large data sets. | |||
// Returns the initial buffer size. | |||
func SetArraySize(size int) int { | |||
if size > minArraySize { | |||
defaultArraySize = size | |||
} else { | |||
defaultArraySize = minArraySize | |||
} | |||
return defaultArraySize | |||
} | |||
// ValuesForKey return all values in Map, 'mv', associated with a 'key'. If len(returned_values) == 0, then no match. | |||
// On error, the returned slice is 'nil'. NOTE: 'key' can be wildcard, "*". | |||
// 'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list. | |||
// - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them. | |||
// - For attributes prefix the label with the attribute prefix character, by default a | |||
// hyphen, '-', e.g., "-seq:3". (See SetAttrPrefix function.) | |||
// - If the 'key' refers to a list, then "key:value" could select a list member of the list. | |||
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value. | |||
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an | |||
// exclusion critera - e.g., "!author:William T. Gaddis". | |||
// - If val contains ":" symbol, use SetFieldSeparator to a unused symbol, perhaps "|". | |||
func (mv Map) ValuesForKey(key string, subkeys ...string) ([]interface{}, error) { | |||
m := map[string]interface{}(mv) | |||
var subKeyMap map[string]interface{} | |||
if len(subkeys) > 0 { | |||
var err error | |||
subKeyMap, err = getSubKeyMap(subkeys...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
ret := make([]interface{}, 0, defaultArraySize) | |||
var cnt int | |||
hasKey(m, key, &ret, &cnt, subKeyMap) | |||
return ret[:cnt], nil | |||
} | |||
var KeyNotExistError = errors.New("Key does not exist") | |||
// ValueForKey is a wrapper on ValuesForKey. It returns the first member of []interface{}, if any. | |||
// If there is no value, "nil, nil" is returned. | |||
func (mv Map) ValueForKey(key string, subkeys ...string) (interface{}, error) { | |||
vals, err := mv.ValuesForKey(key, subkeys...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if len(vals) == 0 { | |||
return nil, KeyNotExistError | |||
} | |||
return vals[0], nil | |||
} | |||
// hasKey - if the map 'key' exists append it to array | |||
// if it doesn't do nothing except scan array and map values | |||
func hasKey(iv interface{}, key string, ret *[]interface{}, cnt *int, subkeys map[string]interface{}) { | |||
// func hasKey(iv interface{}, key string, ret *[]interface{}, subkeys map[string]interface{}) { | |||
switch iv.(type) { | |||
case map[string]interface{}: | |||
vv := iv.(map[string]interface{}) | |||
// see if the current value is of interest | |||
if v, ok := vv[key]; ok { | |||
switch v.(type) { | |||
case map[string]interface{}: | |||
if hasSubKeys(v, subkeys) { | |||
*ret = append(*ret, v) | |||
*cnt++ | |||
} | |||
case []interface{}: | |||
for _, av := range v.([]interface{}) { | |||
if hasSubKeys(av, subkeys) { | |||
*ret = append(*ret, av) | |||
*cnt++ | |||
} | |||
} | |||
default: | |||
if len(subkeys) == 0 { | |||
*ret = append(*ret, v) | |||
*cnt++ | |||
} | |||
} | |||
} | |||
// wildcard case | |||
if key == "*" { | |||
for _, v := range vv { | |||
switch v.(type) { | |||
case map[string]interface{}: | |||
if hasSubKeys(v, subkeys) { | |||
*ret = append(*ret, v) | |||
*cnt++ | |||
} | |||
case []interface{}: | |||
for _, av := range v.([]interface{}) { | |||
if hasSubKeys(av, subkeys) { | |||
*ret = append(*ret, av) | |||
*cnt++ | |||
} | |||
} | |||
default: | |||
if len(subkeys) == 0 { | |||
*ret = append(*ret, v) | |||
*cnt++ | |||
} | |||
} | |||
} | |||
} | |||
// scan the rest | |||
for _, v := range vv { | |||
hasKey(v, key, ret, cnt, subkeys) | |||
} | |||
case []interface{}: | |||
for _, v := range iv.([]interface{}) { | |||
hasKey(v, key, ret, cnt, subkeys) | |||
} | |||
} | |||
} | |||
// ----------------------- get everything for a node in the Map --------------------------- | |||
// Allow indexed arrays in "path" specification. (Request from Abhijit Kadam - abhijitk100@gmail.com.) | |||
// 2014.04.28 - implementation note. | |||
// Implemented as a wrapper of (old)ValuesForPath() because we need look-ahead logic to handle expansion | |||
// of wildcards and unindexed arrays. Embedding such logic into valuesForKeyPath() would have made the | |||
// code much more complicated; this wrapper is straightforward, easy to debug, and doesn't add significant overhead. | |||
// ValuesForPatb retrieves all values for a path from the Map. If len(returned_values) == 0, then no match. | |||
// On error, the returned array is 'nil'. | |||
// 'path' is a dot-separated path of key values. | |||
// - If a node in the path is '*', then everything beyond is walked. | |||
// - 'path' can contain indexed array references, such as, "*.data[1]" and "msgs[2].data[0].field" - | |||
// even "*[2].*[0].field". | |||
// 'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list. | |||
// - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them. | |||
// - For attributes prefix the label with the attribute prefix character, by default a | |||
// hyphen, '-', e.g., "-seq:3". (See SetAttrPrefix function.) | |||
// - If the 'path' refers to a list, then "tag:value" would return member of the list. | |||
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value. | |||
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an | |||
// exclusion critera - e.g., "!author:William T. Gaddis". | |||
// - If val contains ":" symbol, use SetFieldSeparator to a unused symbol, perhaps "|". | |||
func (mv Map) ValuesForPath(path string, subkeys ...string) ([]interface{}, error) { | |||
// If there are no array indexes in path, use legacy ValuesForPath() logic. | |||
if strings.Index(path, "[") < 0 { | |||
return mv.oldValuesForPath(path, subkeys...) | |||
} | |||
var subKeyMap map[string]interface{} | |||
if len(subkeys) > 0 { | |||
var err error | |||
subKeyMap, err = getSubKeyMap(subkeys...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
keys, kerr := parsePath(path) | |||
if kerr != nil { | |||
return nil, kerr | |||
} | |||
vals, verr := valuesForArray(keys, mv) | |||
if verr != nil { | |||
return nil, verr // Vals may be nil, but return empty array. | |||
} | |||
// Need to handle subkeys ... only return members of vals that satisfy conditions. | |||
retvals := make([]interface{}, 0) | |||
for _, v := range vals { | |||
if hasSubKeys(v, subKeyMap) { | |||
retvals = append(retvals, v) | |||
} | |||
} | |||
return retvals, nil | |||
} | |||
func valuesForArray(keys []*key, m Map) ([]interface{}, error) { | |||
var tmppath string | |||
var haveFirst bool | |||
var vals []interface{} | |||
var verr error | |||
lastkey := len(keys) - 1 | |||
for i := 0; i <= lastkey; i++ { | |||
if !haveFirst { | |||
tmppath = keys[i].name | |||
haveFirst = true | |||
} else { | |||
tmppath += "." + keys[i].name | |||
} | |||
// Look-ahead: explode wildcards and unindexed arrays. | |||
// Need to handle un-indexed list recursively: | |||
// e.g., path is "stuff.data[0]" rather than "stuff[0].data[0]". | |||
// Need to treat it as "stuff[0].data[0]", "stuff[1].data[0]", ... | |||
if !keys[i].isArray && i < lastkey && keys[i+1].isArray { | |||
// Can't pass subkeys because we may not be at literal end of path. | |||
vv, vverr := m.oldValuesForPath(tmppath) | |||
if vverr != nil { | |||
return nil, vverr | |||
} | |||
for _, v := range vv { | |||
// See if we can walk the value. | |||
am, ok := v.(map[string]interface{}) | |||
if !ok { | |||
continue | |||
} | |||
// Work the backend. | |||
nvals, nvalserr := valuesForArray(keys[i+1:], Map(am)) | |||
if nvalserr != nil { | |||
return nil, nvalserr | |||
} | |||
vals = append(vals, nvals...) | |||
} | |||
break // have recursed the whole path - return | |||
} | |||
if keys[i].isArray || i == lastkey { | |||
// Don't pass subkeys because may not be at literal end of path. | |||
vals, verr = m.oldValuesForPath(tmppath) | |||
} else { | |||
continue | |||
} | |||
if verr != nil { | |||
return nil, verr | |||
} | |||
if i == lastkey && !keys[i].isArray { | |||
break | |||
} | |||
// Now we're looking at an array - supposedly. | |||
// Is index in range of vals? | |||
if len(vals) <= keys[i].position { | |||
vals = nil | |||
break | |||
} | |||
// Return the array member of interest, if at end of path. | |||
if i == lastkey { | |||
vals = vals[keys[i].position:(keys[i].position + 1)] | |||
break | |||
} | |||
// Extract the array member of interest. | |||
am := vals[keys[i].position:(keys[i].position + 1)] | |||
// must be a map[string]interface{} value so we can keep walking the path | |||
amm, ok := am[0].(map[string]interface{}) | |||
if !ok { | |||
vals = nil | |||
break | |||
} | |||
m = Map(amm) | |||
haveFirst = false | |||
} | |||
return vals, nil | |||
} | |||
type key struct { | |||
name string | |||
isArray bool | |||
position int | |||
} | |||
func parsePath(s string) ([]*key, error) { | |||
keys := strings.Split(s, ".") | |||
ret := make([]*key, 0) | |||
for i := 0; i < len(keys); i++ { | |||
if keys[i] == "" { | |||
continue | |||
} | |||
newkey := new(key) | |||
if strings.Index(keys[i], "[") < 0 { | |||
newkey.name = keys[i] | |||
ret = append(ret, newkey) | |||
continue | |||
} | |||
p := strings.Split(keys[i], "[") | |||
newkey.name = p[0] | |||
p = strings.Split(p[1], "]") | |||
if p[0] == "" { // no right bracket | |||
return nil, fmt.Errorf("no right bracket on key index: %s", keys[i]) | |||
} | |||
// convert p[0] to a int value | |||
pos, nerr := strconv.ParseInt(p[0], 10, 32) | |||
if nerr != nil { | |||
return nil, fmt.Errorf("cannot convert index to int value: %s", p[0]) | |||
} | |||
newkey.position = int(pos) | |||
newkey.isArray = true | |||
ret = append(ret, newkey) | |||
} | |||
return ret, nil | |||
} | |||
// legacy ValuesForPath() - now wrapped to handle special case of indexed arrays in 'path'. | |||
func (mv Map) oldValuesForPath(path string, subkeys ...string) ([]interface{}, error) { | |||
m := map[string]interface{}(mv) | |||
var subKeyMap map[string]interface{} | |||
if len(subkeys) > 0 { | |||
var err error | |||
subKeyMap, err = getSubKeyMap(subkeys...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
keys := strings.Split(path, ".") | |||
if keys[len(keys)-1] == "" { | |||
keys = keys[:len(keys)-1] | |||
} | |||
ivals := make([]interface{}, 0, defaultArraySize) | |||
var cnt int | |||
valuesForKeyPath(&ivals, &cnt, m, keys, subKeyMap) | |||
return ivals[:cnt], nil | |||
} | |||
func valuesForKeyPath(ret *[]interface{}, cnt *int, m interface{}, keys []string, subkeys map[string]interface{}) { | |||
lenKeys := len(keys) | |||
// load 'm' values into 'ret' | |||
// expand any lists | |||
if lenKeys == 0 { | |||
switch m.(type) { | |||
case map[string]interface{}: | |||
if subkeys != nil { | |||
if ok := hasSubKeys(m, subkeys); !ok { | |||
return | |||
} | |||
} | |||
*ret = append(*ret, m) | |||
*cnt++ | |||
case []interface{}: | |||
for i, v := range m.([]interface{}) { | |||
if subkeys != nil { | |||
if ok := hasSubKeys(v, subkeys); !ok { | |||
continue // only load list members with subkeys | |||
} | |||
} | |||
*ret = append(*ret, (m.([]interface{}))[i]) | |||
*cnt++ | |||
} | |||
default: | |||
if subkeys != nil { | |||
return // must be map[string]interface{} if there are subkeys | |||
} | |||
*ret = append(*ret, m) | |||
*cnt++ | |||
} | |||
return | |||
} | |||
// key of interest | |||
key := keys[0] | |||
switch key { | |||
case "*": // wildcard - scan all values | |||
switch m.(type) { | |||
case map[string]interface{}: | |||
for _, v := range m.(map[string]interface{}) { | |||
// valuesForKeyPath(ret, v, keys[1:], subkeys) | |||
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys) | |||
} | |||
case []interface{}: | |||
for _, v := range m.([]interface{}) { | |||
switch v.(type) { | |||
// flatten out a list of maps - keys are processed | |||
case map[string]interface{}: | |||
for _, vv := range v.(map[string]interface{}) { | |||
// valuesForKeyPath(ret, vv, keys[1:], subkeys) | |||
valuesForKeyPath(ret, cnt, vv, keys[1:], subkeys) | |||
} | |||
default: | |||
// valuesForKeyPath(ret, v, keys[1:], subkeys) | |||
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys) | |||
} | |||
} | |||
} | |||
default: // key - must be map[string]interface{} | |||
switch m.(type) { | |||
case map[string]interface{}: | |||
if v, ok := m.(map[string]interface{})[key]; ok { | |||
// valuesForKeyPath(ret, v, keys[1:], subkeys) | |||
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys) | |||
} | |||
case []interface{}: // may be buried in list | |||
for _, v := range m.([]interface{}) { | |||
switch v.(type) { | |||
case map[string]interface{}: | |||
if vv, ok := v.(map[string]interface{})[key]; ok { | |||
// valuesForKeyPath(ret, vv, keys[1:], subkeys) | |||
valuesForKeyPath(ret, cnt, vv, keys[1:], subkeys) | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
// hasSubKeys() - interface{} equality works for string, float64, bool | |||
// 'v' must be a map[string]interface{} value to have subkeys | |||
// 'a' can have k:v pairs with v.(string) == "*", which is treated like a wildcard. | |||
func hasSubKeys(v interface{}, subkeys map[string]interface{}) bool { | |||
if len(subkeys) == 0 { | |||
return true | |||
} | |||
switch v.(type) { | |||
case map[string]interface{}: | |||
// do all subKey name:value pairs match? | |||
mv := v.(map[string]interface{}) | |||
for skey, sval := range subkeys { | |||
isNotKey := false | |||
if skey[:1] == "!" { // a NOT-key | |||
skey = skey[1:] | |||
isNotKey = true | |||
} | |||
vv, ok := mv[skey] | |||
if !ok { // key doesn't exist | |||
if isNotKey { // key not there, but that's what we want | |||
if kv, ok := sval.(string); ok && kv == "*" { | |||
continue | |||
} | |||
} | |||
return false | |||
} | |||
// wildcard check | |||
if kv, ok := sval.(string); ok && kv == "*" { | |||
if isNotKey { // key is there, and we don't want it | |||
return false | |||
} | |||
continue | |||
} | |||
switch sval.(type) { | |||
case string: | |||
if s, ok := vv.(string); ok && s == sval.(string) { | |||
if isNotKey { | |||
return false | |||
} | |||
continue | |||
} | |||
case bool: | |||
if b, ok := vv.(bool); ok && b == sval.(bool) { | |||
if isNotKey { | |||
return false | |||
} | |||
continue | |||
} | |||
case float64: | |||
if f, ok := vv.(float64); ok && f == sval.(float64) { | |||
if isNotKey { | |||
return false | |||
} | |||
continue | |||
} | |||
} | |||
// key there but didn't match subkey value | |||
if isNotKey { // that's what we want | |||
continue | |||
} | |||
return false | |||
} | |||
// all subkeys matched | |||
return true | |||
} | |||
// not a map[string]interface{} value, can't have subkeys | |||
return false | |||
} | |||
// Generate map of key:value entries as map[string]string. | |||
// 'kv' arguments are "name:value" pairs: attribute keys are designated with prepended hyphen, '-'. | |||
// If len(kv) == 0, the return is (nil, nil). | |||
func getSubKeyMap(kv ...string) (map[string]interface{}, error) { | |||
if len(kv) == 0 { | |||
return nil, nil | |||
} | |||
m := make(map[string]interface{}, 0) | |||
for _, v := range kv { | |||
vv := strings.Split(v, fieldSep) | |||
switch len(vv) { | |||
case 2: | |||
m[vv[0]] = interface{}(vv[1]) | |||
case 3: | |||
switch vv[2] { | |||
case "string", "char", "text": | |||
m[vv[0]] = interface{}(vv[1]) | |||
case "bool", "boolean": | |||
// ParseBool treats "1"==true & "0"==false | |||
b, err := strconv.ParseBool(vv[1]) | |||
if err != nil { | |||
return nil, fmt.Errorf("can't convert subkey value to bool: %s", vv[1]) | |||
} | |||
m[vv[0]] = interface{}(b) | |||
case "float", "float64", "num", "number", "numeric": | |||
f, err := strconv.ParseFloat(vv[1], 64) | |||
if err != nil { | |||
return nil, fmt.Errorf("can't convert subkey value to float: %s", vv[1]) | |||
} | |||
m[vv[0]] = interface{}(f) | |||
default: | |||
return nil, fmt.Errorf("unknown subkey conversion spec: %s", v) | |||
} | |||
default: | |||
return nil, fmt.Errorf("unknown subkey spec: %s", v) | |||
} | |||
} | |||
return m, nil | |||
} | |||
// ------------------------------- END of valuesFor ... ---------------------------- | |||
// ----------------------- locate where a key value is in the tree ------------------- | |||
//----------------------------- find all paths to a key -------------------------------- | |||
// PathsForKey returns all paths through Map, 'mv', (in dot-notation) that terminate with the specified key. | |||
// Results can be used with ValuesForPath. | |||
func (mv Map) PathsForKey(key string) []string { | |||
m := map[string]interface{}(mv) | |||
breadbasket := make(map[string]bool, 0) | |||
breadcrumbs := "" | |||
hasKeyPath(breadcrumbs, m, key, breadbasket) | |||
if len(breadbasket) == 0 { | |||
return nil | |||
} | |||
// unpack map keys to return | |||
res := make([]string, len(breadbasket)) | |||
var i int | |||
for k := range breadbasket { | |||
res[i] = k | |||
i++ | |||
} | |||
return res | |||
} | |||
// PathForKeyShortest extracts the shortest path from all possible paths - from PathsForKey() - in Map, 'mv'.. | |||
// Paths are strings using dot-notation. | |||
func (mv Map) PathForKeyShortest(key string) string { | |||
paths := mv.PathsForKey(key) | |||
lp := len(paths) | |||
if lp == 0 { | |||
return "" | |||
} | |||
if lp == 1 { | |||
return paths[0] | |||
} | |||
shortest := paths[0] | |||
shortestLen := len(strings.Split(shortest, ".")) | |||
for i := 1; i < len(paths); i++ { | |||
vlen := len(strings.Split(paths[i], ".")) | |||
if vlen < shortestLen { | |||
shortest = paths[i] | |||
shortestLen = vlen | |||
} | |||
} | |||
return shortest | |||
} | |||
// hasKeyPath - if the map 'key' exists append it to KeyPath.path and increment KeyPath.depth | |||
// This is really just a breadcrumber that saves all trails that hit the prescribed 'key'. | |||
func hasKeyPath(crumbs string, iv interface{}, key string, basket map[string]bool) { | |||
switch iv.(type) { | |||
case map[string]interface{}: | |||
vv := iv.(map[string]interface{}) | |||
if _, ok := vv[key]; ok { | |||
// create a new breadcrumb, intialized with the one we have | |||
var nbc string | |||
if crumbs == "" { | |||
nbc = key | |||
} else { | |||
nbc = crumbs + "." + key | |||
} | |||
basket[nbc] = true | |||
} | |||
// walk on down the path, key could occur again at deeper node | |||
for k, v := range vv { | |||
// create a new breadcrumb, intialized with the one we have | |||
var nbc string | |||
if crumbs == "" { | |||
nbc = k | |||
} else { | |||
nbc = crumbs + "." + k | |||
} | |||
hasKeyPath(nbc, v, key, basket) | |||
} | |||
case []interface{}: | |||
// crumb-trail doesn't change, pass it on | |||
for _, v := range iv.([]interface{}) { | |||
hasKeyPath(crumbs, v, key, basket) | |||
} | |||
} | |||
} | |||
var PathNotExistError = errors.New("Path does not exist") | |||
// ValueForPath wraps ValuesFor Path and returns the first value returned. | |||
// If no value is found it returns 'nil' and PathNotExistError. | |||
func (mv Map) ValueForPath(path string) (interface{}, error) { | |||
vals, err := mv.ValuesForPath(path) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if len(vals) == 0 { | |||
return nil, PathNotExistError | |||
} | |||
return vals[0], nil | |||
} | |||
// ValuesForPathString returns the first found value for the path as a string. | |||
func (mv Map) ValueForPathString(path string) (string, error) { | |||
vals, err := mv.ValuesForPath(path) | |||
if err != nil { | |||
return "", err | |||
} | |||
if len(vals) == 0 { | |||
return "", errors.New("ValueForPath: path not found") | |||
} | |||
val := vals[0] | |||
return fmt.Sprintf("%v", val), nil | |||
} | |||
// ValueOrEmptyForPathString returns the first found value for the path as a string. | |||
// If the path is not found then it returns an empty string. | |||
func (mv Map) ValueOrEmptyForPathString(path string) string { | |||
str, _ := mv.ValueForPathString(path) | |||
return str | |||
} |
@@ -0,0 +1,112 @@ | |||
package mxj | |||
// leafnode.go - return leaf nodes with paths and values for the Map | |||
// inspired by: https://groups.google.com/forum/#!topic/golang-nuts/3JhuVKRuBbw | |||
import ( | |||
"strconv" | |||
"strings" | |||
) | |||
const ( | |||
NoAttributes = true // suppress LeafNode values that are attributes | |||
) | |||
// LeafNode - a terminal path value in a Map. | |||
// For XML Map values it represents an attribute or simple element value - of type | |||
// string unless Map was created using Cast flag. For JSON Map values it represents | |||
// a string, numeric, boolean, or null value. | |||
type LeafNode struct { | |||
Path string // a dot-notation representation of the path with array subscripting | |||
Value interface{} // the value at the path termination | |||
} | |||
// LeafNodes - returns an array of all LeafNode values for the Map. | |||
// The option no_attr argument suppresses attribute values (keys with prepended hyphen, '-') | |||
// as well as the "#text" key for the associated simple element value. | |||
// | |||
// PrependAttrWithHypen(false) will result in attributes having .attr-name as | |||
// terminal node in 'path' while the path for the element value, itself, will be | |||
// the base path w/o "#text". | |||
// | |||
// LeafUseDotNotation(true) causes list members to be identified using ".N" syntax | |||
// rather than "[N]" syntax. | |||
func (mv Map) LeafNodes(no_attr ...bool) []LeafNode { | |||
var a bool | |||
if len(no_attr) == 1 { | |||
a = no_attr[0] | |||
} | |||
l := make([]LeafNode, 0) | |||
getLeafNodes("", "", map[string]interface{}(mv), &l, a) | |||
return l | |||
} | |||
func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) { | |||
// if stripping attributes, then also strip "#text" key | |||
if !noattr || node != "#text" { | |||
if path != "" && node[:1] != "[" { | |||
path += "." | |||
} | |||
path += node | |||
} | |||
switch mv.(type) { | |||
case map[string]interface{}: | |||
for k, v := range mv.(map[string]interface{}) { | |||
// if noattr && k[:1] == "-" { | |||
if noattr && len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 { | |||
continue | |||
} | |||
getLeafNodes(path, k, v, l, noattr) | |||
} | |||
case []interface{}: | |||
for i, v := range mv.([]interface{}) { | |||
if useDotNotation { | |||
getLeafNodes(path, strconv.Itoa(i), v, l, noattr) | |||
} else { | |||
getLeafNodes(path, "["+strconv.Itoa(i)+"]", v, l, noattr) | |||
} | |||
} | |||
default: | |||
// can't walk any further, so create leaf | |||
n := LeafNode{path, mv} | |||
*l = append(*l, n) | |||
} | |||
} | |||
// LeafPaths - all paths that terminate in LeafNode values. | |||
func (mv Map) LeafPaths(no_attr ...bool) []string { | |||
ln := mv.LeafNodes() | |||
ss := make([]string, len(ln)) | |||
for i := 0; i < len(ln); i++ { | |||
ss[i] = ln[i].Path | |||
} | |||
return ss | |||
} | |||
// LeafValues - all terminal values in the Map. | |||
func (mv Map) LeafValues(no_attr ...bool) []interface{} { | |||
ln := mv.LeafNodes() | |||
vv := make([]interface{}, len(ln)) | |||
for i := 0; i < len(ln); i++ { | |||
vv[i] = ln[i].Value | |||
} | |||
return vv | |||
} | |||
// ====================== utilities ====================== | |||
// https://groups.google.com/forum/#!topic/golang-nuts/pj0C5IrZk4I | |||
var useDotNotation bool | |||
// LeafUseDotNotation sets a flag that list members in LeafNode paths | |||
// should be identified using ".N" syntax rather than the default "[N]" | |||
// syntax. Calling LeafUseDotNotation with no arguments toggles the | |||
// flag on/off; otherwise, the argument sets the flag value 'true'/'false'. | |||
func LeafUseDotNotation(b ...bool) { | |||
if len(b) == 0 { | |||
useDotNotation = !useDotNotation | |||
return | |||
} | |||
useDotNotation = b[0] | |||
} |
@@ -0,0 +1,86 @@ | |||
// Copyright 2016 Charles Banning. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file | |||
// misc.go - mimic functions (+others) called out in: | |||
// https://groups.google.com/forum/#!topic/golang-nuts/jm_aGsJNbdQ | |||
// Primarily these methods let you retrive XML structure information. | |||
package mxj | |||
import ( | |||
"fmt" | |||
"sort" | |||
"strings" | |||
) | |||
// Return the root element of the Map. If there is not a single key in Map, | |||
// then an error is returned. | |||
func (mv Map) Root() (string, error) { | |||
mm := map[string]interface{}(mv) | |||
if len(mm) != 1 { | |||
return "", fmt.Errorf("Map does not have singleton root. Len: %d.", len(mm)) | |||
} | |||
for k, _ := range mm { | |||
return k, nil | |||
} | |||
return "", nil | |||
} | |||
// If the path is an element with sub-elements, return a list of the sub-element | |||
// keys. (The list is alphabeticly sorted.) NOTE: Map keys that are prefixed with | |||
// '-', a hyphen, are considered attributes; see m.Attributes(path). | |||
func (mv Map) Elements(path string) ([]string, error) { | |||
e, err := mv.ValueForPath(path) | |||
if err != nil { | |||
return nil, err | |||
} | |||
switch e.(type) { | |||
case map[string]interface{}: | |||
ee := e.(map[string]interface{}) | |||
elems := make([]string, len(ee)) | |||
var i int | |||
for k, _ := range ee { | |||
if len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 { | |||
continue // skip attributes | |||
} | |||
elems[i] = k | |||
i++ | |||
} | |||
elems = elems[:i] | |||
// alphabetic sort keeps things tidy | |||
sort.Strings(elems) | |||
return elems, nil | |||
} | |||
return nil, fmt.Errorf("no elements for path: %s", path) | |||
} | |||
// If the path is an element with attributes, return a list of the attribute | |||
// keys. (The list is alphabeticly sorted.) NOTE: Map keys that are not prefixed with | |||
// '-', a hyphen, are not treated as attributes; see m.Elements(path). Also, if the | |||
// attribute prefix is "" - SetAttrPrefix("") or PrependAttrWithHyphen(false) - then | |||
// there are no identifiable attributes. | |||
func (mv Map) Attributes(path string) ([]string, error) { | |||
a, err := mv.ValueForPath(path) | |||
if err != nil { | |||
return nil, err | |||
} | |||
switch a.(type) { | |||
case map[string]interface{}: | |||
aa := a.(map[string]interface{}) | |||
attrs := make([]string, len(aa)) | |||
var i int | |||
for k, _ := range aa { | |||
if len(attrPrefix) == 0 || strings.Index(k, attrPrefix) != 0 { | |||
continue // skip non-attributes | |||
} | |||
attrs[i] = k[len(attrPrefix):] | |||
i++ | |||
} | |||
attrs = attrs[:i] | |||
// alphabetic sort keeps things tidy | |||
sort.Strings(attrs) | |||
return attrs, nil | |||
} | |||
return nil, fmt.Errorf("no attributes for path: %s", path) | |||
} |
Dear OpenI User
Thank you for your continuous support to the Openl Qizhi Community AI Collaboration Platform. In order to protect your usage rights and ensure network security, we updated the Openl Qizhi Community AI Collaboration Platform Usage Agreement in January 2024. The updated agreement specifies that users are prohibited from using intranet penetration tools. After you click "Agree and continue", you can continue to use our services. Thank you for your cooperation and understanding.
For more agreement content, please refer to the《Openl Qizhi Community AI Collaboration Platform Usage Agreement》