From 9f97fcaf8164c67db939b45008c0bee4b154968a Mon Sep 17 00:00:00 2001 From: brent saner Date: Sat, 13 Dec 2025 04:19:05 -0500 Subject: [PATCH] v0.2.0 ADDED: * The ability to show both IPv4 and IPv6 addresses (if the client has dual-stack and either the server does as well or a separate ClientInfo is running on the "other" net family). --- TODO | 2 -- _extras/clientinfo.env | 19 +++++++++++ args/types.go | 7 +++-- go.mod | 24 +++++++------- go.sum | 59 +++++++++++++---------------------- server/consts.go | 4 ++- server/funcs.go | 38 ++++++++++++++++++++++ server/funcs_page.go | 42 +++++++++++++++++++++++++ server/funcs_server.go | 43 +++++++++++++++---------- server/funcs_tpl.go | 20 ++++++++++++ server/tpl/meta.info.html.tpl | 30 ++++++++++++++++-- server/types.go | 49 ++++++++++++++++------------- 12 files changed, 244 insertions(+), 93 deletions(-) diff --git a/TODO b/TODO index 01e1cac..d1fc355 100644 --- a/TODO +++ b/TODO @@ -3,5 +3,3 @@ - also link to https://www.useragentstring.com/ -- or maybe https://www.whatsmyua.info/ -- see https://www.useragentstring.com/pages/api.php -- note that though lynx and elinks are "graphical", links is considered text. w3m is considered graphical. -- fix the text/plain issue for the json (et. al.) renderer diff --git a/_extras/clientinfo.env b/_extras/clientinfo.env index ba712bc..1db3a14 100644 --- a/_extras/clientinfo.env +++ b/_extras/clientinfo.env @@ -34,3 +34,22 @@ # The GID or group name of the group to chown the socket directory to. # Uses the primary group of the runtime user if not specified. #CINFO_DGRP=http + +# If specified, IPv4 clients can have their IPv6 address detected +# by using this URL (e.g. 'https://c6.r00t2.io/'). +# It is expected that this URL is also serving ClientInfo. +#CINFO_V6U='https://c6.r00t2.io/' + +# If specified, IPv6 clients can have their IPv4 address detected +# by using this URL (e.g. 'https://c4.r00t2.io/'). +# It is expected that this URL is also serving ClientInfo. +#CINFO_V4U='https://c4.r00t2.io/' + +# Specify additional/explicit CORS origin(s). +# If you're using -4/--ipv4/$CINFO_V4U and/or +# -6/--ipv6/$CINFO_V6U, they will be added +# automatically but you should add any +# dual-stack domains that serve ClientInfo here, +# separated by commas. +# (e.g. 'https://c.r00t2.io' +#CINFO_CORS='https://c.r00t2.io' diff --git a/args/types.go b/args/types.go index 23ff713..a6aaf97 100644 --- a/args/types.go +++ b/args/types.go @@ -1,7 +1,7 @@ package args import ( - `io/fs` + "io/fs" ) type Args struct { @@ -12,11 +12,14 @@ type Args struct { SockDirMode fs.FileMode `env:"CINFO_DMODE" short:"M" long:"dmode" default:"0o0700" description:"If using a UDS, attempt to set the directory containing the socket to use this permission. This should probably be either 0o0700 or 0o0770."` SockGrp *string `env:"CINFO_FGRP" short:"g" long:"fgroup" description:"If specified and using a UDS, attempt to set the socket to this GID/group name. (If unspecified, the default is current user's primary group.)"` SockDirGrp *string `env:"CINFO_DGRP" short:"G" long:"dgroup" description:"If specified and using a UDS, attempt to set the directory containing the socket to this GID/group name. (If unspecified, the default is current user's primary group.)"` + V4Url *string `env:"CINFO_V4U" short:"4" long:"ipv4" description:"If specified, IPv6 clients can have their IPv4 address detected by using this URL (e.g. 'https://c4.r00t2.io/').\nIt is expected that this URL is also serving ClientInfo." validate:"omitempty,url"` + V6Url *string `env:"CINFO_V6U" short:"6" long:"ipv6" description:"If specified, IPv4 clients can have their IPv6 address detected by using this URL (e.g. 'https://c6.r00t2.io/').\nIt is expected that this URL is also serving ClientInfo." validate:"omitempty,url"` + CORS []string `env:"CINFO_CORS" short:"c" long:"cors" description:"Specify additional/explicit CORS origin(s). If you're using -4/--ipv4 and/or -6/--ipv6, they will be added automatically but you should add any dual-stack domains that serve ClientInfo here (e.g. '--cors https://r00t2.io'"` Listen ListenArgs `positional-args:"true"` } type ListenArgs struct { - Listen string `env:"CINFO_URI" positional-arg-name:"LISTEN_URI" default:"unix:///var/run/clientinfo/fcgi.sock" description:"The specification to listen on.\nIf the scheme is 'unix', a FastCGI UDS/IPC socket is used (default); any host, query parameters, etc. component is ignored and the URI path is used to specify the socket.\nIf 'tcp', a FastCGI socket over TCP is opened on the .\nIf 'http', an HTTP listener is opened on the ; any path, query parameters, etc. components are ignored.\nHTTPS is unsupported; terminate with a reverse proxy. All other schemes will cause a fatal error.\nThe default is 'unix:///var/run/clientinfo/fcgi.sock'." validate:"required,uri"` + Listen string `env:"CINFO_URI" positional-arg-name:"LISTEN_URI" default:"unix:///var/run/clientinfo/fcgi.sock" description:"The specification to listen on.\nIf the scheme is 'unix', a FastCGI UDS/IPC socket is used (default); any host, query parameters, etc. component is ignored and the URI path is used to specify the socket.\nIf 'tcp', a FastCGI socket over TCP is opened on the .\nIf 'http', an HTTP listener is opened on the ; any path, query parameters, etc. components are ignored.\nHTTPS is unsupported; terminate with a reverse proxy. All other schemes will cause a fatal error." validate:"required,uri"` } type UdsPerms struct { diff --git a/go.mod b/go.mod index 881d224..f3469df 100644 --- a/go.mod +++ b/go.mod @@ -1,30 +1,30 @@ module r00t2.io/clientinfo -go 1.23.3 +go 1.25.5 require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/creasty/defaults v1.8.0 github.com/davecgh/go-spew v1.1.1 - github.com/go-playground/validator/v10 v10.23.0 - github.com/goccy/go-yaml v1.15.7 + github.com/go-playground/validator/v10 v10.29.0 + github.com/goccy/go-yaml v1.19.0 github.com/jessevdk/go-flags v1.6.1 github.com/mileusna/useragent v1.3.5 - golang.org/x/mod v0.22.0 - r00t2.io/goutils v1.7.1 - r00t2.io/sysutils v1.12.0 + golang.org/x/mod v0.31.0 + r00t2.io/goutils v1.13.0 + r00t2.io/sysutils v1.15.0 ) require ( + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/djherbis/times v1.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.7 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - golang.org/x/crypto v0.30.0 // indirect - golang.org/x/net v0.32.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect ) diff --git a/go.sum b/go.sum index d154a4a..ccbce7e 100644 --- a/go.sum +++ b/go.sum @@ -1,27 +1,25 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= 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/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= -github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= -github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98= -github.com/goccy/go-yaml v1.15.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/go-playground/validator/v10 v10.29.0 h1:lQlF5VNJWNlRbRZNeOIkWElR+1LL/OuHcc0Kp14w1xk= +github.com/go-playground/validator/v10 v10.29.0/go.mod h1:D6QxqeMlgIPuT02L66f2ccrZ7AGgHkzKmmTMZhk/Kc4= +github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE= +github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= @@ -34,33 +32,20 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc= -r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= -r00t2.io/sysutils v1.1.1 h1:q2P5u50HIIRk6muCPo1Gpapy6sNT4oaB1l2O/C/mi3A= -r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o= -r00t2.io/sysutils v1.12.0 h1:Ce3qUOyLixE1ZtFT/+SVwOT5kSkzg5+l1VloGeGugrU= -r00t2.io/sysutils v1.12.0/go.mod h1:bNTKNBk9MnUhj9coG9JBNicSi5FrtJHEM645um85pyw= +r00t2.io/goutils v1.13.0 h1:103QR2eUu42TNF7h9r6YgahbSgRAJ8VtFxSzw9rMaXI= +r00t2.io/goutils v1.13.0/go.mod h1:/0M+6fW2VAgdtAvwGE9oNV259MoEZeJ84rLCZsxe8uI= +r00t2.io/sysutils v1.15.0 h1:FSnREfbXDhBQEO7LMpnRQeKlPshozxk9XHw3YgWRgRg= +r00t2.io/sysutils v1.15.0/go.mod h1:28qB0074EIRQ8Sy/ybaA5jC3qA32iW2aYLkMCRhyAFM= diff --git a/server/consts.go b/server/consts.go index 41832d8..8da677c 100644 --- a/server/consts.go +++ b/server/consts.go @@ -24,7 +24,7 @@ const ( trueUaFieldStr string = "Yes" falseUaFieldStr string = "No" dfltIndent string = " " - httpRealHdr string = "X-ClientInfo-RealIP" + httpRealHdr string = "X-ClientInfo-RealIP" // TODO: advise https://nginx.org/en/docs/http/ngx_http_realip_module.html NGINX module? Allow config for user-specified header? ) var ( @@ -35,6 +35,8 @@ var ( Funcs( template.FuncMap{ "getTitle": getTitle, + "getIpver": getIpver, + "safeUrl": safeUrl, }, ).ParseFS(tplDir, "tpl/*.tpl"), ) diff --git a/server/funcs.go b/server/funcs.go index ab7f499..c98a31e 100644 --- a/server/funcs.go +++ b/server/funcs.go @@ -46,6 +46,7 @@ func NewClient(uaStr string) (r *R00tClient, err error) { func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error) { var s Server + var origin string var udsSockPerms args.UdsPerms if log == nil { @@ -62,6 +63,7 @@ func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error) args: cliArgs, mux: http.NewServeMux(), sock: nil, + corsOrigins: nil, reloadChan: make(chan os.Signal), stopChan: make(chan os.Signal), } @@ -73,6 +75,42 @@ func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error) s.mux.HandleFunc("/usage.html", s.handleUsage) s.mux.HandleFunc("/favicon.ico", s.explicit404) + if cliArgs.CORS != nil && len(cliArgs.CORS) > 0 { + if s.corsOrigins == nil { + s.corsOrigins = make(map[string]struct{}) + } + for _, origin = range cliArgs.CORS { + s.corsOrigins[strings.ToLower(origin)] = struct{}{} + } + } + + if cliArgs.V4Url != nil { + if s.v4Url, err = url.Parse(*cliArgs.V4Url); err != nil { + s.log.Err("server.NewServer: Failed to parse IPv4 URI '%s': %v", *cliArgs.V4Url, err) + return + } + if s.corsOrigins == nil { + s.corsOrigins = make(map[string]struct{}) + } + origin = strings.ToLower(fmt.Sprintf("%s://%s", s.v4Url.Scheme, s.v4Url.Host)) + s.corsOrigins[origin] = struct{}{} + } + if cliArgs.V6Url != nil { + if s.v6Url, err = url.Parse(*cliArgs.V6Url); err != nil { + s.log.Err("server.NewServer: Failed to parse IPv6 URI '%s': %v", *cliArgs.V6Url, err) + return + } + if s.corsOrigins == nil { + s.corsOrigins = make(map[string]struct{}) + } + origin = strings.ToLower(fmt.Sprintf("%s://%s", s.v6Url.Scheme, s.v6Url.Host)) + s.corsOrigins[origin] = struct{}{} + } + + if s.corsOrigins != nil { + s.log.Debug("server.NewServer: CORS origins: %#v", s.corsOrigins) + } + if s.listenUri, err = url.Parse(cliArgs.Listen.Listen); err != nil { s.log.Err("server.NewServer: Failed to parse listener URI: %v", err) return diff --git a/server/funcs_page.go b/server/funcs_page.go index b8e2f2b..f671ca4 100644 --- a/server/funcs_page.go +++ b/server/funcs_page.go @@ -4,6 +4,48 @@ import ( `fmt` ) +func (p *Page) AltURL() (altUrl string) { + + if !p.HasAltURL() { + return + } + + if p.Info.IP.Is4() { + altUrl = p.srv.v6Url.String() + } else if p.Info.IP.Is6() { + altUrl = p.srv.v4Url.String() + } + + return +} + +func (p *Page) AltVer() (altVer string) { + + if !p.HasAltURL() { + return + } + + if p.Info.IP.Is4() { + altVer = "v6" + } else if p.Info.IP.Is6() { + altVer = "v4" + } + + return +} + +func (p *Page) HasAltURL() (hasAlt bool) { + + if p.Info == nil { + return + } + + hasAlt = (p.Info.IP.Is4() && p.srv.v6Url != nil) || + (p.Info.IP.Is6() && p.srv.v4Url != nil) + + return +} + func (p *Page) RenderIP(indent uint) (s string) { s = fmt.Sprintf("%s", p.Info.IP.String(), p.Info.IP.String()) diff --git a/server/funcs_server.go b/server/funcs_server.go index 8273ea1..7e2d791 100644 --- a/server/funcs_server.go +++ b/server/funcs_server.go @@ -3,24 +3,24 @@ package server import ( `encoding/json` `encoding/xml` - "errors" - "fmt" - "net" - "net/http" + `errors` + `fmt` + `net` + `net/http` `net/http/fcgi` - "net/netip" - "net/url" - "os" - "os/signal" - "strings" - "sync" + `net/netip` + `net/url` + `os` + `os/signal` + `strings` + `sync` `syscall` - sysd "github.com/coreos/go-systemd/daemon" - "github.com/davecgh/go-spew/spew" + sysd `github.com/coreos/go-systemd/daemon` + `github.com/davecgh/go-spew/spew` `github.com/goccy/go-yaml` `r00t2.io/clientinfo/version` - "r00t2.io/goutils/multierr" + `r00t2.io/goutils/multierr` ) // Close cleanly closes any remnants of a Server. Stop should be used instead to cleanly shut down; this is a little more aggressive. @@ -267,8 +267,10 @@ func (s *Server) explicit404(resp http.ResponseWriter, req *http.Request) { func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { var err error + var ok bool var page *Page var uas []string + var origin string var reqdMimes []string var parsedUA *R00tClient var nAP netip.AddrPort @@ -280,13 +282,20 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { var outerFmt string = mediaJSON s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req)) + origin = req.Header.Get("Origin") + if s.corsOrigins != nil && len(s.corsOrigins) != 0 && origin != "" { + if _, ok = s.corsOrigins[origin]; ok { + resp.Header().Set("Access-Control-Allow-Origin", origin) + resp.Header().Add("Vary", "Origin") + } + } resp.Header().Set("ClientInfo-Version", version.Ver.Short()) page = &Page{ Info: &R00tInfo{ Client: nil, - IP: nil, + IP: netip.Addr{}, Port: 0, Headers: XmlHeaders(req.Header), Req: req, @@ -301,7 +310,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { // First the client info. remAddrPort = req.RemoteAddr if s.isHttp && req.Header.Get(httpRealHdr) != "" { - // TODO: WHitelist explicit reverse proxy addr(s)? + // TODO: Whitelist explicit reverse proxy addr(s)? remAddrPort = req.Header.Get(httpRealHdr) req.Header.Del(httpRealHdr) } @@ -315,7 +324,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) { */ err = nil } - page.Info.IP = net.ParseIP(nAP.Addr().String()) + page.Info.IP = nAP.Addr().Unmap() page.Info.Port = nAP.Port() } if req.URL != nil { @@ -547,6 +556,8 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) { var b []byte + page.srv = s + if page.RawFmt != nil { switch *page.RawFmt { case mediaHTML: diff --git a/server/funcs_tpl.go b/server/funcs_tpl.go index 269b22f..e4ce6d3 100644 --- a/server/funcs_tpl.go +++ b/server/funcs_tpl.go @@ -2,9 +2,22 @@ package server import ( `fmt` + `net/netip` `strings` + `html/template` ) +func getIpver(a netip.Addr) (verStr string) { + + if a.Is4() { + verStr = "v4" + } else if a.Is6() { + verStr = "v6" + } + + return +} + func getTitle(subPage string) (title string) { if subPage == "" || subPage == "index" { @@ -16,3 +29,10 @@ func getTitle(subPage string) (title string) { return } + +func safeUrl(urlStr string) (u template.URL) { + + u = template.URL(urlStr) + + return +} diff --git a/server/tpl/meta.info.html.tpl b/server/tpl/meta.info.html.tpl index 52e579d..60d1007 100644 --- a/server/tpl/meta.info.html.tpl +++ b/server/tpl/meta.info.html.tpl @@ -4,9 +4,35 @@ {{- $linkico := "🔗" }}

