diff --git a/README.md b/README.md index 96f1c51..b42a94b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ | Common Queries | Required | Description | Default | | :------------- | :------- | :---------- | :---------------- | -| `data` | ◯ | JSON | | +| `data` | ◯ | JSON or URL | | | `title` | | string | | | `subtitle` | | string | | | `theme` | | string | `light` or `dark` | diff --git a/go.mod b/go.mod index 3ddc40a..f435df2 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21.3 require ( github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 github.com/go-playground/validator v9.31.0+incompatible + github.com/imroc/req/v3 v3.42.2 github.com/labstack/echo/v4 v4.11.3 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 @@ -13,26 +14,44 @@ require ( ) require ( + github.com/andybalholm/brotli v1.0.5 // indirect github.com/blend/go-sdk v1.20220411.3 // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/google/pprof v0.0.0-20230901174712-0191c66da455 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect + github.com/quic-go/quic-go v0.38.1 // indirect + github.com/refraction-networking/utls v1.5.3 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/image v0.11.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 36e2a90..729b60c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,10 @@ +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/blend/go-sdk v1.20220411.3 h1:GFV4/FQX5UzXLPwWV03gP811pj7B8J2sbuq+GJQofXc= github.com/blend/go-sdk v1.20220411.3/go.mod h1:7lnH8fTi6U4i1fArEXRyOIY2E1X4MALg09qsQqY1+ak= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,16 +12,43 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc= github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg= +github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= +github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20230901174712-0191c66da455 h1:YhRUmI1ttDC4sxKY2V62BTI8hCXnyZBV9h38eAanInE= +github.com/google/pprof v0.0.0-20230901174712-0191c66da455/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/imroc/req/v3 v3.42.2 h1:/BwrKXGR7X1/ptccaQAiziDCeZ7T6ye55g3ZhiLy1fc= +github.com/imroc/req/v3 v3.42.2/go.mod h1:W7dOrfQORA9nFoj+CafIZ6P5iyk+rWdbp2sffOAvABU= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM= github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= @@ -30,13 +62,29 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE= +github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4= +github.com/refraction-networking/utls v1.5.3 h1:Ds5Ocg1+MC1ahNx5iBEcHe0jHeLaA/fLey61EENm7ro= +github.com/refraction-networking/utls v1.5.3/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -52,26 +100,39 @@ github.com/vicanso/go-charts/v2 v2.6.1 h1:xWODVa4KtzkZrbUNd6WQunqGyFWXgaUeeXMQkF github.com/vicanso/go-charts/v2 v2.6.1/go.mod h1:Ii2KDI3udTG1wPtiTnntzjlUBJVJTqNscMzh3oYHzUk= github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE= github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -98,11 +159,19 @@ golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index b1379ad..ea9e0b9 100644 --- a/main.go +++ b/main.go @@ -13,9 +13,10 @@ import ( var publicDir embed.FS var ( - port string - host string - baseURL string + port string + host string + baseURL string + allowedRemoteDomains string ) var version = "dev" @@ -25,7 +26,7 @@ func main() { return } flags() - e := pkg.NewEcho(baseURL, publicDir) + e := pkg.NewEcho(baseURL, publicDir, allowedRemoteDomains) pkg.GracefulServerWithPid(e, host, port) } @@ -33,6 +34,7 @@ func main() { func flags() { flag.StringVar(&host, "host", "localhost", "host to serve") flag.StringVar(&port, "port", "3001", "port to serve") - flag.StringVar(&baseURL, "baseURL", "/", "base url with slash") + flag.StringVar(&baseURL, "base-url", "/", "base url with slash") + flag.StringVar(&allowedRemoteDomains, "allow-domains", "", "csv list of allowed domains for remote fetching") flag.Parse() } diff --git a/pkg/bar_chart_handler.go b/pkg/bar_chart_handler.go index 894dbd8..4d4c7d0 100644 --- a/pkg/bar_chart_handler.go +++ b/pkg/bar_chart_handler.go @@ -8,12 +8,14 @@ import ( ) type BarChartHandler struct { - chart *BarChart + chart *BarChart + allowedRemoteDomains string } -func NewBarChartHandler() *BarChartHandler { +func NewBarChartHandler(allowedRemoteDomains string) *BarChartHandler { return &BarChartHandler{ - chart: NewBarChart(), + chart: NewBarChart(), + allowedRemoteDomains: allowedRemoteDomains, } } @@ -28,6 +30,13 @@ func (h *BarChartHandler) Get(c echo.Context) ([]byte, error) { if err := BindRequest(c, req); err != nil { return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err) } + err := SetDataIfRemoteURL(req, h.allowedRemoteDomains) + if err != nil { + msgs := map[string]string{ + "data": err.Error(), + } + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, msgs) + } var data BarChartData if err := json.Unmarshal([]byte(req.ChartData), &data); err != nil { diff --git a/pkg/bar_chart_handler_test.go b/pkg/bar_chart_handler_test.go index cd44716..16dca1e 100644 --- a/pkg/bar_chart_handler_test.go +++ b/pkg/bar_chart_handler_test.go @@ -14,7 +14,7 @@ func TestGetBarChart(t *testing.T) { e := echo.New() e.GET("/bar", func(c echo.Context) error { - img, err := NewBarChartHandler().Get(c) + img, err := NewBarChartHandler("").Get(c) if err != nil { return err } diff --git a/pkg/chart.go b/pkg/chart.go index fbedcf5..8a401d5 100644 --- a/pkg/chart.go +++ b/pkg/chart.go @@ -1,7 +1,12 @@ package pkg import ( + "errors" "net/http" + "net/url" + "strings" + + "github.com/imroc/req/v3" ) const ( @@ -52,3 +57,64 @@ func SetHeadersResponseHTML(header http.Header) { header.Set("X-Frame-Options", "DENY") header.Set("X-XSS-Protection", "1; mode=block") } + +func SetHeadersResponseTxt(header http.Header) { + header.Set("Cache-Control", "max-age=86400") + header.Set("Expires", "86400") + header.Set("Content-Type", "text/plain; charset=utf-8") + // security headers + header.Set("X-Content-Type-Options", "nosniff") + header.Set("X-Frame-Options", "DENY") + header.Set("X-XSS-Protection", "1; mode=block") +} + +func IsURL(urlStr string) bool { + parsedURL, err := url.ParseRequestURI(urlStr) + return err == nil && parsedURL.Scheme != "" && parsedURL.Host != "" +} +func IsAllowedDomain(urlStr string, allowedDomains string) bool { + if allowedDomains == "" { + return false // default do not allow any urls + } + + // Parse the URL to extract the domain + parsedURL, err := url.Parse(urlStr) + if err != nil { + return false // If the URL is invalid, do not allow + } + domain := parsedURL.Hostname() + + // Split the allowedDomains into a slice + domains := strings.Split(allowedDomains, ",") + + // Check if the domain is in the list of allowed domains + for _, d := range domains { + if domain == d { + return true + } + } + + return false +} + +func GetURL(urlStr string) (string, error) { + resp, err := req.Get(urlStr) + if err != nil { + return "", err + } + return resp.ToString() +} + +func SetDataIfRemoteURL(req *ChartRequest, allowedRemoteDomains string) error { + if IsURL(req.ChartData) { + if !IsAllowedDomain(req.ChartData, allowedRemoteDomains) { + return errors.New("URL is not allowed") + } + data, err := GetURL(req.ChartData) + if err != nil { + return err + } + req.ChartData = string(data) + } + return nil +} diff --git a/pkg/donut_chart_handler.go b/pkg/donut_chart_handler.go index be3b56c..2ea3bae 100644 --- a/pkg/donut_chart_handler.go +++ b/pkg/donut_chart_handler.go @@ -8,12 +8,14 @@ import ( ) type DonutChartHandler struct { - chart *DonutChart + chart *DonutChart + allowedRemoteDomains string } -func NewDonutChartHandler() *DonutChartHandler { +func NewDonutChartHandler(allowedRemoteDomains string) *DonutChartHandler { return &DonutChartHandler{ - chart: NewDonutChart(), + chart: NewDonutChart(), + allowedRemoteDomains: allowedRemoteDomains, } } @@ -27,6 +29,13 @@ func (h *DonutChartHandler) Get(c echo.Context) ([]byte, error) { if err := BindRequest(c, req); err != nil { return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err) } + err := SetDataIfRemoteURL(req, h.allowedRemoteDomains) + if err != nil { + msgs := map[string]string{ + "data": err.Error(), + } + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, msgs) + } var data DonutChartData if err := json.Unmarshal([]byte(req.ChartData), &data); err != nil { diff --git a/pkg/donut_chart_handler_test.go b/pkg/donut_chart_handler_test.go index 8369cb9..9be94d2 100644 --- a/pkg/donut_chart_handler_test.go +++ b/pkg/donut_chart_handler_test.go @@ -14,7 +14,7 @@ func TestGetDonutChart(t *testing.T) { e := echo.New() e.GET("/donut", func(c echo.Context) error { - img, err := NewDonutChartHandler().Get(c) + img, err := NewDonutChartHandler("").Get(c) if err != nil { return err } diff --git a/pkg/echo.go b/pkg/echo.go index 45c3424..096bbf9 100644 --- a/pkg/echo.go +++ b/pkg/echo.go @@ -12,7 +12,7 @@ import ( "github.com/labstack/echo/v4/middleware" ) -func NewEcho(baseURL string, publicDir embed.FS) *echo.Echo { +func NewEcho(baseURL string, publicDir embed.FS, allowedRemoteDomains string) *echo.Echo { e := echo.New() //recover e.Use(middleware.Recover()) @@ -22,7 +22,7 @@ func NewEcho(baseURL string, publicDir embed.FS) *echo.Echo { Format: "REQUEST[${time_custom}] ${method} ${uri} (${latency_human}) ${status} ${remote_ip}\n", CustomTimeFormat: "2006-01-02 15:04:05", })) - SetupRoutes(e, baseURL, publicDir) + SetupRoutes(e, baseURL, publicDir, allowedRemoteDomains) return e } diff --git a/pkg/funnel_chart_handler.go b/pkg/funnel_chart_handler.go index 03c4e31..9213092 100644 --- a/pkg/funnel_chart_handler.go +++ b/pkg/funnel_chart_handler.go @@ -8,12 +8,14 @@ import ( ) type FunnelChartHandler struct { - chart *FunnelChart + chart *FunnelChart + allowedRemoteDomains string } -func NewFunnelChartHandler() *FunnelChartHandler { +func NewFunnelChartHandler(allowedRemoteDomains string) *FunnelChartHandler { return &FunnelChartHandler{ - chart: NewFunnelChart(), + chart: NewFunnelChart(), + allowedRemoteDomains: allowedRemoteDomains, } } @@ -27,6 +29,13 @@ func (h *FunnelChartHandler) Get(c echo.Context) ([]byte, error) { if err := BindRequest(c, req); err != nil { return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err) } + err := SetDataIfRemoteURL(req, h.allowedRemoteDomains) + if err != nil { + msgs := map[string]string{ + "data": err.Error(), + } + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, msgs) + } var data FunnelChartData if err := json.Unmarshal([]byte(req.ChartData), &data); err != nil { diff --git a/pkg/funnel_chart_handler_test.go b/pkg/funnel_chart_handler_test.go index 3010fd3..50491f7 100644 --- a/pkg/funnel_chart_handler_test.go +++ b/pkg/funnel_chart_handler_test.go @@ -14,7 +14,7 @@ func TestGetFunnelChart(t *testing.T) { e := echo.New() e.GET("/funnel", func(c echo.Context) error { - img, err := NewFunnelChartHandler().Get(c) + img, err := NewFunnelChartHandler("").Get(c) if err != nil { return err } diff --git a/pkg/line_chart_handler.go b/pkg/line_chart_handler.go index b6a0ea6..caaf1bd 100644 --- a/pkg/line_chart_handler.go +++ b/pkg/line_chart_handler.go @@ -8,12 +8,14 @@ import ( ) type LineChartHandler struct { - chart *LineChart + chart *LineChart + allowedRemoteDomains string } -func NewLineChartHandler() *LineChartHandler { +func NewLineChartHandler(allowedRemoteURLS string) *LineChartHandler { return &LineChartHandler{ - chart: NewLineChart(), + chart: NewLineChart(), + allowedRemoteDomains: allowedRemoteURLS, } } @@ -28,6 +30,13 @@ func (h *LineChartHandler) Get(c echo.Context) ([]byte, error) { if err := BindRequest(c, req); err != nil { return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err) } + err := SetDataIfRemoteURL(req, h.allowedRemoteDomains) + if err != nil { + msgs := map[string]string{ + "data": err.Error(), + } + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, msgs) + } var data LineChartData if err := json.Unmarshal([]byte(req.ChartData), &data); err != nil { diff --git a/pkg/line_chart_handler_test.go b/pkg/line_chart_handler_test.go index 64f9158..696098d 100644 --- a/pkg/line_chart_handler_test.go +++ b/pkg/line_chart_handler_test.go @@ -14,7 +14,7 @@ func TestGetLineChart(t *testing.T) { e := echo.New() e.GET("/line", func(c echo.Context) error { - img, err := NewLineChartHandler().Get(c) + img, err := NewLineChartHandler("").Get(c) if err != nil { return err } diff --git a/pkg/pie_chart_handler.go b/pkg/pie_chart_handler.go index 3cd2eb9..2382502 100644 --- a/pkg/pie_chart_handler.go +++ b/pkg/pie_chart_handler.go @@ -8,12 +8,14 @@ import ( ) type PieChartHandler struct { - chart *PieChart + chart *PieChart + allowedRemoteDomains string } -func NewPieChartHandler() *PieChartHandler { +func NewPieChartHandler(allowedRemoteDomains string) *PieChartHandler { return &PieChartHandler{ - chart: NewPieChart(), + chart: NewPieChart(), + allowedRemoteDomains: allowedRemoteDomains, } } @@ -27,6 +29,13 @@ func (h *PieChartHandler) Get(c echo.Context) ([]byte, error) { if err := BindRequest(c, req); err != nil { return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err) } + err := SetDataIfRemoteURL(req, h.allowedRemoteDomains) + if err != nil { + msgs := map[string]string{ + "data": err.Error(), + } + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, msgs) + } var data PieChartData if err := json.Unmarshal([]byte(req.ChartData), &data); err != nil { diff --git a/pkg/pie_chart_handler_test.go b/pkg/pie_chart_handler_test.go index 02e4818..5fccd06 100644 --- a/pkg/pie_chart_handler_test.go +++ b/pkg/pie_chart_handler_test.go @@ -14,7 +14,7 @@ func TestGetPieChart(t *testing.T) { e := echo.New() e.GET("/pie", func(c echo.Context) error { - img, err := NewPieChartHandler().Get(c) + img, err := NewPieChartHandler("").Get(c) if err != nil { return err } diff --git a/pkg/radar_chart_handler.go b/pkg/radar_chart_handler.go index eb4b266..8e894f8 100644 --- a/pkg/radar_chart_handler.go +++ b/pkg/radar_chart_handler.go @@ -8,12 +8,14 @@ import ( ) type RadarChartHandler struct { - chart *RadarChart + chart *RadarChart + allowedRemoteDomains string } -func NewRadarChartHandler() *RadarChartHandler { +func NewRadarChartHandler(allowedRemoteDomains string) *RadarChartHandler { return &RadarChartHandler{ - chart: NewRadarChart(), + chart: NewRadarChart(), + allowedRemoteDomains: allowedRemoteDomains, } } @@ -28,6 +30,13 @@ func (h *RadarChartHandler) Get(c echo.Context) ([]byte, error) { if err := BindRequest(c, req); err != nil { return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err) } + err := SetDataIfRemoteURL(req, h.allowedRemoteDomains) + if err != nil { + msgs := map[string]string{ + "data": err.Error(), + } + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, msgs) + } var data RadarChartData if err := json.Unmarshal([]byte(req.ChartData), &data); err != nil { diff --git a/pkg/radar_chart_handler_test.go b/pkg/radar_chart_handler_test.go index 083c973..652e03d 100644 --- a/pkg/radar_chart_handler_test.go +++ b/pkg/radar_chart_handler_test.go @@ -14,7 +14,7 @@ func TestGetRadarChart(t *testing.T) { e := echo.New() e.GET("/radar", func(c echo.Context) error { - img, err := NewRadarChartHandler().Get(c) + img, err := NewRadarChartHandler("").Get(c) if err != nil { return err } diff --git a/pkg/routes.go b/pkg/routes.go index baee786..9cd1eb6 100644 --- a/pkg/routes.go +++ b/pkg/routes.go @@ -24,7 +24,7 @@ Disallow: /table` DIST_DIR = "frontend/dist" ) -func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS) { +func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS, allowedRemoteDomains string) { e.GET(baseURL+"", func(c echo.Context) error { filename := fmt.Sprintf("%s/%s", DIST_DIR, "index.html") @@ -38,7 +38,7 @@ func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS) { // /robots.txt e.GET(baseURL+ROBOTS_FILE, func(c echo.Context) error { - c.Response().Header().Set("Cache-Control", "public, max-age=86400") + SetHeadersResponseTxt(c.Response().Header()) return c.String(http.StatusOK, ROBOTS_TXT) }) @@ -55,7 +55,7 @@ func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS) { // /line e.GET(baseURL+"line", func(c echo.Context) error { - img, err := NewLineChartHandler().Get(c) + img, err := NewLineChartHandler(allowedRemoteDomains).Get(c) if err != nil { return err } @@ -63,7 +63,7 @@ func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS) { }) // /bar e.GET(baseURL+"bar", func(c echo.Context) error { - img, err := NewBarChartHandler().Get(c) + img, err := NewBarChartHandler(allowedRemoteDomains).Get(c) if err != nil { return err } @@ -71,7 +71,7 @@ func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS) { }) // /radar e.GET(baseURL+"radar", func(c echo.Context) error { - img, err := NewRadarChartHandler().Get(c) + img, err := NewRadarChartHandler(allowedRemoteDomains).Get(c) if err != nil { return err } @@ -79,7 +79,7 @@ func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS) { }) // /donut e.GET(baseURL+"donut", func(c echo.Context) error { - img, err := NewDonutChartHandler().Get(c) + img, err := NewDonutChartHandler(allowedRemoteDomains).Get(c) if err != nil { return err } @@ -87,7 +87,7 @@ func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS) { }) // /pie e.GET(baseURL+"pie", func(c echo.Context) error { - img, err := NewPieChartHandler().Get(c) + img, err := NewPieChartHandler(allowedRemoteDomains).Get(c) if err != nil { return err } @@ -95,15 +95,15 @@ func SetupRoutes(e *echo.Echo, baseURL string, publicDir embed.FS) { }) // /funnel e.GET(baseURL+"funnel", func(c echo.Context) error { - img, err := NewFunnelChartHandler().Get(c) + img, err := NewFunnelChartHandler(allowedRemoteDomains).Get(c) if err != nil { return err } return c.Blob(http.StatusOK, "", img) }) - // /funnel + // /table e.GET(baseURL+"table", func(c echo.Context) error { - img, err := NewTableChartHandler().Get(c) + img, err := NewTableChartHandler(allowedRemoteDomains).Get(c) if err != nil { return err } diff --git a/pkg/table_chart_handler.go b/pkg/table_chart_handler.go index 9258825..6df59fd 100644 --- a/pkg/table_chart_handler.go +++ b/pkg/table_chart_handler.go @@ -8,12 +8,14 @@ import ( ) type TableChartHandler struct { - chart *TableChart + chart *TableChart + allowedRemoteDomains string } -func NewTableChartHandler() *TableChartHandler { +func NewTableChartHandler(allowedRemoteDomains string) *TableChartHandler { return &TableChartHandler{ - chart: NewTableChart(), + chart: NewTableChart(), + allowedRemoteDomains: allowedRemoteDomains, } } @@ -27,6 +29,13 @@ func (h *TableChartHandler) Get(c echo.Context) ([]byte, error) { if err := BindRequest(c, req); err != nil { return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err) } + err := SetDataIfRemoteURL(req, h.allowedRemoteDomains) + if err != nil { + msgs := map[string]string{ + "data": err.Error(), + } + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, msgs) + } var data TableChartData if err := json.Unmarshal([]byte(req.ChartData), &data); err != nil { diff --git a/pkg/table_chart_handler_test.go b/pkg/table_chart_handler_test.go index bb09369..c238110 100644 --- a/pkg/table_chart_handler_test.go +++ b/pkg/table_chart_handler_test.go @@ -14,7 +14,7 @@ func TestGetTableChart(t *testing.T) { e := echo.New() e.GET("/table", func(c echo.Context) error { - img, err := NewTableChartHandler().Get(c) + img, err := NewTableChartHandler("").Get(c) if err != nil { return err }