@@ -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= | |||
@@ -15,10 +15,15 @@ type CustomMigrationStatic struct { | |||
Migrate func(*xorm.Engine, *xorm.Engine) error | |||
} | |||
var customMigrations []CustomMigration | |||
var customMigrations = []CustomMigration{ | |||
//手机号功能可以不启用,不启用时手机号为空串,为了避免启用时唯一性约束报错,定制唯一性约束(对null和空字符串不做唯一性检查) | |||
{"set phone number unique index", setPhoneNumberUniqueIndex}, | |||
} | |||
var customMigrationsStatic []CustomMigrationStatic | |||
func MigrateCustom(x *xorm.Engine) { | |||
for _, m := range customMigrations { | |||
@@ -35,13 +40,20 @@ func MigrateCustomStatic(x *xorm.Engine, static *xorm.Engine) { | |||
for _, m := range customMigrationsStatic { | |||
log.Info("Migration: %s", m.Description) | |||
if err := m.Migrate(x, static); err != nil { | |||
log.Error("Migration: %v", err) | |||
} | |||
} | |||
} | |||
func setPhoneNumberUniqueIndex(x *xorm.Engine) error { | |||
query := "CREATE UNIQUE INDEX IF NOT EXISTS \"UQE_user_phone_number\" ON \"user\" (phone_number) WHERE phone_number IS NOT NULL and phone_number !='';" | |||
_, err := x.Exec(query) | |||
return err | |||
} | |||
func syncTopicStruct(x *xorm.Engine) error { | |||
query := "ALTER TABLE topic ALTER COLUMN name TYPE varchar(105);" | |||
@@ -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:"VARCHAR(255)"` | |||
} | |||
type UserShow struct { | |||
@@ -1446,6 +1448,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 { | |||
@@ -1614,6 +1641,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,8 +81,11 @@ 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"` | |||
Agree bool | |||
} | |||
// Validate valideates the fields | |||
@@ -105,7 +108,7 @@ func (f RegisterForm) IsEmailDomainWhitelisted() bool { | |||
} | |||
domain := strings.ToLower(f.Email[n+1:]) | |||
//support edu.cn | |||
if strings.HasSuffix(domain, "edu.cn") { | |||
return true | |||
@@ -209,6 +212,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 +369,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") | |||
} |
@@ -1568,4 +1568,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") | |||
} | |||
} |
@@ -33,6 +33,8 @@ captcha = CAPTCHA | |||
twofa = Two-Factor Authentication | |||
twofa_scratch = Two-Factor Scratch Code | |||
passcode = Passcode | |||
use_and_privacy_agree = I have read and agree to the <a target="_blank" href="%s">use agreement</a> and <a target="_blank" href="%s">privacy agreement</a> | |||
sign_up_agree_tips = should agree to the use agreement and privacy agreement! | |||
u2f_insert_key = Insert your security key | |||
u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it. | |||
@@ -198,6 +200,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 | |||
@@ -336,11 +339,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 | |||
@@ -375,6 +383,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 | |||
@@ -2503,6 +2542,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 | |||
@@ -33,6 +33,8 @@ captcha=验证码 | |||
twofa=两步验证 | |||
twofa_scratch=两步验证口令 | |||
passcode=验证码 | |||
use_and_privacy_agree = 我已阅读并同意 <a target="_blank" href="%s">使用协议</a> 和 <a target="_blank" href="%s">隐私协议</a> | |||
sign_up_agree_tips = 注册时需同意使用协议和隐私协议 | |||
u2f_insert_key=插入安全密钥 | |||
u2f_sign_in=按下安全密钥上的按钮。如果安全密钥没有按钮,请重新插入。 | |||
@@ -199,6 +201,7 @@ no_reply_address_helper=具有隐藏电子邮件地址的用户的域名。例 | |||
[home] | |||
uname_holder=登录名或电子邮箱地址 | |||
login_uname_holder=用户名/邮箱/手机号 | |||
uname_holder_cloud_brain=云脑登录名 | |||
password_holder=密码 | |||
switch_dashboard_context=切换控制面板用户 | |||
@@ -340,11 +343,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=验证 | |||
@@ -379,6 +387,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=请激活您的帐户 | |||
@@ -2517,6 +2556,7 @@ users.new_account=创建新帐户 | |||
users.name=用户名 | |||
users.full_name=全名 | |||
users.activated=已激活 | |||
users.bind_phone=手机验证 | |||
users.admin=管理员 | |||
users.restricted=受限 | |||
users.repos=项目数 | |||
@@ -40,6 +40,7 @@ const ( | |||
tplExploreImages base.TplName = "explore/images" | |||
tplExploreExploreDataAnalysis base.TplName = "explore/data_analysis" | |||
tplHomeTerm base.TplName = "terms" | |||
tplHomePrivacy base.TplName = "privacy" | |||
) | |||
// Home render home page | |||
@@ -117,6 +118,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) | |||
} | |||
@@ -804,3 +809,7 @@ func RecommendHomeInfo(ctx *context.Context) { | |||
func HomeTerm(ctx *context.Context) { | |||
ctx.HTML(200, tplHomeTerm) | |||
} | |||
func HomePrivacy(ctx *context.Context) { | |||
ctx.HTML(200, tplHomePrivacy) | |||
} |
@@ -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) | |||
} | |||
@@ -14,6 +14,8 @@ import ( | |||
"text/template" | |||
"time" | |||
"code.gitea.io/gitea/modules/slideimage" | |||
"code.gitea.io/gitea/routers/image" | |||
"code.gitea.io/gitea/routers/authentication" | |||
@@ -235,6 +237,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, | |||
@@ -336,6 +342,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/all/dosearch/", routers.SearchApi) | |||
m.Post("/user/login/kanban", user.SignInPostAPI) | |||
m.Get("/home/term", routers.HomeTerm) | |||
m.Get("/home/privacy", routers.HomePrivacy) | |||
m.Group("/explore", func() { | |||
m.Get("", func(ctx *context.Context) { | |||
ctx.Redirect(setting.AppSubURL + "/explore/repos") | |||
@@ -369,8 +376,11 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Group("/user", func() { | |||
m.Get("/login", user.SignIn) | |||
m.Get("/login/cloud_brain", user.SignInCloudBrain) | |||
m.Post("/login/cloud_brain", bindIgnErr(auth.SignInForm{}), user.SignInCloudBrainPost) | |||
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). | |||
@@ -410,6 +420,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) | |||
@@ -489,6 +503,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) | |||
@@ -593,7 +608,6 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Post("/delete", admin.DeleteNotices) | |||
m.Post("/empty", admin.EmptyNotices) | |||
}) | |||
}, adminReq) | |||
// ***** END: Admin ***** | |||
@@ -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,16 +48,18 @@ 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" | |||
tplResetPassword base.TplName = "user/auth/reset_passwd" | |||
tplTwofa base.TplName = "user/auth/twofa" | |||
tplTwofaScratch base.TplName = "user/auth/twofa_scratch" | |||
tplLinkAccount base.TplName = "user/auth/link_account" | |||
tplU2F base.TplName = "user/auth/u2f" | |||
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" | |||
tplLinkAccount base.TplName = "user/auth/link_account" | |||
tplU2F base.TplName = "user/auth/u2f" | |||
) | |||
// AutoSignIn reads cookie and try to auto-login. | |||
@@ -168,7 +180,7 @@ func SignInCloudBrain(ctx *context.Context) { | |||
return | |||
} | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login/cloud_brain" | |||
ctx.Data["PageIsSignIn"] = true | |||
ctx.Data["PageIsCloudBrainLogin"] = true | |||
ctx.Data["EnableCloudBrain"] = true | |||
@@ -176,6 +188,50 @@ 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") | |||
ctx.Data["EnableCloudBrain"] = true | |||
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") | |||
@@ -211,8 +267,7 @@ func SignInPostAPI(ctx *context.Context) { | |||
handleSignInFullNotRedirect(ctx, u, true, false) | |||
} | |||
// SignInPost response for sign in request | |||
func SignInPost(ctx *context.Context, form auth.SignInForm) { | |||
func SignInPostCommon(ctx *context.Context, form auth.SignInForm) { | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
orderedOAuth2Names, oauth2Providers, err := models.GetActiveOAuth2Providers() | |||
@@ -223,11 +278,12 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { | |||
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names | |||
ctx.Data["OAuth2Providers"] = oauth2Providers | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | |||
ctx.Data["PageIsSignIn"] = true | |||
ctx.Data["PageIsLogin"] = true | |||
ctx.Data["IsCourse"] = ctx.QueryBool("course") | |||
ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() | |||
ctx.Data["EnableCloudBrain"] = true | |||
if ctx.HasError() { | |||
ctx.HTML(200, tplSignIn) | |||
@@ -256,7 +312,9 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { | |||
ctx.HTML(200, "user/auth/prohibit_login") | |||
} | |||
} else { | |||
ctx.ServerError("UserSignIn", err) | |||
log.Warn("UserSignIn", err) | |||
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) | |||
log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr()) | |||
} | |||
return | |||
} | |||
@@ -294,6 +352,20 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { | |||
ctx.Redirect(setting.AppSubURL + "/user/two_factor") | |||
} | |||
func SignInCloudBrainPost(ctx *context.Context, form auth.SignInForm) { | |||
ctx.Data["PageIsCloudBrainLogin"] = true | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login/cloud_brain" | |||
SignInPostCommon(ctx,form) | |||
} | |||
// SignInPost response for sign in request | |||
func SignInPost(ctx *context.Context, form auth.SignInForm) { | |||
ctx.Data["PageIsLogin"] = true | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | |||
SignInPostCommon(ctx,form) | |||
} | |||
// TwoFactor shows the user a two-factor authentication page. | |||
func TwoFactor(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("twofa") | |||
@@ -1252,11 +1324,26 @@ 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 | |||
} | |||
} | |||
if !form.Agree { | |||
ctx.RenderWithErr(ctx.Tr("sign_up_agree_tips"), tplSignUp, &form) | |||
return | |||
} | |||
u := &models.User{ | |||
Name: form.UserName, | |||
Email: form.Email, | |||
Passwd: form.Password, | |||
IsActive: !setting.Service.RegisterEmailConfirm, | |||
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 { | |||
switch { | |||
@@ -1427,18 +1514,30 @@ 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 setting.MailService == nil { | |||
ctx.Data["IsResetDisable"] = true | |||
ctx.HTML(200, tplForgotPassword) | |||
return | |||
} | |||
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 { | |||
email := ctx.Query("email") | |||
ctx.Data["Email"] = email | |||
if setting.MailService == nil { | |||
ctx.Data["IsResetDisable"] = true | |||
ctx.HTML(200, tplForgotPassword) | |||
return | |||
} | |||
ctx.Data["IsResetRequest"] = true | |||
ctx.HTML(200, tplForgotPassword) | |||
email := ctx.Query("email") | |||
ctx.Data["Email"] = email | |||
ctx.Data["IsResetRequest"] = true | |||
ctx.HTML(200, tplForgotPassword) | |||
} | |||
} | |||
// ForgotPasswdPost response for forget password request | |||
@@ -1454,12 +1553,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 +1750,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 +1861,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) | |||
} |
@@ -12,6 +12,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" | |||
@@ -90,6 +92,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() | |||
} |
@@ -11,7 +11,7 @@ func GetSingleDailyPointLimitConfig() (*models.LimitConfigVO, error) { | |||
r, err := GetLimitConfigList(models.LimitConfigQueryOpts{ | |||
RefreshRate: models.PeriodDaily, | |||
Scope: models.LimitScopeSingleUser, | |||
LimitCode: "", | |||
LimitCode: models.SourceTypeAccomplishTask.Name(), | |||
LimitType: models.LimitTypeRewardPoint, | |||
}) | |||
if err != nil { | |||
@@ -27,7 +27,7 @@ func SetSingleDailyPointLimitConfig(limitNum int64, doer *models.User) error { | |||
l := &models.LimitConfigVO{ | |||
RefreshRate: models.PeriodDaily, | |||
Scope: models.LimitScopeSingleUser.Name(), | |||
LimitCode: "", | |||
LimitCode: models.SourceTypeAccomplishTask.Name(), | |||
LimitType: models.LimitTypeRewardPoint.Name(), | |||
LimitNum: limitNum, | |||
} | |||
@@ -150,7 +150,7 @@ func (l *limiterRunner) limit(r models.LimitConfig) error { | |||
if usedNum > r.LimitNum { | |||
if usedNum-r.LimitNum >= l.amount { | |||
redis_client.IncrBy(redisKey, -1*l.amount) | |||
return errors.New(fmt.Sprintf("%s:over limit", r.Tittle)) | |||
return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID)) | |||
} | |||
switch l.rejectPolicy { | |||
case models.FillUp: | |||
@@ -161,7 +161,7 @@ func (l *limiterRunner) limit(r models.LimitConfig) error { | |||
return nil | |||
case models.JustReject: | |||
redis_client.IncrBy(redisKey, -1*l.amount) | |||
return errors.New(fmt.Sprintf("%s:over limit", r.Tittle)) | |||
return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID)) | |||
case models.PermittedOnce: | |||
l.resultMap[l.index] = newLimitResult(false, l.amount, l.amount) | |||
return nil | |||
@@ -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,92 @@ | |||
{{template "base/head_home" .}} | |||
<style> | |||
.text-indent { | |||
text-indent: 2em; | |||
} | |||
</style> | |||
<div class="ui container"> | |||
<h1 class="ui center am-pt-30 am-pb-20">OpenI启智社区AI协作平台隐私协议</h1> | |||
<div class="ui divider am-pb-10"></div> | |||
<p class="text-indent"> | |||
OpenI启智社区AI协作平台作为新一代人工智能领域开源开放开发协作平台,不仅为用户提供代码托管与数据集管理等服务,同时提供开发者所需的计算算力资源,一个良好的开发环境对用户、组织和项目都尤为重要。 | |||
</p> | |||
<p class="text-indent"> | |||
为切实保护平台用户隐私权,优化用户体验,OpenI启智社区根据现行法规及政策,制定本《OpenI启智社区AI协作平台隐私协议》。本协议将详细说明OpenI启智社区AI协作平台在获取、管理及保护用户个人信息方面的政策及措施。本协议适用于OpenI启智社区AI协作平台向您提供的所有服务,无论您是通过计算机设备、移动终端或其他设备获得本平台的服务。 | |||
</p> | |||
<p> | |||
<strong>一、个人信息的收集</strong> | |||
</p> | |||
<p class="text-indent"> | |||
您已知悉且同意,在您注册本平台帐号或使用平台提供的服务时,本平台将记录您提供的相关个人信息,如:账号ID、密码、邮箱、手机号码等,上述个人信息是您获得平台提供服务的基础。 | |||
</p> | |||
<p class="text-indent"> | |||
在您使用本平台前,我们会引导您阅读本协议,并在您接受本协议的基础上,获得您的相关个人信息。如果您不同意提供个人信息,您将无法使用本平台的全部或部分功能和服务。 | |||
</p> | |||
<p> | |||
<strong>二、个人信息的存储</strong> | |||
</p> | |||
<p class="text-indent"> | |||
本平台会采取合适的安全措施和技术手段存储及保护您的个人信息,以防止丢失、被误用、受到未授权访问或泄漏、被篡改或毁坏。您的个人信息存放在有密码控制的位于中国境内的服务器中,访问均是受到限制的。 | |||
</p> | |||
<p class="text-indent"> | |||
根据本条款的规定,我们仅允许有必要知晓这些信息的平台员工等第三方访问个人信息,并要求他们履行相应的保密义务。 | |||
</p> | |||
<p> | |||
<strong>三、个人信息的管理</strong> | |||
</p> | |||
<p class="text-indent"> | |||
在完成平台账号注册并进行身份验证后,您可以查阅、修改、删除所提交的个人信息。一般情况下,您可随时浏览、修改、删除自己提交的信息,但出于安全性和身份识别的考虑,您可能无法修改注册时提供的某些初始注册信息及验证信息。 | |||
</p> | |||
<p class="text-indent"> | |||
您有权自主更新或更正您的个人信息,或授权本平台工作人员进行信息更新、更正。在您进行信息更新或更正之前,我们会首先验证您的身份。 | |||
</p> | |||
<p class="text-indent"> | |||
您可以自主注销本平台账号,注销后本平台不再收集您的个人信息,注销账号是不可逆的行为,一旦完成注销,本平台将删除有关您账户的一切信息。 | |||
</p> | |||
<p> | |||
<strong>四、个人信息的保护</strong> | |||
</p> | |||
<p class="text-indent"> | |||
本平台将尽一切合理努力保护其获得的用户个人信息。为防止用户个人信息在意外的、未经授权的情况下被非法访问、复制、修改、传送、遗失、破坏、处理或使用,本平台已经并将继续采取以下措施保护您的个人信息:<br> | |||
</p> | |||
<ol style="text-indent:0.5em;"> | |||
<li>以适当的方式对用户的个人信息进行加密护理,并通过隔离技术进行隔离;</li> | |||
<li>在适当的位置使用密码对用户个人信息进行保护;</li> | |||
<li>设立严格的数据使用和访问制度,采用严格的数据访问权限控制保护个人信息;</li> | |||
<li>其他合理措施。</li> | |||
</ol> | |||
<p class="text-indent"> | |||
尽管已经采取了上述合理有效措施,并遵守相关法律规定要求的标准,但本平台仍然无法保证您的个人信息通过不安全途径进行交流时的安全性。因此,用户个人应采取积极措施保证个人信息的安全,如:定期修改账号密码,不将自己的账号密码等个人信息透露给他人。 | |||
</p> | |||
<p class="text-indent"> | |||
您知悉,本平台提供的个人信息保护措施仅适用于OpenI启智社区AI协作平台,一旦您离开本平台,浏览或使用其他网站、服务及内容资源,本平台即没有能力及义务保护您在本平台以外的网站提交的任何个人信息,无论您登录或浏览上述网站是否基于本平台的链接或引导。 | |||
</p> | |||
<p> | |||
<strong>五、个人信息的使用和对外提供</strong> | |||
</p> | |||
<p class="text-indent"> | |||
未经您本人允许,平台不会向任何第三方公开您的个人信息,下列情形除外: | |||
</p> | |||
<ol style="text-indent:0.5em;"> | |||
<li>本平台已经取得您或您监护人的授权或同意;</li> | |||
<li>司法机关或行政机关基于法定程序要求平台披露的;</li> | |||
<li>本平台为维护自身合法权益而向用户提起诉讼或仲裁时;</li> | |||
<li>根据您与本平台相关服务条款、应用许可使用协议的约定;</li> | |||
<li>在法律允许的范围内,为保障本平台、平台用户以及社会公共利益免受损害时;</li> | |||
<li>法律法规规定的其他情形。</li> | |||
</ol> | |||
<p> | |||
<strong>六、对未成年人个人信息的特别保护</strong> | |||
</p> | |||
<p class="text-indent"> | |||
本平台非常重视对未成年人个人信息的保护。若您是18周岁以下的未成年人,在使用本平台的服务前,应确保事先取得监护人的同意,如您在平台上申请注册账号,本平台将默认为您已得到前述同意。本平台将根据国家相关法律法规及本协议的规定保护未成年人的个人信息。 | |||
</p> | |||
<p> | |||
<strong>七、隐私协议的修改</strong> | |||
</p> | |||
<p class="text-indent"> | |||
平台有权随时修改《OpenI启智社区AI协作平台隐私协议》的任何条款,一旦《OpenI启智社区AI协作平台隐私协议》内容发生变动,本平台将会在OpenI启智社区网站首页公告上公布修改后的政策,该公布行为视为本平台已经通知您修改内容。如果您不同意本平台对《OpenI启智社区AI协作平台隐私协议》相关条款所做的修改,您有权停止使用平台服务。如果您继续使用平台服务,则视为您接受平台对协议相关条款所做的修改。 | |||
</p> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -535,13 +535,10 @@ | |||
<div class="divider"> / </div> | |||
</div> | |||
<div id="dir_list{{.VersionName}}"> | |||
<div id="dir_list{{.VersionName}}" style="max-height: 500px;overflow:auto;"> | |||
</div> | |||
<div style="display:flex;align-items: center;justify-content: end;color: #f2711c;"> | |||
<i class="ri-error-warning-line" style="margin-right:0.5rem;"></i> | |||
<span>{{$.i18n.Tr "repo.file_limit_100"}}</span> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -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> |
@@ -28,7 +28,7 @@ | |||
</div> | |||
<div class="ui grid"> | |||
<div class="column"> | |||
<div class="column"> | |||
{{if .IsCourse}} | |||
<form class="ui form" action="{{.SignInLink}}?course=true" method="post"> | |||
{{else}} | |||
@@ -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}} | |||
{{if or .EnablePhone .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" .}} |
@@ -7,7 +7,7 @@ | |||
<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"> | |||
<div class="column"> | |||
<h2 class="ui header"> | |||
{{if .LinkAccountMode}} | |||
{{.i18n.Tr "auth.oauth_signup_title"}} | |||
@@ -23,13 +23,15 @@ | |||
{{end}} | |||
</div> | |||
<div class="ui grid"> | |||
<div class="column"> | |||
<div class="column"> | |||
<form class="ui form" action="{{.SignUpLink}}" method="post"> | |||
{{.CsrfTokenHtml}} | |||
{{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,8 +66,17 @@ | |||
</div> | |||
{{end}} | |||
{{if .EnablePhone }} | |||
<div class="use-type" usetype="0"> | |||
{{template "user/auth/phone_verify" .}} | |||
</div> | |||
{{end}} | |||
<div class="field"> | |||
<div class="ui checkbox"> | |||
<input name="agree" type="checkbox" tabindex="0" class="hidden" {{if .agree}}checked{{end}}><label>{{.i18n.Tr "use_and_privacy_agree" "/home/term" "/home/privacy" | Safe}}</label> | |||
</div> | |||
</div> | |||
<div class="ui hidden divider"></div> | |||
<div class="center aligned field"> | |||
<button class="fluid large ui blue button"> | |||
{{if .LinkAccountMode}} | |||
@@ -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,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,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 |