Client/Browser Information{{ $linkico }}

- Your IP Address is {{ $page.Info.IP.String }}. + Your IP{{ getIpver $page.Info.IP }} Address is {{ $page.Info.IP.String }}.
You are connecting with port {{ $page.Info.Port }} outbound. + {{- if $page.HasAltURL }} +

+ + {{- end }}

{{- if $page.Raw }}

Raw Block ({{ $page.RawFmt }}){{ $linkico }}

@@ -42,7 +68,7 @@ - {{- /* + {{- /*
    {{- $flds := $ua.ToMap }} diff --git a/server/types.go b/server/types.go index 9e3096c..06793e6 100644 --- a/server/types.go +++ b/server/types.go @@ -1,19 +1,22 @@ package server import ( - `encoding/xml` - `net` - `net/http` - `net/url` - `os` + "encoding/xml" + "net" + "net/http" + "net/netip" + "net/url" + "os" - `github.com/mileusna/useragent` - `r00t2.io/clientinfo/args` - `r00t2.io/goutils/logging` + "github.com/mileusna/useragent" + "r00t2.io/clientinfo/args" + "r00t2.io/goutils/logging" ) -type outerRenderer func(page *Page, resp http.ResponseWriter) (err error) -type XmlHeaders map[string][]string +type ( + outerRenderer func(page *Page, resp http.ResponseWriter) (err error) + XmlHeaders map[string][]string +) // R00tInfo is the structure of data returned to the client. type R00tInfo struct { @@ -22,7 +25,7 @@ type R00tInfo struct { // Client is the UA/Client info, if any passed by the client. Client []*R00tClient `json:"ua,omitempty" xml:"ua,omitempty" yaml:"Client/User Agent,omitempty"` // IP is the client IP address. - IP net.IP `json:"ip" xml:"ip,attr" yaml:"Client IP Address"` + IP netip.Addr `json:"ip" xml:"ip,attr" yaml:"Client IP Address"` // Port is the client's port number. Port uint16 `json:"port" xml:"port,attr" yaml:"Client Port"` // Headers are the collection of the request headers sent by the client. @@ -84,19 +87,23 @@ type Page struct { Indent string // DoIndent indicates if indenting was enabled. DoIndent bool + srv *Server } type Server struct { - log logging.Logger - args *args.Args - listenUri *url.URL - isHttp bool - mux *http.ServeMux - sock net.Listener - doneChan chan bool - stopChan chan os.Signal - reloadChan chan os.Signal - isStopping bool + log logging.Logger + args *args.Args + listenUri *url.URL + isHttp bool + mux *http.ServeMux + sock net.Listener + doneChan chan bool + stopChan chan os.Signal + reloadChan chan os.Signal + v4Url *url.URL + v6Url *url.URL + corsOrigins map[string]struct{} + isStopping bool } // https://www.iana.org/assignments/media-types/media-types.xhtml