Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a4549ea72
|
||
|
|
9f97fcaf81
|
2
TODO
2
TODO
@@ -3,5 +3,3 @@
|
|||||||
- also link to https://www.useragentstring.com/
|
- also link to https://www.useragentstring.com/
|
||||||
-- or maybe https://www.whatsmyua.info/
|
-- or maybe https://www.whatsmyua.info/
|
||||||
-- see https://www.useragentstring.com/pages/api.php
|
-- 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
|
|
||||||
|
|||||||
@@ -34,3 +34,22 @@
|
|||||||
# The GID or group name of the group to chown the socket directory to.
|
# 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.
|
# Uses the primary group of the runtime user if not specified.
|
||||||
#CINFO_DGRP=http
|
#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'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package args
|
package args
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`io/fs`
|
"io/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Args struct {
|
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."`
|
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.)"`
|
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.)"`
|
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"`
|
Listen ListenArgs `positional-args:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenArgs struct {
|
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 <host:port>.\nIf 'http', an HTTP listener is opened on the <host:port>; 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 <host:port>.\nIf 'http', an HTTP listener is opened on the <host:port>; 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 {
|
type UdsPerms struct {
|
||||||
|
|||||||
24
go.mod
24
go.mod
@@ -1,30 +1,30 @@
|
|||||||
module r00t2.io/clientinfo
|
module r00t2.io/clientinfo
|
||||||
|
|
||||||
go 1.23.3
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/creasty/defaults v1.8.0
|
github.com/creasty/defaults v1.8.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/go-playground/validator/v10 v10.23.0
|
github.com/go-playground/validator/v10 v10.29.0
|
||||||
github.com/goccy/go-yaml v1.15.7
|
github.com/goccy/go-yaml v1.19.0
|
||||||
github.com/jessevdk/go-flags v1.6.1
|
github.com/jessevdk/go-flags v1.6.1
|
||||||
github.com/mileusna/useragent v1.3.5
|
github.com/mileusna/useragent v1.3.5
|
||||||
golang.org/x/mod v0.22.0
|
golang.org/x/mod v0.31.0
|
||||||
r00t2.io/goutils v1.7.1
|
r00t2.io/goutils v1.13.0
|
||||||
r00t2.io/sysutils v1.12.0
|
r00t2.io/sysutils v1.15.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
|
||||||
github.com/djherbis/times v1.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/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
golang.org/x/crypto v0.30.0 // indirect
|
golang.org/x/crypto v0.46.0 // indirect
|
||||||
golang.org/x/net v0.32.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
59
go.sum
59
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 h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
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 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||||
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
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.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
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/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 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
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.29.0 h1:lQlF5VNJWNlRbRZNeOIkWElR+1LL/OuHcc0Kp14w1xk=
|
||||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.29.0/go.mod h1:D6QxqeMlgIPuT02L66f2ccrZ7AGgHkzKmmTMZhk/Kc4=
|
||||||
github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98=
|
github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE=
|
||||||
github.com/goccy/go-yaml v1.15.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.0/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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
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/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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
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/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
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=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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.13.0 h1:103QR2eUu42TNF7h9r6YgahbSgRAJ8VtFxSzw9rMaXI=
|
||||||
r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
r00t2.io/goutils v1.13.0/go.mod h1:/0M+6fW2VAgdtAvwGE9oNV259MoEZeJ84rLCZsxe8uI=
|
||||||
r00t2.io/sysutils v1.1.1 h1:q2P5u50HIIRk6muCPo1Gpapy6sNT4oaB1l2O/C/mi3A=
|
r00t2.io/sysutils v1.15.0 h1:FSnREfbXDhBQEO7LMpnRQeKlPshozxk9XHw3YgWRgRg=
|
||||||
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
r00t2.io/sysutils v1.15.0/go.mod h1:28qB0074EIRQ8Sy/ybaA5jC3qA32iW2aYLkMCRhyAFM=
|
||||||
r00t2.io/sysutils v1.12.0 h1:Ce3qUOyLixE1ZtFT/+SVwOT5kSkzg5+l1VloGeGugrU=
|
|
||||||
r00t2.io/sysutils v1.12.0/go.mod h1:bNTKNBk9MnUhj9coG9JBNicSi5FrtJHEM645um85pyw=
|
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ const (
|
|||||||
trueUaFieldStr string = "Yes"
|
trueUaFieldStr string = "Yes"
|
||||||
falseUaFieldStr string = "No"
|
falseUaFieldStr string = "No"
|
||||||
dfltIndent string = " "
|
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?
|
||||||
|
httpCIDHdr string = "Request-Id"
|
||||||
|
urlParamXCheck string = "src_req_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -35,6 +37,8 @@ var (
|
|||||||
Funcs(
|
Funcs(
|
||||||
template.FuncMap{
|
template.FuncMap{
|
||||||
"getTitle": getTitle,
|
"getTitle": getTitle,
|
||||||
|
"getIpver": getIpver,
|
||||||
|
"safeUrl": safeUrl,
|
||||||
},
|
},
|
||||||
).ParseFS(tplDir, "tpl/*.tpl"),
|
).ParseFS(tplDir, "tpl/*.tpl"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ func NewClient(uaStr string) (r *R00tClient, err error) {
|
|||||||
func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error) {
|
func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error) {
|
||||||
|
|
||||||
var s Server
|
var s Server
|
||||||
|
var origin string
|
||||||
var udsSockPerms args.UdsPerms
|
var udsSockPerms args.UdsPerms
|
||||||
|
|
||||||
if log == nil {
|
if log == nil {
|
||||||
@@ -62,6 +63,7 @@ func NewServer(log logging.Logger, cliArgs *args.Args) (srv *Server, err error)
|
|||||||
args: cliArgs,
|
args: cliArgs,
|
||||||
mux: http.NewServeMux(),
|
mux: http.NewServeMux(),
|
||||||
sock: nil,
|
sock: nil,
|
||||||
|
corsOrigins: nil,
|
||||||
reloadChan: make(chan os.Signal),
|
reloadChan: make(chan os.Signal),
|
||||||
stopChan: 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("/usage.html", s.handleUsage)
|
||||||
s.mux.HandleFunc("/favicon.ico", s.explicit404)
|
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 {
|
if s.listenUri, err = url.Parse(cliArgs.Listen.Listen); err != nil {
|
||||||
s.log.Err("server.NewServer: Failed to parse listener URI: %v", err)
|
s.log.Err("server.NewServer: Failed to parse listener URI: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,8 +2,60 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
`fmt`
|
`fmt`
|
||||||
|
`net/url`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (p *Page) AltURL() (altUrl string) {
|
||||||
|
|
||||||
|
var u *url.URL
|
||||||
|
|
||||||
|
if !p.HasAltURL() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Info.IP.Is4() {
|
||||||
|
u = p.srv.v6Url
|
||||||
|
} else if p.Info.IP.Is6() {
|
||||||
|
u = p.srv.v4Url
|
||||||
|
} else {
|
||||||
|
// lol how did you get here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Query().Add(urlParamXCheck, p.ReqUUID.String())
|
||||||
|
|
||||||
|
altUrl = u.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) {
|
func (p *Page) RenderIP(indent uint) (s string) {
|
||||||
|
|
||||||
s = fmt.Sprintf("<a href=\"https://ipinfo.io/%s\">%s</a>", p.Info.IP.String(), p.Info.IP.String())
|
s = fmt.Sprintf("<a href=\"https://ipinfo.io/%s\">%s</a>", p.Info.IP.String(), p.Info.IP.String())
|
||||||
|
|||||||
@@ -3,24 +3,25 @@ package server
|
|||||||
import (
|
import (
|
||||||
`encoding/json`
|
`encoding/json`
|
||||||
`encoding/xml`
|
`encoding/xml`
|
||||||
"errors"
|
`errors`
|
||||||
"fmt"
|
`fmt`
|
||||||
"net"
|
`net`
|
||||||
"net/http"
|
`net/http`
|
||||||
`net/http/fcgi`
|
`net/http/fcgi`
|
||||||
"net/netip"
|
`net/netip`
|
||||||
"net/url"
|
`net/url`
|
||||||
"os"
|
`os`
|
||||||
"os/signal"
|
`os/signal`
|
||||||
"strings"
|
`strings`
|
||||||
"sync"
|
`sync`
|
||||||
`syscall`
|
`syscall`
|
||||||
|
|
||||||
sysd "github.com/coreos/go-systemd/daemon"
|
sysd `github.com/coreos/go-systemd/daemon`
|
||||||
"github.com/davecgh/go-spew/spew"
|
`github.com/davecgh/go-spew/spew`
|
||||||
`github.com/goccy/go-yaml`
|
`github.com/goccy/go-yaml`
|
||||||
|
`github.com/google/uuid`
|
||||||
`r00t2.io/clientinfo/version`
|
`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.
|
// Close cleanly closes any remnants of a Server. Stop should be used instead to cleanly shut down; this is a little more aggressive.
|
||||||
@@ -261,14 +262,17 @@ func (s *Server) Reload() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) explicit404(resp http.ResponseWriter, req *http.Request) {
|
func (s *Server) explicit404(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
s.log.Debug("server.Server.explicit404: '%s' requested '%s %s'", req.RemoteAddr, req.Method, req.URL.String())
|
||||||
resp.WriteHeader(http.StatusNotFound)
|
resp.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
var ok bool
|
||||||
var page *Page
|
var page *Page
|
||||||
var uas []string
|
var uas []string
|
||||||
|
var origin string
|
||||||
var reqdMimes []string
|
var reqdMimes []string
|
||||||
var parsedUA *R00tClient
|
var parsedUA *R00tClient
|
||||||
var nAP netip.AddrPort
|
var nAP netip.AddrPort
|
||||||
@@ -276,17 +280,27 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
var parsedFmts []*parsedMIME
|
var parsedFmts []*parsedMIME
|
||||||
var renderer outerRenderer
|
var renderer outerRenderer
|
||||||
var includeFmt string
|
var includeFmt string
|
||||||
|
var reqId uuid.UUID = uuid.New()
|
||||||
var params url.Values = make(url.Values)
|
var params url.Values = make(url.Values)
|
||||||
var outerFmt string = mediaJSON
|
var outerFmt string = mediaJSON
|
||||||
|
|
||||||
s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req))
|
s.log.Info("server.Server.handleDefault: '%s' (%s): '%s %s'", reqId.String(), req.RemoteAddr, req.Method, req.URL.String())
|
||||||
|
s.log.Debug("server.Server.handleDefault: Handling request for '%s':\n%s", reqId.String(), 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())
|
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
|
||||||
|
resp.Header().Set(httpCIDHdr, reqId.String())
|
||||||
|
|
||||||
page = &Page{
|
page = &Page{
|
||||||
Info: &R00tInfo{
|
Info: &R00tInfo{
|
||||||
Client: nil,
|
Client: nil,
|
||||||
IP: nil,
|
IP: netip.Addr{},
|
||||||
Port: 0,
|
Port: 0,
|
||||||
Headers: XmlHeaders(req.Header),
|
Headers: XmlHeaders(req.Header),
|
||||||
Req: req,
|
Req: req,
|
||||||
@@ -296,18 +310,19 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
RawFmt: nil,
|
RawFmt: nil,
|
||||||
Indent: "",
|
Indent: "",
|
||||||
DoIndent: false,
|
DoIndent: false,
|
||||||
|
ReqUUID: reqId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// First the client info.
|
// First the client info.
|
||||||
remAddrPort = req.RemoteAddr
|
remAddrPort = req.RemoteAddr
|
||||||
if s.isHttp && req.Header.Get(httpRealHdr) != "" {
|
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)
|
remAddrPort = req.Header.Get(httpRealHdr)
|
||||||
req.Header.Del(httpRealHdr)
|
req.Header.Del(httpRealHdr)
|
||||||
}
|
}
|
||||||
if remAddrPort != "" {
|
if remAddrPort != "" {
|
||||||
if nAP, err = netip.ParseAddrPort(remAddrPort); err != nil {
|
if nAP, err = netip.ParseAddrPort(remAddrPort); err != nil {
|
||||||
s.log.Warning("server.Server.handleDefault: Failed to parse remote address '%s': %v", remAddrPort, err)
|
s.log.Warning("server.Server.handleDefault: Failed to parse remote address '%s' for '%s': %v", remAddrPort, reqId.String(), err)
|
||||||
// Don't return an error in case we're doing weird things like direct socket clients.
|
// Don't return an error in case we're doing weird things like direct socket clients.
|
||||||
/*
|
/*
|
||||||
http.Error(resp, "ERROR: Failed to parse client address", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to parse client address", http.StatusInternalServerError)
|
||||||
@@ -315,18 +330,21 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
*/
|
*/
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
page.Info.IP = net.ParseIP(nAP.Addr().String())
|
page.Info.IP = nAP.Addr().Unmap()
|
||||||
page.Info.Port = nAP.Port()
|
page.Info.Port = nAP.Port()
|
||||||
}
|
}
|
||||||
if req.URL != nil {
|
if req.URL != nil {
|
||||||
params = req.URL.Query()
|
params = req.URL.Query()
|
||||||
|
if params.Has(urlParamXCheck) {
|
||||||
|
s.log.Info("server.Server.handleDefault: Cross-stack check: '%s' => '%s'", params.Get(urlParamXCheck), reqId.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uas = req.Header.Values("User-Agent")
|
uas = req.Header.Values("User-Agent")
|
||||||
if uas != nil && len(uas) > 0 {
|
if uas != nil && len(uas) > 0 {
|
||||||
page.Info.Client = make([]*R00tClient, 0, len(uas))
|
page.Info.Client = make([]*R00tClient, 0, len(uas))
|
||||||
for _, ua := range uas {
|
for _, ua := range uas {
|
||||||
if parsedUA, err = NewClient(ua); err != nil {
|
if parsedUA, err = NewClient(ua); err != nil {
|
||||||
s.log.Err("server.Server.handleDefault: Failed to create client for '%s': %v", ua, err)
|
s.log.Err("server.Server.handleDefault: Failed to create client for UA '%s' via request '%s': %v", ua, reqId.String(), err)
|
||||||
http.Error(resp, fmt.Sprintf("ERROR: Failed to parse 'User-Agent' '%s'", ua), http.StatusInternalServerError)
|
http.Error(resp, fmt.Sprintf("ERROR: Failed to parse 'User-Agent' '%s'", ua), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -354,7 +372,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
reqdMimes = req.Header.Values("Accept")
|
reqdMimes = req.Header.Values("Accept")
|
||||||
if reqdMimes != nil && len(reqdMimes) > 0 {
|
if reqdMimes != nil && len(reqdMimes) > 0 {
|
||||||
if parsedFmts, err = parseAccept(strings.Join(reqdMimes, ",")); err != nil {
|
if parsedFmts, err = parseAccept(strings.Join(reqdMimes, ",")); err != nil {
|
||||||
s.log.Err("server.Server.handleDefault: Failed to parse Accept header '%#v' for '%s': %v", reqdMimes, remAddrPort, err)
|
s.log.Err("server.Server.handleDefault: Failed to parse Accept header '%#v' for '%s' ('%s'): %v", reqdMimes, remAddrPort, reqId.String(), err)
|
||||||
resp.Header()["Accept"] = okAcceptMime
|
resp.Header()["Accept"] = okAcceptMime
|
||||||
http.Error(
|
http.Error(
|
||||||
resp,
|
resp,
|
||||||
@@ -365,12 +383,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
|
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
|
||||||
if errors.Is(err, ErrUnsupportedMIME) {
|
if errors.Is(err, ErrUnsupportedMIME) {
|
||||||
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, reqdMimes)
|
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v' for '%s'.", remAddrPort, reqdMimes, reqId.String())
|
||||||
resp.Header()["Accept"] = okAcceptMime
|
resp.Header()["Accept"] = okAcceptMime
|
||||||
http.Error(resp, "ERROR: No supported MIME type specified via request 'Accept'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
|
http.Error(resp, "ERROR: No supported MIME type specified via request 'Accept'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
s.log.Err("server.Server.handleDefault: Received unknown error choosing from Accept header for '%s': %v", remAddrPort, err)
|
s.log.Err("server.Server.handleDefault: Received unknown error choosing from Accept header for '%s' from '%s': %v", remAddrPort, reqId.String(), err)
|
||||||
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -379,7 +397,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
// `mime` URL query parameter.
|
// `mime` URL query parameter.
|
||||||
if params.Has("mime") {
|
if params.Has("mime") {
|
||||||
if parsedFmts, err = parseAccept(strings.Join(params["mime"], ",")); err != nil {
|
if parsedFmts, err = parseAccept(strings.Join(params["mime"], ",")); err != nil {
|
||||||
s.log.Err("server.Server.handleDefault: Failed to parse 'mime' URL parameter '%#v' for '%s': %v", params["mime"], remAddrPort, err)
|
s.log.Err("server.Server.handleDefault: Failed to parse 'mime' URL parameter '%#v' for '%s' (%s): %v", params["mime"], remAddrPort, reqId.String(), err)
|
||||||
resp.Header()["Accept"] = okAcceptMime
|
resp.Header()["Accept"] = okAcceptMime
|
||||||
http.Error(
|
http.Error(
|
||||||
resp,
|
resp,
|
||||||
@@ -390,12 +408,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
|
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
|
||||||
if errors.Is(err, ErrUnsupportedMIME) {
|
if errors.Is(err, ErrUnsupportedMIME) {
|
||||||
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, params["mime"])
|
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' (%s) via '%#v'.", remAddrPort, reqId.String(), params["mime"])
|
||||||
resp.Header()["Accept"] = okAcceptMime
|
resp.Header()["Accept"] = okAcceptMime
|
||||||
http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'mime'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
|
http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'mime'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'mime' URL parameter for '%s': %v", remAddrPort, err)
|
s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'mime' URL parameter for '%s' (%s): %v", remAddrPort, reqId.String(), err)
|
||||||
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -404,7 +422,7 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
// 'include' URL query parameter (only for text/html).
|
// 'include' URL query parameter (only for text/html).
|
||||||
if outerFmt == mediaHTML && params.Has("include") {
|
if outerFmt == mediaHTML && params.Has("include") {
|
||||||
if parsedFmts, err = parseAccept(strings.Join(params["include"], ",")); err != nil {
|
if parsedFmts, err = parseAccept(strings.Join(params["include"], ",")); err != nil {
|
||||||
s.log.Err("server.Server.handleDefault: Failed to parse 'include' URL parameter '%#v' for '%s': %v", params["include"], remAddrPort, err)
|
s.log.Err("server.Server.handleDefault: Failed to parse 'include' URL parameter '%#v' for '%s' (%s): %v", params["include"], remAddrPort, reqId.String(), err)
|
||||||
resp.Header()["Accept"] = okAcceptMime
|
resp.Header()["Accept"] = okAcceptMime
|
||||||
http.Error(
|
http.Error(
|
||||||
resp,
|
resp,
|
||||||
@@ -415,12 +433,12 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
if includeFmt, err = decideParseAccept(parsedFmts, includeFmt); err != nil {
|
if includeFmt, err = decideParseAccept(parsedFmts, includeFmt); err != nil {
|
||||||
if errors.Is(err, ErrUnsupportedMIME) {
|
if errors.Is(err, ErrUnsupportedMIME) {
|
||||||
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, params["include"])
|
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' (%s) via '%#v'.", remAddrPort, reqId.String(), params["include"])
|
||||||
resp.Header()["Accept"] = okAcceptMime
|
resp.Header()["Accept"] = okAcceptMime
|
||||||
http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'include'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
|
http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'include'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'include' URL parameter for '%s': %v", remAddrPort, err)
|
s.log.Err("server.Server.handleDefault: Received unknown error choosing from 'include' URL parameter for '%s' (%s): %v", remAddrPort, reqId.String(), err)
|
||||||
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Unknown error occurred when negotiating MIME type.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -450,13 +468,13 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
|
|||||||
case mediaYAML:
|
case mediaYAML:
|
||||||
renderer = s.renderYML
|
renderer = s.renderYML
|
||||||
default:
|
default:
|
||||||
s.log.Err("server.Server.handleDefault: Unknown output format '%s'", outerFmt)
|
s.log.Err("server.Server.handleDefault: Unknown output format '%s' from '%s' (%s)", outerFmt, remAddrPort, reqId.String())
|
||||||
http.Error(resp, "ERROR: Unable to determine default renderer.", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Unable to determine default renderer.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = renderer(page, resp); err != nil {
|
if err = renderer(page, resp); err != nil {
|
||||||
s.log.Err("server.Server.handleDefault: Failed to render request from '%s' as '%s': %v", remAddrPort, outerFmt, err)
|
s.log.Err("server.Server.handleDefault: Failed to render request from '%s' (%s) as '%s': %v", remAddrPort, reqId.String(), outerFmt, err)
|
||||||
// The renderer handles the error-handling with the client.
|
// The renderer handles the error-handling with the client.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -472,20 +490,23 @@ func (s *Server) handleAbout(resp http.ResponseWriter, req *http.Request) {
|
|||||||
Req: req,
|
Req: req,
|
||||||
},
|
},
|
||||||
PageType: "about",
|
PageType: "about",
|
||||||
|
ReqUUID: uuid.New(),
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug("server.Server.handleAbout: Handling request:\n%s", spew.Sdump(req))
|
s.log.Info("server.Server.handleAbout: '%s' (%s)", renderPage.ReqUUID.String(), req.RemoteAddr)
|
||||||
|
s.log.Debug("server.Server.handleAbout: Handling request for '%s':\n%s", renderPage.ReqUUID.String(), spew.Sdump(req))
|
||||||
|
|
||||||
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
|
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
|
||||||
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
|
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
resp.Header().Set(httpCIDHdr, renderPage.ReqUUID.String())
|
||||||
|
|
||||||
if err = tpl.ExecuteTemplate(resp, "about", renderPage); err != nil {
|
if err = tpl.ExecuteTemplate(resp, "about", renderPage); err != nil {
|
||||||
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", req.RemoteAddr, err)
|
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", renderPage.ReqUUID.String(), err)
|
||||||
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug("server.Server.handleAbout: Handled request:\n%s", spew.Sdump(req))
|
s.log.Debug("server.Server.handleAbout: Handled request for '%s'", renderPage.ReqUUID.String())
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -498,20 +519,23 @@ func (s *Server) handleUsage(resp http.ResponseWriter, req *http.Request) {
|
|||||||
Req: req,
|
Req: req,
|
||||||
},
|
},
|
||||||
PageType: "usage",
|
PageType: "usage",
|
||||||
|
ReqUUID: uuid.New(),
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug("server.Server.handleUsage: Handling request:\n%s", spew.Sdump(req))
|
s.log.Info("server.Server.handleUsage: '%s' (%s)", renderPage.ReqUUID.String(), req.RemoteAddr)
|
||||||
|
s.log.Debug("server.Server.handleUsage: Handling request for '%s':\n%s", renderPage.ReqUUID.String(), spew.Sdump(req))
|
||||||
|
|
||||||
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
|
resp.Header().Set("ClientInfo-Version", version.Ver.Short())
|
||||||
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
|
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
resp.Header().Set(httpCIDHdr, renderPage.ReqUUID.String())
|
||||||
|
|
||||||
if err = tpl.ExecuteTemplate(resp, "usage", renderPage); err != nil {
|
if err = tpl.ExecuteTemplate(resp, "usage", renderPage); err != nil {
|
||||||
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", req.RemoteAddr, err)
|
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s' (%s): %v", renderPage.ReqUUID.String(), req.RemoteAddr, err)
|
||||||
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug("server.Server.handleUsage: Handled request:\n%s", spew.Sdump(req))
|
s.log.Debug("server.Server.handleUsage: Handled request for '%s'", renderPage.ReqUUID.String())
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -524,7 +548,7 @@ func (s *Server) renderJSON(page *Page, resp http.ResponseWriter) (err error) {
|
|||||||
|
|
||||||
if page.DoIndent {
|
if page.DoIndent {
|
||||||
if b, err = json.MarshalIndent(page.Info, "", page.Indent); err != nil {
|
if b, err = json.MarshalIndent(page.Info, "", page.Indent); err != nil {
|
||||||
s.log.Err("server.Server.renderJSON: Failed to render to indented JSON: %v", err)
|
s.log.Err("server.Server.renderJSON: Failed to render to indented JSON for '%s': %v", page.ReqUUID.String(), err)
|
||||||
http.Error(resp, "ERROR: Failed to render JSON", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render JSON", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -547,6 +571,8 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
|
|||||||
|
|
||||||
var b []byte
|
var b []byte
|
||||||
|
|
||||||
|
page.srv = s
|
||||||
|
|
||||||
if page.RawFmt != nil {
|
if page.RawFmt != nil {
|
||||||
switch *page.RawFmt {
|
switch *page.RawFmt {
|
||||||
case mediaHTML:
|
case mediaHTML:
|
||||||
@@ -555,13 +581,13 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
|
|||||||
case mediaJSON, mediaXML:
|
case mediaJSON, mediaXML:
|
||||||
if page.DoIndent {
|
if page.DoIndent {
|
||||||
if b, err = mediaIndent[*page.RawFmt](page.Info, "", page.Indent); err != nil {
|
if b, err = mediaIndent[*page.RawFmt](page.Info, "", page.Indent); err != nil {
|
||||||
s.log.Err("server.Server.renderHTML: Failed to render to indented include '%s': %v", *page.RawFmt, err)
|
s.log.Err("server.Server.renderHTML: Failed to render to indented include '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err)
|
||||||
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil {
|
if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil {
|
||||||
s.log.Err("server.Server.renderHTML: Failed to render to include '%s': %v", *page.RawFmt, err)
|
s.log.Err("server.Server.renderHTML: Failed to render to include '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err)
|
||||||
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -569,7 +595,9 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
|
|||||||
// Non-indentable
|
// Non-indentable
|
||||||
case mediaYAML:
|
case mediaYAML:
|
||||||
if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil {
|
if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil {
|
||||||
s.log.Err("server.Server.renderHTML: Failed to render to '%s': %v", *page.RawFmt, err)
|
s.log.Err("server.Server.renderHTML: Failed to render to '%s' for '%s': %v", *page.RawFmt, page.ReqUUID.String(), err)
|
||||||
|
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if b != nil {
|
if b != nil {
|
||||||
@@ -581,7 +609,7 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
|
|||||||
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
|
resp.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
if err = tpl.ExecuteTemplate(resp, "index", page); err != nil {
|
if err = tpl.ExecuteTemplate(resp, "index", page); err != nil {
|
||||||
s.log.Err("server.Server.renderHTML: Failed to render template: %v", err)
|
s.log.Err("server.Server.renderHTML: Failed to render template for '%s': %v", page.ReqUUID.String(), err)
|
||||||
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -595,13 +623,13 @@ func (s *Server) renderXML(page *Page, resp http.ResponseWriter) (err error) {
|
|||||||
|
|
||||||
if page.DoIndent {
|
if page.DoIndent {
|
||||||
if b, err = xml.MarshalIndent(page.Info, "", page.Indent); err != nil {
|
if b, err = xml.MarshalIndent(page.Info, "", page.Indent); err != nil {
|
||||||
s.log.Err("server.Server.renderXML: Failed to render to indented XML: %v", err)
|
s.log.Err("server.Server.renderXML: Failed to render to indented XML for '%s': %v", page.ReqUUID.String(), err)
|
||||||
http.Error(resp, "ERROR: Failed to render XML", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render XML", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if b, err = xml.Marshal(page.Info); err != nil {
|
if b, err = xml.Marshal(page.Info); err != nil {
|
||||||
s.log.Err("server.Server.renderXML: Failed to render to XML: %v", err)
|
s.log.Err("server.Server.renderXML: Failed to render to XML for '%s': %v", page.ReqUUID.String(), err)
|
||||||
http.Error(resp, "ERROR: Failed to render XML", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render XML", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -610,7 +638,7 @@ func (s *Server) renderXML(page *Page, resp http.ResponseWriter) (err error) {
|
|||||||
resp.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
resp.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
||||||
|
|
||||||
if _, err = resp.Write(b); err != nil {
|
if _, err = resp.Write(b); err != nil {
|
||||||
s.log.Err("server.Server.renderXML: Failed to send XML: %v", err)
|
s.log.Err("server.Server.renderXML: Failed to send XML to '%s': %v", page.ReqUUID.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,15 +650,15 @@ func (s *Server) renderYML(page *Page, resp http.ResponseWriter) (err error) {
|
|||||||
var b []byte
|
var b []byte
|
||||||
|
|
||||||
if b, err = yaml.Marshal(page.Info); err != nil {
|
if b, err = yaml.Marshal(page.Info); err != nil {
|
||||||
s.log.Err("server.Server.renderJSON: Failed to render to JSON: %v", err)
|
s.log.Err("server.Server.renderYML: Failed to render to YAML for '%s': %v", page.ReqUUID.String(), err)
|
||||||
http.Error(resp, "ERROR: Failed to render JSON", http.StatusInternalServerError)
|
http.Error(resp, "ERROR: Failed to render YAML", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Header().Set("Content-Type", "application/yaml")
|
resp.Header().Set("Content-Type", "application/yaml")
|
||||||
|
|
||||||
if _, err = resp.Write(b); err != nil {
|
if _, err = resp.Write(b); err != nil {
|
||||||
s.log.Err("server.Server.renderJSON: Failed to send JSON: %v", err)
|
s.log.Err("server.Server.renderYML: Failed to send YAML to '%s': %v", page.ReqUUID.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,22 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
`fmt`
|
`fmt`
|
||||||
|
`html/template`
|
||||||
|
`net/netip`
|
||||||
`strings`
|
`strings`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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) {
|
func getTitle(subPage string) (title string) {
|
||||||
|
|
||||||
if subPage == "" || subPage == "index" {
|
if subPage == "" || subPage == "index" {
|
||||||
@@ -16,3 +29,10 @@ func getTitle(subPage string) (title string) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func safeUrl(urlStr string) (u template.URL) {
|
||||||
|
|
||||||
|
u = template.URL(urlStr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,9 +4,35 @@
|
|||||||
{{- $linkico := "🔗" }}
|
{{- $linkico := "🔗" }}
|
||||||
<h2 id="client">Client/Browser Information<a href="#client">{{ $linkico }}</a></h2>
|
<h2 id="client">Client/Browser Information<a href="#client">{{ $linkico }}</a></h2>
|
||||||
<p>
|
<p>
|
||||||
<b>Your IP Address is <i><a href="https://ipinfo.io/{{ $page.Info.IP.String }}">{{ $page.Info.IP.String }}</a></i>.</b>
|
<b>Your IP{{ getIpver $page.Info.IP }} Address is <i>{{ $page.Info.IP.String }}</i> <i>(<a href="https://ipinfo.io/{{ $page.Info.IP.String }}">more info</a></i>).</b>
|
||||||
<br/>
|
<br/>
|
||||||
<i>You are connecting with port <b>{{ $page.Info.Port }}</b> outbound.</i>
|
<i>You are connecting with port <b>{{ $page.Info.Port }}</b> outbound.</i>
|
||||||
|
{{- if $page.HasAltURL }}
|
||||||
|
<div id="alt_addr"></div>
|
||||||
|
<script>
|
||||||
|
async function fetchAltAddr() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('{{ safeUrl $page.AltURL }}', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (resp.ok) {
|
||||||
|
const dat = await resp.json();
|
||||||
|
const addr = dat.ip;
|
||||||
|
const infoDiv = document.getElementById('alt_addr');
|
||||||
|
infoDiv.innerHTML = `
|
||||||
|
<i>You also have IP{{ $page.AltVer }} address <b>${addr}</b> (<a href="https://ipinfo.io/${addr}">more info</a>).</i>
|
||||||
|
<br/>
|
||||||
|
<i>Try loading <b><a href="{{ $page.AltURL }}">{{ $page.AltURL }}</a></b> to check your secondary address.</i>`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.info('Did not fetch alternate address: ', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchAltAddr()
|
||||||
|
</script>
|
||||||
|
{{- end }}
|
||||||
</p>
|
</p>
|
||||||
{{- if $page.Raw }}
|
{{- if $page.Raw }}
|
||||||
<h3 id="client_raw">Raw Block ({{ $page.RawFmt }})<a href="#client_raw">{{ $linkico }}</a></h3>
|
<h3 id="client_raw">Raw Block ({{ $page.RawFmt }})<a href="#client_raw">{{ $linkico }}</a></h3>
|
||||||
@@ -42,7 +68,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{- /*
|
{{- /*
|
||||||
<ul>
|
<ul>
|
||||||
|
|
||||||
{{- $flds := $ua.ToMap }}
|
{{- $flds := $ua.ToMap }}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
{{- $linkico := "🔗" -}}
|
{{- $linkico := "🔗" -}}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<!-- Request ID: {{ .ReqUUID.String }} -->
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{{ getTitle $page.PageType }}</title>
|
<title>{{ getTitle $page.PageType }}</title>
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`encoding/xml`
|
"encoding/xml"
|
||||||
`net`
|
"net"
|
||||||
`net/http`
|
"net/http"
|
||||||
`net/url`
|
"net/netip"
|
||||||
`os`
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
`github.com/mileusna/useragent`
|
"github.com/google/uuid"
|
||||||
`r00t2.io/clientinfo/args`
|
"github.com/mileusna/useragent"
|
||||||
`r00t2.io/goutils/logging`
|
"r00t2.io/clientinfo/args"
|
||||||
|
"r00t2.io/goutils/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type outerRenderer func(page *Page, resp http.ResponseWriter) (err error)
|
type (
|
||||||
type XmlHeaders map[string][]string
|
outerRenderer func(page *Page, resp http.ResponseWriter) (err error)
|
||||||
|
XmlHeaders map[string][]string
|
||||||
|
)
|
||||||
|
|
||||||
// R00tInfo is the structure of data returned to the client.
|
// R00tInfo is the structure of data returned to the client.
|
||||||
type R00tInfo struct {
|
type R00tInfo struct {
|
||||||
@@ -22,7 +26,7 @@ type R00tInfo struct {
|
|||||||
// Client is the UA/Client info, if any passed by the client.
|
// 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"`
|
Client []*R00tClient `json:"ua,omitempty" xml:"ua,omitempty" yaml:"Client/User Agent,omitempty"`
|
||||||
// IP is the client IP address.
|
// 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 is the client's port number.
|
||||||
Port uint16 `json:"port" xml:"port,attr" yaml:"Client Port"`
|
Port uint16 `json:"port" xml:"port,attr" yaml:"Client Port"`
|
||||||
// Headers are the collection of the request headers sent by the client.
|
// Headers are the collection of the request headers sent by the client.
|
||||||
@@ -84,19 +88,25 @@ type Page struct {
|
|||||||
Indent string
|
Indent string
|
||||||
// DoIndent indicates if indenting was enabled.
|
// DoIndent indicates if indenting was enabled.
|
||||||
DoIndent bool
|
DoIndent bool
|
||||||
|
// ReqUUID is a unique request ID assigned.
|
||||||
|
ReqUUID uuid.UUID
|
||||||
|
srv *Server
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
log logging.Logger
|
log logging.Logger
|
||||||
args *args.Args
|
args *args.Args
|
||||||
listenUri *url.URL
|
listenUri *url.URL
|
||||||
isHttp bool
|
isHttp bool
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
sock net.Listener
|
sock net.Listener
|
||||||
doneChan chan bool
|
doneChan chan bool
|
||||||
stopChan chan os.Signal
|
stopChan chan os.Signal
|
||||||
reloadChan chan os.Signal
|
reloadChan chan os.Signal
|
||||||
isStopping bool
|
v4Url *url.URL
|
||||||
|
v6Url *url.URL
|
||||||
|
corsOrigins map[string]struct{}
|
||||||
|
isStopping bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.iana.org/assignments/media-types/media-types.xhtml
|
// https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||||
|
|||||||
Reference in New Issue
Block a user