From d819a1181dd4883eb8faeb94e5c510aa0503640a Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Tue, 21 Mar 2023 21:29:24 -0700 Subject: [PATCH] Photo sharing support --- go.mod | 14 +++-- go.sum | 84 ++++++++++++++++++++++++--- pkg/config/config.go | 11 +++- pkg/handlers.go | 60 +++++++++++++++++++ pkg/images.go | 122 +++++++++++++++++++++++++++++++++++++++ pkg/messages.go | 14 ++++- pkg/websocket.go | 18 ++++-- web/static/js/BareRTC.js | 57 ++++++++++++++++++ web/templates/chat.html | 20 ++++++- 9 files changed, 380 insertions(+), 20 deletions(-) create mode 100644 pkg/images.go diff --git a/go.mod b/go.mod index d46505f..476b84b 100644 --- a/go.mod +++ b/go.mod @@ -5,18 +5,24 @@ go 1.19 require ( git.kirsle.net/go/log v0.0.0-20200902035305-70ac2848949b github.com/BurntSushi/toml v1.2.1 + github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f github.com/golang-jwt/jwt/v4 v4.4.3 github.com/microcosm-cc/bluemonday v1.0.22 github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 + golang.org/x/image v0.6.0 nhooyr.io/websocket v1.8.7 ) require ( github.com/aymerick/douceur v0.2.0 // indirect + github.com/disintegration/imaging v1.6.2 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/klauspost/compress v1.10.3 // indirect github.com/russross/blackfriday v1.5.2 // indirect + github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/sergi/go-diff v1.3.1 // indirect + github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect + github.com/shurcooL/go-goon v1.0.0 // indirect github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b // indirect github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c // indirect @@ -24,8 +30,8 @@ require ( github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/net v0.5.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/term v0.4.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index 2041716..f1b5ac0 100644 --- a/go.sum +++ b/go.sum @@ -5,44 +5,74 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 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= +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/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f h1:RMnUwTnNR070mFAEIoqMYjNirHj8i0h79VXTYyBCyVA= +github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f/go.mod h1:KoE3Ti1qbQXCb3s/XGj0yApHnbnNnn1bXTtB5Auq/Vc= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +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/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/microcosm-cc/bluemonday v1.0.22 h1:p2tT7RNzRdCi0qmwxG+HbqD6ILkmwter1ZwVZn1oTxA= github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +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/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 h1:86e54L0i3pH3dAIA8OxBbfLrVyhoGpnNk1iJCigAWYs= github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= +github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v1.0.0 h1:BCQPvxGkHHJ4WpBO4m/9FXbITVIsvAm/T66cCcCGI7E= +github.com/shurcooL/go-goon v1.0.0/go.mod h1:2wTHMsGo7qnpmqA8ADYZtP4I1DD94JpXGQ3Dxq2YQ5w= github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 h1:KaKXZldeYH73dpQL+Nr38j1r5BgpAYQjYvENOUpIZDQ= github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b h1:rBIwpb5ggtqf0uZZY5BPs1sL7njUMM7I8qD2jiou70E= @@ -57,28 +87,66 @@ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10= github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= +golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= +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/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-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.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/pkg/config/config.go b/pkg/config/config.go index d8a0e6f..37037de 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -12,7 +12,7 @@ import ( // Version of the config format - when new fields are added, it will attempt // to write the settings.toml to disk so new defaults populate. -var currentVersion = 3 +var currentVersion = 2 // Config for your BareRTC app. type Config struct { @@ -33,6 +33,10 @@ type Config struct { UseXForwardedFor bool + WebSocketReadLimit int64 + MaxImageWidth int + PreviewImageWidth int + PublicChannels []Channel } @@ -65,6 +69,9 @@ func DefaultConfig() Config { CORSHosts: []string{ "https://www.example.com", }, + WebSocketReadLimit: 1024 * 1024 * 40, // 40 MB. + MaxImageWidth: 1280, + PreviewImageWidth: 360, PublicChannels: []Channel{ { ID: "lobby", @@ -104,7 +111,7 @@ func LoadSettings() error { _, err = toml.Decode(string(data), &Current) // Have we added new config fields? Save the settings.toml. - if Current.Version < currentVersion { + if Current.Version != currentVersion { log.Warn("New options are available for your settings.toml file. Your settings will be re-saved now.") Current.Version = currentVersion if err := WriteSettings(); err != nil { diff --git a/pkg/handlers.go b/pkg/handlers.go index 5f195a5..0345e81 100644 --- a/pkg/handlers.go +++ b/pkg/handlers.go @@ -1,7 +1,9 @@ package barertc import ( + "encoding/base64" "fmt" + "path/filepath" "strings" "time" @@ -141,6 +143,64 @@ func (s *Server) OnMessage(sub *Subscriber, msg Message) { s.Broadcast(message) } +// OnFile handles a picture shared in chat with a channel. +func (s *Server) OnFile(sub *Subscriber, msg Message) { + if sub.Username == "" { + sub.ChatServer("You must log in first.") + return + } + + // Detect image type and convert it into an tag. + var ( + filename = msg.Message + ext = filepath.Ext(filename) + filetype string + ) + switch strings.ToLower(ext) { + case ".jpg", ".jpeg": + filetype = "image/jpeg" + case ".gif": + filetype = "image/gif" + case ".png": + filetype = "image/png" + default: + sub.ChatServer("Unsupported image type, should be a jpeg, GIF or png.") + return + } + + // Process the image: scale it down, strip metadata, etc. + img, pvWidth, pvHeight := ProcessImage(filetype, msg.Bytes) + var dataURL = fmt.Sprintf("data:%s;base64,%s", filetype, base64.StdEncoding.EncodeToString(img)) + + // Message to be echoed to the channel. + var message = Message{ + Action: ActionMessage, + Channel: msg.Channel, + Username: sub.Username, + + // Their image embedded via a data: URI - no server storage needed! + Message: fmt.Sprintf( + ``, + dataURL, + pvWidth, pvHeight, + ), + } + + // Is this a DM? + if strings.HasPrefix(msg.Channel, "@") { + // Echo the message only to both parties. + s.SendTo(sub.Username, message) + message.Channel = "@" + sub.Username + if err := s.SendTo(msg.Channel, message); err != nil { + sub.ChatServer("Your message could not be delivered: %s", err) + } + return + } + + // Broadcast a chat message to the room. + s.Broadcast(message) +} + // OnMe handles current user state updates. func (s *Server) OnMe(sub *Subscriber, msg Message) { if msg.VideoActive { diff --git a/pkg/images.go b/pkg/images.go new file mode 100644 index 0000000..5594f4d --- /dev/null +++ b/pkg/images.go @@ -0,0 +1,122 @@ +package barertc + +import ( + "bytes" + "image" + "image/jpeg" + "image/png" + "io" + + "git.kirsle.net/apps/barertc/pkg/config" + "git.kirsle.net/apps/barertc/pkg/log" + "github.com/edwvee/exiffix" + "golang.org/x/image/draw" +) + +var ( + // TODO: configurable + MaxPhotoWidth = 1280 +) + +// ProcessImage treats user uploaded images: +// +// - Scales them down to a reasonable size +// - Strips EXIF metadata +// +// and returns the modified image again as bytes. +// +// Also returns the suggested preview width, height to draw the image +// at. This may be smaller than its true width x height. +// +// Filetype should be image/jpeg, image/gif or image/png. +func ProcessImage(fileType string, data []byte) ([]byte, int, int) { + reader := bytes.NewReader(data) + + // Strip EXIF data. + origImage, _, err := exiffix.Decode(reader) + if err != nil { + log.Error("ProcessImage: exiffix: %s", err) + return data, config.Current.PreviewImageWidth, config.Current.PreviewImageWidth + } + + reader.Seek(0, io.SeekStart) + var width, height = origImage.Bounds().Max.X, origImage.Bounds().Max.Y + + log.Info("ProcessImage: taking a %dx%d image", width, height) + + // Compute what size we should scale the width/height to, + // and the even smaller preview size for front-end. + var ( + previewWidth = config.Current.PreviewImageWidth + previewHeight = previewWidth + ) + if width >= height { + log.Debug("Its width(%d) is >= its height (%d)", width, height) + if width > config.Current.MaxImageWidth { + newWidth := config.Current.MaxImageWidth + log.Debug("\tnewWidth=%d", newWidth) + log.Debug("\tnewHeight=(%d / %d) * %d", width, height, newWidth) + height = int((float64(height) / float64(width)) * float64(newWidth)) + width = newWidth + log.Debug("Its longest is width, scale to %dx%d", width, height) + } + + // Compute the preview width. + if width > config.Current.PreviewImageWidth { + newWidth := config.Current.PreviewImageWidth + previewHeight = int((float64(height) / float64(width)) * float64(newWidth)) + previewWidth = newWidth + } + } else { + if height > config.Current.MaxImageWidth { + newHeight := config.Current.MaxImageWidth + width = int((float64(width) / float64(height)) * float64(newHeight)) + height = newHeight + log.Debug("Its longest is height, scale to %dx%d", width, height) + } + + // Compute the preview height. + if height > config.Current.PreviewImageWidth { + newHeight := config.Current.PreviewImageWidth + previewWidth = int((float64(width) / float64(height)) * float64(newHeight)) + previewHeight = newHeight + log.Debug("Its longest is height, scale to %dx%d", previewWidth, previewHeight) + } + } + + // Scale the image. + scaledImg := Scale(origImage, image.Rect(0, 0, width, height), draw.ApproxBiLinear) + + // Return the new bytes. + var buf = bytes.NewBuffer([]byte{}) + switch fileType { + case "image/jpeg": + jpeg.Encode(buf, scaledImg, &jpeg.Options{ + Quality: 90, + }) + case "image/gif": + // Return the original data - we will only break it. + return data, width, height + case "image/png": + png.Encode(buf, scaledImg) + default: + return data, config.Current.PreviewImageWidth, config.Current.PreviewImageWidth + } + + return buf.Bytes(), previewWidth, previewHeight +} + +// Scale down an image. Example: +// +// scaled := Scale(src, image.Rect(0, 0, 200, 200), draw.ApproxBiLinear) +func Scale(src image.Image, rect image.Rectangle, scale draw.Scaler) image.Image { + dst := image.NewRGBA(rect) + copyRect := image.Rect( + rect.Min.X, + rect.Min.Y, + rect.Min.X+rect.Max.X, + rect.Min.Y+rect.Max.Y, + ) + scale.Scale(dst, copyRect, src, src.Bounds(), draw.Over, nil) + return dst +} diff --git a/pkg/messages.go b/pkg/messages.go index 4d10b65..8e2ce2b 100644 --- a/pkg/messages.go +++ b/pkg/messages.go @@ -1,5 +1,12 @@ package barertc +/* +Message is the basic carrier of WebSocket chat protocol actions. + +Every message (client or server) has an Action and the rest of the +fields may vary depending on the action. Many messages target (or carry) +a Username, chat Channel and carry an arbitrary Message. +*/ type Message struct { Action string `json:"action,omitempty"` Channel string `json:"channel,omitempty"` @@ -19,7 +26,11 @@ type Message struct { // Sent on `open` actions along with the (other) Username. OpenSecret string `json:"openSecret,omitempty"` - // Parameters sent on WebRTC signaling messages. + // Send on `file` actions, passing e.g. image data. + Bytes []byte `json:"bytes,omitempty"` + + // WebRTC negotiation messages: proxy their signaling messages + // between the two users to negotiate peer connection. Candidate string `json:"candidate,omitempty"` // candidate Description string `json:"description,omitempty"` // sdp } @@ -35,6 +46,7 @@ const ( ActionRing = "ring" // receiver of a WebRTC open request ActionWatch = "watch" // user has received video and is watching you ActionUnwatch = "unwatch" // user has closed your video + ActionFile = "file" // image sharing in chat // Actions sent by server only ActionPing = "ping" diff --git a/pkg/websocket.go b/pkg/websocket.go index 6306c0f..0a46faa 100644 --- a/pkg/websocket.go +++ b/pkg/websocket.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "git.kirsle.net/apps/barertc/pkg/config" "git.kirsle.net/apps/barertc/pkg/jwt" "git.kirsle.net/apps/barertc/pkg/log" "git.kirsle.net/apps/barertc/pkg/util" @@ -59,18 +60,23 @@ func (sub *Subscriber) ReadLoop(s *Server) { // Read the user's posted message. var msg Message - log.Debug("Read(%d=%s): %s", sub.ID, sub.Username, data) if err := json.Unmarshal(data, &msg); err != nil { log.Error("Read(%d=%s) Message error: %s", sub.ID, sub.Username, err) continue } + if msg.Action != ActionFile { + log.Debug("Read(%d=%s): %s", sub.ID, sub.Username, data) + } + // What action are they performing? switch msg.Action { case ActionLogin: s.OnLogin(sub, msg) case ActionMessage: s.OnMessage(sub, msg) + case ActionFile: + s.OnFile(sub, msg) case ActionMe: s.OnMe(sub, msg) case ActionOpen: @@ -96,7 +102,7 @@ func (sub *Subscriber) SendJSON(v interface{}) error { if err != nil { return err } - log.Debug("SendJSON(%d=%s): %s", sub.ID, sub.Username, data) + // log.Debug("SendJSON(%d=%s): %s", sub.ID, sub.Username, data) return sub.conn.Write(sub.ctx, websocket.MessageText, data) } @@ -118,7 +124,7 @@ func (sub *Subscriber) ChatServer(message string, v ...interface{}) { }) } -// WebSocket handles the /ws websocket connection. +// WebSocket handles the /ws websocket connection endpoint. func (s *Server) WebSocket() http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ip := util.IPAddress(r) @@ -133,6 +139,7 @@ func (s *Server) WebSocket() http.HandlerFunc { defer c.Close(websocket.StatusInternalError, "the sky is falling") log.Debug("WebSocket: %s has connected", ip) + c.SetReadLimit(config.Current.WebSocketReadLimit) // CloseRead starts a goroutine that will read from the connection // until it is closed. @@ -235,7 +242,10 @@ func (s *Server) IterSubscribers(isLocked ...bool) []*Subscriber { // Broadcast a message to the chat room. func (s *Server) Broadcast(msg Message) { - log.Debug("Broadcast: %+v", msg) + if len(msg.Message) < 1024 { + log.Debug("Broadcast: %+v", msg) + } + s.subscribersMu.RLock() defer s.subscribersMu.RUnlock() for _, sub := range s.IterSubscribers(true) { diff --git a/web/static/js/BareRTC.js b/web/static/js/BareRTC.js index 3c449b1..644cad3 100644 --- a/web/static/js/BareRTC.js +++ b/web/static/js/BareRTC.js @@ -7,6 +7,17 @@ const configuration = { }] }; +const FileUploadMaxSize = 1024 * 1024 * 8; // 8 MB + + +function setModalImage(url) { + let $modalImg = document.querySelector("#modalImage"), + $modal = document.querySelector("#photo-modal"); + $modalImg.src = url; + $modal.classList.add("is-active"); + return false; +} + const app = Vue.createApp({ delimiters: ['[[', ']]'], @@ -1072,6 +1083,52 @@ const app = Vue.createApp({ return `${(hour)}:${minutes}:${seconds} ${ampm}`; }, + /** + * Image sharing in chat + */ + + // The image upload button handler. + uploadFile() { + let input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + input.onchange = e => { + let file = e.target.files[0]; + if (file.size > FileUploadMaxSize) { + this.ChatClient(`Please share an image smaller than ${FileUploadMaxSize / 1024 / 1024} MB in size!`); + return; + } + + this.ChatClient(`Uploading file to chat: ${file.name} - ${file.size} bytes, ${file.type} format.`); + + // Get image file data. + let reader = new FileReader(); + let rawData = new ArrayBuffer(); + reader.onload = e => { + rawData = e.target.result; + + let fileByteArray = [], + u8array = new Uint8Array(rawData); + for (let i = 0; i < u8array.length; i++) { + fileByteArray.push(u8array[i]); + } + + let msg = JSON.stringify({ + action: "file", + channel: this.channel, + message: file.name, + bytes: fileByteArray, //btoa(fileByteArray), + }); + + // Send it to the chat server. + this.ws.conn.send(msg); + }; + + reader.readAsArrayBuffer(file); + }; + input.click(); + }, + /** * Sound effect concerns. */ diff --git a/web/templates/chat.html b/web/templates/chat.html index a89dff4..eb2a93c 100644 --- a/web/templates/chat.html +++ b/web/templates/chat.html @@ -275,6 +275,17 @@ + + +
@@ -588,12 +599,13 @@
+