Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1d8ea34a6
|
||
|
|
e101758187
|
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.24.5
|
|||||||
require (
|
require (
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0
|
github.com/coreos/go-systemd/v22 v22.5.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/sys v0.34.0
|
golang.org/x/sys v0.34.0
|
||||||
r00t2.io/sysutils v1.14.0
|
r00t2.io/sysutils v1.14.0
|
||||||
)
|
)
|
||||||
|
|||||||
3
go.sum
3
go.sum
@@ -5,9 +5,12 @@ github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYC
|
|||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
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=
|
||||||
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
r00t2.io/sysutils v1.14.0 h1:Lrio3uPi9CuUdg+sg3WkVV1CK/qcOpV9GdFCGFG1KJs=
|
||||||
r00t2.io/sysutils v1.14.0/go.mod h1:ZJ7gZxFVQ7QIokQ5fPZr7wl0XO5Iu+LqtE8j3ciRINw=
|
r00t2.io/sysutils v1.14.0/go.mod h1:ZJ7gZxFVQ7QIokQ5fPZr7wl0XO5Iu+LqtE8j3ciRINw=
|
||||||
|
|||||||
13
netx/consts_nix.go
Normal file
13
netx/consts_nix.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package netx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`golang.org/x/sys/unix`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AFUnspec uint16 = unix.AF_UNSPEC
|
||||||
|
AFInet uint16 = unix.AF_INET
|
||||||
|
AFInet6 uint16 = unix.AF_INET6
|
||||||
|
)
|
||||||
13
netx/consts_windows.go
Normal file
13
netx/consts_windows.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package netx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`golang.org/x/sys/windows`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AFUnspec uint16 = windows.AF_UNSPEC
|
||||||
|
AFInet uint16 = windows.AF_INET
|
||||||
|
AFInet6 uint16 = windows.AF_INET6
|
||||||
|
)
|
||||||
10
netx/errors.go
Normal file
10
netx/errors.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package netx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadMask4Str error = errors.New("netx: unknown/bad IPv4 netmask dotted quad")
|
||||||
|
ErrBadNetFam error = errors.New("netx: unknown/bad IP network family")
|
||||||
|
)
|
||||||
410
netx/funcs.go
Normal file
410
netx/funcs.go
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
package netx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`math/bits`
|
||||||
|
`net`
|
||||||
|
`net/netip`
|
||||||
|
`strconv`
|
||||||
|
`strings`
|
||||||
|
|
||||||
|
`go4.org/netipx`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
AddrRfc returns an RFC-friendly string from an IP address ([net/netip.Addr]).
|
||||||
|
|
||||||
|
If addr is an IPv4 address, it will simmply be the string representation (e.g. "203.0.113.1").
|
||||||
|
|
||||||
|
If addr is an IPv6 address, it will be enclosed in brackets (e.g. "[2001:db8::1]").
|
||||||
|
|
||||||
|
If the version can't be determined, rfcStr will be an empty string.
|
||||||
|
*/
|
||||||
|
func AddrRfc(addr netip.Addr) (rfcStr string) {
|
||||||
|
|
||||||
|
if addr.Is4() {
|
||||||
|
rfcStr = addr.String()
|
||||||
|
} else if addr.Is6() {
|
||||||
|
rfcStr = "[" + addr.String() + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cidr4ToIPMask takes an IPv4 CIDR/bit size/prefix length and returns the [net.IPMask].
|
||||||
|
It's (essentially) the inverse of [net.IPMask.Size].
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Cidr4ToMask]
|
||||||
|
* [Cidr4ToStr]
|
||||||
|
|
||||||
|
Inverse of [IPMask4ToCidr].
|
||||||
|
*/
|
||||||
|
func Cidr4ToIPMask(cidr uint8) (ipMask net.IPMask, err error) {
|
||||||
|
|
||||||
|
if cidr > 32 {
|
||||||
|
err = ErrBadNetFam
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ipMask = net.CIDRMask(int(cidr), 32)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cidr4ToMask takes an IPv4 CIDR/bit size/prefix length and returns the netmask *in bitmask form*.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Cidr4ToIPMask]
|
||||||
|
* [Cidr4ToStr]
|
||||||
|
|
||||||
|
Inverse of [Mask4ToCidr].
|
||||||
|
*/
|
||||||
|
func Cidr4ToMask(cidr uint8) (mask uint32, err error) {
|
||||||
|
|
||||||
|
if cidr > 32 {
|
||||||
|
err = ErrBadNetFam
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// COULD do (1 << 32) - (1 << (32 - ip.Bits())) instead but in EXTREME edge cases that could cause an overflow.
|
||||||
|
// We're basically converting the CIDR size ("number of bits"/"number of ones") to an integer mask ("number AS bits")
|
||||||
|
mask = uint32(0xffffffff) << uint32(32-cidr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cidr4ToStr is a convenience wrapper around [IPMask4ToStr]([Cidr4ToMask](cidr)).
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Cidr4ToIPMask]
|
||||||
|
* [Cidr4ToMask]
|
||||||
|
|
||||||
|
Inverse of [Mask4StrToCidr].
|
||||||
|
*/
|
||||||
|
func Cidr4ToStr(cidr uint8) (maskStr string, err error) {
|
||||||
|
|
||||||
|
var ipMask net.IPMask
|
||||||
|
|
||||||
|
if ipMask, err = Cidr4ToIPMask(cidr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if maskStr, err = IPMask4ToStr(ipMask); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetAddrFamily returns the network family of a [net/netip.Addr].
|
||||||
|
|
||||||
|
See also [GetIpFamily].
|
||||||
|
|
||||||
|
If addr is not a "valid" IP address or the version can't be determined, family will be AFUnspec (usually 0x00/0).
|
||||||
|
*/
|
||||||
|
func GetAddrFamily(addr netip.Addr) (family uint16) {
|
||||||
|
|
||||||
|
family = AFUnspec
|
||||||
|
|
||||||
|
if !addr.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.Is4() {
|
||||||
|
family = AFInet
|
||||||
|
} else if addr.Is6() {
|
||||||
|
family = AFInet6
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetIpFamily returns the network family of a [net.IP].
|
||||||
|
|
||||||
|
See also [GetAddrFamily].
|
||||||
|
|
||||||
|
If ip is not a "valid" IP address or the version can't be determined,
|
||||||
|
family will be [golang.org/x/sys/unix.AF_UNSPEC] or [golang.org/x/sys/windows.AF_UNSPEC] depending on platform (usually 0x00/0).
|
||||||
|
*/
|
||||||
|
func GetIpFamily(ip net.IP) (family uint16) {
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
var addr netip.Addr
|
||||||
|
|
||||||
|
if addr, ok = netipx.FromStdIP(ip); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
family = GetAddrFamily(addr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IpRfc returns an RFC-friendly string from an IP address ([net.IP]).
|
||||||
|
|
||||||
|
If ip is an IPv4 address, it will simmply be the string representation (e.g. "203.0.113.1").
|
||||||
|
|
||||||
|
If ip is an IPv6 address, it will be enclosed in brackets (e.g. "[2001:db8::1]").
|
||||||
|
|
||||||
|
If the version can't be determined, rfcStr will be an empty string.
|
||||||
|
*/
|
||||||
|
func IpRfc(ip net.IP) (rfcStr string) {
|
||||||
|
|
||||||
|
if ip.To4() != nil {
|
||||||
|
rfcStr = ip.To4().String()
|
||||||
|
} else if ip.To16() != nil {
|
||||||
|
rfcStr = "[" + ip.To16().String() + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IPMask4ToCidr returns a CIDR prefix size/bit size/bit length from a [net.IPMask].
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [IPMask4ToMask]
|
||||||
|
* [IPMask4ToStr]
|
||||||
|
|
||||||
|
Inverse of [Cidr4ToIPMask].
|
||||||
|
*/
|
||||||
|
func IPMask4ToCidr(ipMask net.IPMask) (cidr uint8, err error) {
|
||||||
|
|
||||||
|
var ones int
|
||||||
|
var total int
|
||||||
|
|
||||||
|
ones, total = ipMask.Size()
|
||||||
|
|
||||||
|
if total != 32 {
|
||||||
|
err = ErrBadNetFam
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ones > 32 {
|
||||||
|
err = ErrBadNetFam
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cidr = uint8(ones)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IPMask4ToMask returns the mask *in bitmask form* from a [net.IPMask].
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [IPMask4ToCidr]
|
||||||
|
* [IPMask4ToStr]
|
||||||
|
|
||||||
|
Inverse of [Mask4ToIPMask].
|
||||||
|
*/
|
||||||
|
func IPMask4ToMask(ipMask net.IPMask) (mask uint32, err error) {
|
||||||
|
|
||||||
|
var cidr uint8
|
||||||
|
|
||||||
|
if cidr, err = IPMask4ToCidr(ipMask); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mask, err = Cidr4ToMask(cidr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IPMask4ToStr returns a string representation of an IPv4 netmask (e.g. "255.255.255.0" for a /24) from a [net.IPMask].
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [IPMask4ToCidr]
|
||||||
|
* [IPMask4ToMask]
|
||||||
|
|
||||||
|
Inverse of [Mask4StrToIPMask].
|
||||||
|
*/
|
||||||
|
func IPMask4ToStr(ipMask net.IPMask) (maskStr string, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var b []byte
|
||||||
|
var quads []string = make([]string, 4)
|
||||||
|
|
||||||
|
b = []byte(ipMask)
|
||||||
|
if len(b) != 4 {
|
||||||
|
err = ErrBadNetFam
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx = 0; idx < len(b); idx++ {
|
||||||
|
quads[idx] = strconv.Itoa(int(b[idx]))
|
||||||
|
}
|
||||||
|
|
||||||
|
maskStr = strings.Join(quads, ".")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mask4ToCidr converts an IPv4 netmask *in bitmask form* to a CIDR prefix size/bit size/bit length.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Mask4ToIPMask]
|
||||||
|
* [Mask4ToStr]
|
||||||
|
|
||||||
|
Inverse of [Cidr4ToMask].
|
||||||
|
*/
|
||||||
|
func Mask4ToCidr(mask uint32) (cidr uint8, err error) {
|
||||||
|
|
||||||
|
cidr = 32 - uint8(bits.LeadingZeros32(mask))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mask4ToIPMask returns mask *in bitmask form* as a [net.IPMask].
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Mask4ToCidr]
|
||||||
|
* [Mask4ToStr]
|
||||||
|
|
||||||
|
Inverse of [IPMask4ToMask].
|
||||||
|
*/
|
||||||
|
func Mask4ToIPMask(mask uint32) (ipMask net.IPMask, err error) {
|
||||||
|
|
||||||
|
var cidr uint8
|
||||||
|
|
||||||
|
if cidr, err = Mask4ToCidr(mask); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ipMask = net.CIDRMask(int(cidr), 32)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mask4ToStr returns a string representation of an IPv4 netmask (e.g. "255.255.255.0" for a /24) from a netmask *in bitmask form*.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Mask4ToCidr]
|
||||||
|
* [Mask4ToIPMask]
|
||||||
|
|
||||||
|
Inverse of [Mask4StrToMask].
|
||||||
|
*/
|
||||||
|
func Mask4ToStr(mask uint32) (maskStr string, err error) {
|
||||||
|
|
||||||
|
var ipMask net.IPMask
|
||||||
|
|
||||||
|
if ipMask, err = Mask4ToIPMask(mask); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if maskStr, err = IPMask4ToStr(ipMask); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mask4StrToCidr parses a "dotted-quad" IPv4 netmask (e.g. "255.255.255.0" for a /24) and returns am IPv4 CIDR/bit size/prefix length.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Mask4StrToIPMask]
|
||||||
|
* [Mask4StrToMask]
|
||||||
|
|
||||||
|
Inverse of [Cidr4ToMaskStr].
|
||||||
|
*/
|
||||||
|
func Mask4StrToCidr(maskStr string) (cidr uint8, err error) {
|
||||||
|
|
||||||
|
var ipMask net.IPMask
|
||||||
|
|
||||||
|
if ipMask, err = Mask4StrToIPMask(maskStr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cidr, err = IPMask4ToCidr(ipMask); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mask4StrToIPMask parses a "dotted-quad" IPv4 netmask (e.g. "255.255.255.0" for a /24) and returns a [net.IPMask].
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Mask4StrToCidr]
|
||||||
|
* [Mask4StrToMask]
|
||||||
|
|
||||||
|
Inverse of [IPMask4ToStr].
|
||||||
|
*/
|
||||||
|
func Mask4StrToIPMask(maskStr string) (mask net.IPMask, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var s string
|
||||||
|
var u64 uint64
|
||||||
|
var b []byte = make([]byte, 4)
|
||||||
|
var sl []string = strings.Split(maskStr, ".")
|
||||||
|
|
||||||
|
if len(sl) != 4 {
|
||||||
|
err = ErrBadMask4Str
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A net.IPMask is just a []byte.
|
||||||
|
for idx = 0; idx < len(sl); idx++ {
|
||||||
|
s = sl[idx]
|
||||||
|
if u64, err = strconv.ParseUint(s, 10, 8); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b[idx] = byte(u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
mask = net.IPMask(b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mask4StrToMask parses a "dotted-quad" IPv4 netmask (e.g. "255.255.255.0" for a /24) and returns a netmask *in bitmask form*.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [Mask4StrToCidr]
|
||||||
|
* [Mask4StrToIPMask]
|
||||||
|
|
||||||
|
Inverse of [Mask4ToStr].
|
||||||
|
*/
|
||||||
|
func Mask4StrToMask(maskStr string) (mask uint32, err error) {
|
||||||
|
|
||||||
|
var ipMask net.IPMask
|
||||||
|
|
||||||
|
if ipMask, err = Mask4StrToIPMask(maskStr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mask, err = IPMask4ToMask(ipMask); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
134
netx/funcs_test.go
Normal file
134
netx/funcs_test.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package netx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`math`
|
||||||
|
`net`
|
||||||
|
`net/netip`
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncsIP(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var ip net.IP
|
||||||
|
var addr netip.Addr
|
||||||
|
var ipFamily uint16
|
||||||
|
var tgtFamily uint16
|
||||||
|
var addrFamily uint16
|
||||||
|
|
||||||
|
// IPv4 on even indexes, IPv6 on odd.
|
||||||
|
for idx, s := range []string{
|
||||||
|
"203.0.113.10",
|
||||||
|
"2001:db8::203:0:113:10",
|
||||||
|
} {
|
||||||
|
if ip = net.ParseIP(s); ip == nil {
|
||||||
|
t.Fatalf("ip %s not valid", s)
|
||||||
|
}
|
||||||
|
if addr, err = netip.ParseAddr(s); err != nil {
|
||||||
|
t.Fatalf("addr %s not valid", s)
|
||||||
|
}
|
||||||
|
ipFamily = GetIpFamily(ip)
|
||||||
|
addrFamily = GetAddrFamily(addr)
|
||||||
|
if ipFamily == AFUnspec {
|
||||||
|
t.Fatalf("GetIpFamily: Failed on IP %s (unspecified family)", s)
|
||||||
|
}
|
||||||
|
if addrFamily == AFUnspec {
|
||||||
|
t.Fatalf("GetAddrFamily: Failed on IP %s (unspecified family)", s)
|
||||||
|
}
|
||||||
|
switch idx%2 == 0 {
|
||||||
|
case true:
|
||||||
|
tgtFamily = AFInet
|
||||||
|
case false:
|
||||||
|
tgtFamily = AFInet6
|
||||||
|
}
|
||||||
|
if ipFamily != tgtFamily {
|
||||||
|
t.Fatalf("GetIpFamily: Failed on IP %s (expected %d, got %d)", s, AFInet, tgtFamily)
|
||||||
|
}
|
||||||
|
if addrFamily != tgtFamily {
|
||||||
|
t.Fatalf("GetAddrFamily: Failed on IP %s (expected %d, got %d)", s, AFInet, tgtFamily)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncsMask(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var cidr uint8
|
||||||
|
var mask uint32
|
||||||
|
var maskStr string
|
||||||
|
var ipMask net.IPMask
|
||||||
|
|
||||||
|
var cidrTgt uint8 = 32
|
||||||
|
var maskTgt uint32 = math.MaxUint32
|
||||||
|
var maskStrTgt string = "255.255.255.255"
|
||||||
|
var ipMaskTgt net.IPMask = net.IPMask{255, 255, 255, 255}
|
||||||
|
|
||||||
|
// To CIDR
|
||||||
|
if cidr, err = Mask4ToCidr(maskTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if cidr != cidrTgt {
|
||||||
|
t.Fatalf("Mask4ToCidr: cidr %d != cidrTgt %d", cidr, cidrTgt)
|
||||||
|
}
|
||||||
|
if cidr, err = IPMask4ToCidr(ipMaskTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if cidr != cidrTgt {
|
||||||
|
t.Fatalf("IPMask4ToCidr: cidr %d != cidrTgt %d", cidr, cidrTgt)
|
||||||
|
}
|
||||||
|
if cidr, err = Mask4StrToCidr(maskStrTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if cidr != cidrTgt {
|
||||||
|
t.Fatalf("Mask4StrToCidr cidr %d != cidrTgt %d", cidr, cidrTgt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// To net.IPMask
|
||||||
|
if ipMask, err = Cidr4ToIPMask(cidrTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if ipMaskTgt.String() != ipMask.String() {
|
||||||
|
t.Fatalf("Cidr4ToIPMask ipMask %s != ipMaskTgt %s", ipMask.String(), ipMaskTgt.String())
|
||||||
|
}
|
||||||
|
if ipMask, err = Mask4ToIPMask(maskTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if ipMaskTgt.String() != ipMask.String() {
|
||||||
|
t.Fatalf("Mask4ToIPMask ipMask %s != ipMaskTgt %s", ipMask.String(), ipMaskTgt.String())
|
||||||
|
}
|
||||||
|
if ipMask, err = Mask4StrToIPMask(maskStrTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if ipMaskTgt.String() != ipMask.String() {
|
||||||
|
t.Fatalf("Mask4StrToIPMask ipMask %s != ipMaskTgt %s", ipMask.String(), ipMaskTgt.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// To bitmask
|
||||||
|
if mask, err = Cidr4ToMask(cidrTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if mask != maskTgt {
|
||||||
|
t.Fatalf("Cidr4ToMask mask %d != maskTgt %d", mask, maskTgt)
|
||||||
|
}
|
||||||
|
if mask, err = IPMask4ToMask(ipMaskTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if mask != maskTgt {
|
||||||
|
t.Fatalf("IPMask4ToMask mask %d != maskTgt %d", mask, maskTgt)
|
||||||
|
}
|
||||||
|
if mask, err = Mask4StrToMask(maskStrTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if mask != maskTgt {
|
||||||
|
t.Fatalf("Mask4StrToMask mask %d != maskTgt %d", mask, maskTgt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// To string
|
||||||
|
if maskStr, err = Cidr4ToStr(cidrTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if maskStr != maskStrTgt {
|
||||||
|
t.Fatalf("Cidr4ToStr maskStr %s != maskStrTgt %s", maskStr, maskStrTgt)
|
||||||
|
}
|
||||||
|
if maskStr, err = IPMask4ToStr(ipMaskTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if maskStr != maskStrTgt {
|
||||||
|
t.Fatalf("IPMask4ToStr maskStr %s != maskStrTgt %s", maskStr, maskStrTgt)
|
||||||
|
}
|
||||||
|
if maskStr, err = Mask4ToStr(maskTgt); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if maskStr != maskStrTgt {
|
||||||
|
t.Fatalf("Mask4ToStr maskStr %s != maskStrTgt %s", maskStr, maskStrTgt)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
stringsx/TODO
Normal file
5
stringsx/TODO
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
- Banner struct, with .Format(s string) method
|
||||||
|
-- draw border around multiline s
|
||||||
|
-- i have a version in python somewhere that does this, should dig that up
|
||||||
|
|
||||||
|
- create bytesx package that duplicates the functions here?
|
||||||
11
stringsx/consts.go
Normal file
11
stringsx/consts.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package stringsx
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefMaskStr is the string used as the default maskStr if left empty in [Redact].
|
||||||
|
DefMaskStr string = "***"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefIndentStr is the string used as the default indent if left empty in [Indent].
|
||||||
|
DefIndentStr string = "\t"
|
||||||
|
)
|
||||||
4
stringsx/doc.go
Normal file
4
stringsx/doc.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package stringsx aims to extend functionality of the stdlib [strings] module.
|
||||||
|
*/
|
||||||
|
package stringsx
|
||||||
249
stringsx/funcs.go
Normal file
249
stringsx/funcs.go
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
package stringsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`strings`
|
||||||
|
`unicode`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Indent takes string s and indents it with string `indent` `level` times.
|
||||||
|
|
||||||
|
If indent is an empty string, [DefIndentStr] will be used.
|
||||||
|
|
||||||
|
If ws is true, lines consisting of only whitespace will be indented as well.
|
||||||
|
(To then trim any extraneous trailing space, you may want to use [TrimSpaceRight]
|
||||||
|
or [TrimLines].)
|
||||||
|
|
||||||
|
If empty is true, lines with no content will be replaced with lines that purely
|
||||||
|
consist of (indent * level) (otherwise they will be left as empty lines).
|
||||||
|
|
||||||
|
This function can also be used to prefix lines with arbitrary strings as well.
|
||||||
|
e.g:
|
||||||
|
|
||||||
|
Indent("foo\nbar\nbaz\n", "# ", 1, false, false)
|
||||||
|
|
||||||
|
would yield:
|
||||||
|
|
||||||
|
# foo
|
||||||
|
# bar
|
||||||
|
# baz
|
||||||
|
<empty line>
|
||||||
|
|
||||||
|
thus allowing you to "comment out" multiple lines at once.
|
||||||
|
*/
|
||||||
|
func Indent(s, indent string, level uint, ws, empty bool) (indented string) {
|
||||||
|
|
||||||
|
var i string
|
||||||
|
var nl string
|
||||||
|
var endsNewline bool
|
||||||
|
var sb strings.Builder
|
||||||
|
var lineStripped string
|
||||||
|
|
||||||
|
if indent == "" {
|
||||||
|
indent = DefIndentStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// This condition functionally won't do anything, so just return the input as-is.
|
||||||
|
if level == 0 {
|
||||||
|
indented = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i = strings.Repeat(indent, int(level))
|
||||||
|
|
||||||
|
// This condition functionally won't do anything, so just return the input as-is.
|
||||||
|
if s == "" {
|
||||||
|
if empty {
|
||||||
|
indented = i
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for line := range strings.Lines(s) {
|
||||||
|
lineStripped = strings.TrimSpace(line)
|
||||||
|
nl = getNewLine(line)
|
||||||
|
endsNewline = nl != ""
|
||||||
|
// fmt.Printf("%#v => %#v\n", line, lineStripped)
|
||||||
|
if lineStripped == "" {
|
||||||
|
// fmt.Printf("WS/EMPTY LINE (%#v) (ws %v, empty %v): \n", s, ws, empty)
|
||||||
|
if line != (lineStripped + nl) {
|
||||||
|
// whitespace-only line
|
||||||
|
if ws {
|
||||||
|
sb.WriteString(i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// empty line
|
||||||
|
if empty {
|
||||||
|
sb.WriteString(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// non-empty/non-whitespace-only line.
|
||||||
|
sb.WriteString(i + line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it ends with a trailing newline and nothing after, strings.Lines() will skip the last (empty) line.
|
||||||
|
if endsNewline && empty {
|
||||||
|
nl = getNewLine(s)
|
||||||
|
sb.WriteString(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
indented = sb.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Redact provides a "masked" version of string s (e.g. `my_terrible_password` -> `my****************rd`).
|
||||||
|
|
||||||
|
maskStr is the character or sequence of characters
|
||||||
|
to repeat for every masked character of s.
|
||||||
|
If an empty string, the default [DefMaskStr] will be used.
|
||||||
|
(maskStr does not need to be a single character.
|
||||||
|
It is recommended to use a multi-char mask to help obfuscate a string's length.)
|
||||||
|
|
||||||
|
leading specifies the number of leading characters of s to leave *unmasked*.
|
||||||
|
If 0, no leading characters will be unmasked.
|
||||||
|
|
||||||
|
trailing specifies the number of trailing characters of s to leave *unmasked*.
|
||||||
|
if 0, no trailing characters will be unmasked.
|
||||||
|
|
||||||
|
newlines, if true, will preserve newline characters - otherwise
|
||||||
|
they will be treated as regular characters.
|
||||||
|
|
||||||
|
As a safety precaution, if:
|
||||||
|
|
||||||
|
len(s) <= (leading + trailing)
|
||||||
|
|
||||||
|
then the entire string will be *masked* and no unmasking will be performed.
|
||||||
|
*/
|
||||||
|
func Redact(s, maskStr string, leading, trailing uint, newlines bool) (redacted string) {
|
||||||
|
|
||||||
|
var nl string
|
||||||
|
var numMasked int
|
||||||
|
var sb strings.Builder
|
||||||
|
var endIdx int = int(leading)
|
||||||
|
|
||||||
|
// This condition functionally won't do anything, so just return the input as-is.
|
||||||
|
if s == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if maskStr == "" {
|
||||||
|
maskStr = DefMaskStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if newlines {
|
||||||
|
for line := range strings.Lines(s) {
|
||||||
|
nl = getNewLine(line)
|
||||||
|
sb.WriteString(
|
||||||
|
Redact(
|
||||||
|
strings.TrimSuffix(line, nl), maskStr, leading, trailing, false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
sb.WriteString(nl)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(s) <= int(leading+trailing) {
|
||||||
|
redacted = strings.Repeat(maskStr, len(s))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leading == 0 && trailing == 0 {
|
||||||
|
redacted = strings.Repeat(maskStr, len(s))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
numMasked = len(s) - int(leading+trailing)
|
||||||
|
endIdx = endIdx + numMasked
|
||||||
|
|
||||||
|
if leading > 0 {
|
||||||
|
sb.WriteString(s[:int(leading)])
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(strings.Repeat(maskStr, numMasked))
|
||||||
|
|
||||||
|
if trailing > 0 {
|
||||||
|
sb.WriteString(s[endIdx:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redacted = sb.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TrimLines is like [strings.TrimSpace] but operates on *each line* of s.
|
||||||
|
It is *NIX-newline (`\n`) vs. Windows-newline (`\r\n`) agnostic.
|
||||||
|
The first encountered linebreak (`\n` vs. `\r\n`) are assumed to be
|
||||||
|
the canonical linebreak for the rest of s.
|
||||||
|
|
||||||
|
left, if true, performs a [TrimSpaceLeft] on each line (retaining the newline).
|
||||||
|
|
||||||
|
right, if true, performs a [TrimSpaceRight] on each line (retaining the newline).
|
||||||
|
*/
|
||||||
|
func TrimLines(s string, left, right bool) (trimmed string) {
|
||||||
|
|
||||||
|
var sl string
|
||||||
|
var nl string
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
// These conditions functionally won't do anything, so just return the input as-is.
|
||||||
|
if s == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !left && !right {
|
||||||
|
trimmed = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for line := range strings.Lines(s) {
|
||||||
|
nl = getNewLine(line)
|
||||||
|
sl = strings.TrimSuffix(line, nl)
|
||||||
|
if left && right {
|
||||||
|
sl = strings.TrimSpace(sl)
|
||||||
|
} else if left {
|
||||||
|
sl = TrimSpaceLeft(sl)
|
||||||
|
} else if right {
|
||||||
|
sl = TrimSpaceRight(sl)
|
||||||
|
}
|
||||||
|
sb.WriteString(sl + nl)
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmed = sb.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimSpaceLeft is like [strings.TrimSpace] but only removes leading whitespace from string s.
|
||||||
|
func TrimSpaceLeft(s string) (trimmed string) {
|
||||||
|
|
||||||
|
trimmed = strings.TrimLeftFunc(s, unicode.IsSpace)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TrimSpaceRight is like [strings.TrimSpace] but only removes trailing whitespace from string s.
|
||||||
|
*/
|
||||||
|
func TrimSpaceRight(s string) (trimmed string) {
|
||||||
|
|
||||||
|
trimmed = strings.TrimRightFunc(s, unicode.IsSpace)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNewLine is too unpredictable to be used outside of this package so it isn't exported.
|
||||||
|
func getNewLine(s string) (nl string) {
|
||||||
|
|
||||||
|
if strings.HasSuffix(s, "\r\n") {
|
||||||
|
nl = "\r\n"
|
||||||
|
} else if strings.HasSuffix(s, "\n") {
|
||||||
|
nl = "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
451
stringsx/funcs_test.go
Normal file
451
stringsx/funcs_test.go
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
package stringsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
testIndentSet struct {
|
||||||
|
name string
|
||||||
|
orig string
|
||||||
|
indent string
|
||||||
|
lvl uint
|
||||||
|
ws bool
|
||||||
|
empty bool
|
||||||
|
tgt string
|
||||||
|
}
|
||||||
|
testRedactSet struct {
|
||||||
|
name string
|
||||||
|
orig string
|
||||||
|
leading uint
|
||||||
|
trailing uint
|
||||||
|
tgt string
|
||||||
|
newline bool
|
||||||
|
mask string // defaults to DefMaskStr.
|
||||||
|
}
|
||||||
|
testTrimLinesSet struct {
|
||||||
|
name string
|
||||||
|
orig string
|
||||||
|
left bool
|
||||||
|
right bool
|
||||||
|
tgt string
|
||||||
|
}
|
||||||
|
testTrimSet struct {
|
||||||
|
name string
|
||||||
|
orig string
|
||||||
|
tgt string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIndent(t *testing.T) {
|
||||||
|
|
||||||
|
var out string
|
||||||
|
var tests []testIndentSet = []testIndentSet{
|
||||||
|
testIndentSet{
|
||||||
|
name: "standard, no trailing newline",
|
||||||
|
orig: "foo\nbar\nbaz",
|
||||||
|
indent: "",
|
||||||
|
lvl: 1,
|
||||||
|
ws: false,
|
||||||
|
empty: false,
|
||||||
|
tgt: "\tfoo\n\tbar\n\tbaz",
|
||||||
|
},
|
||||||
|
testIndentSet{
|
||||||
|
name: "standard, trailing newline",
|
||||||
|
orig: "foo\nbar\nbaz\n",
|
||||||
|
indent: "",
|
||||||
|
lvl: 1,
|
||||||
|
ws: false,
|
||||||
|
empty: false,
|
||||||
|
tgt: "\tfoo\n\tbar\n\tbaz\n",
|
||||||
|
},
|
||||||
|
testIndentSet{
|
||||||
|
name: "standard, trailing newline with empty",
|
||||||
|
orig: "foo\nbar\nbaz\n",
|
||||||
|
indent: "",
|
||||||
|
lvl: 1,
|
||||||
|
ws: false,
|
||||||
|
empty: true,
|
||||||
|
tgt: "\tfoo\n\tbar\n\tbaz\n\t",
|
||||||
|
},
|
||||||
|
testIndentSet{
|
||||||
|
name: "standard, trailing newline with ws",
|
||||||
|
orig: "foo\nbar\nbaz\n",
|
||||||
|
indent: "",
|
||||||
|
lvl: 1,
|
||||||
|
ws: true,
|
||||||
|
empty: false,
|
||||||
|
tgt: "\tfoo\n\tbar\n\tbaz\n",
|
||||||
|
},
|
||||||
|
testIndentSet{
|
||||||
|
name: "standard, trailing newline with ws and empty",
|
||||||
|
orig: "foo\nbar\nbaz\n",
|
||||||
|
indent: "",
|
||||||
|
lvl: 1,
|
||||||
|
ws: true,
|
||||||
|
empty: true,
|
||||||
|
tgt: "\tfoo\n\tbar\n\tbaz\n\t",
|
||||||
|
},
|
||||||
|
testIndentSet{
|
||||||
|
name: "standard, trailing ws newline with empty",
|
||||||
|
orig: "foo\nbar\nbaz\n ",
|
||||||
|
indent: "",
|
||||||
|
lvl: 1,
|
||||||
|
ws: false,
|
||||||
|
empty: true,
|
||||||
|
tgt: "\tfoo\n\tbar\n\tbaz\n ",
|
||||||
|
},
|
||||||
|
testIndentSet{
|
||||||
|
name: "standard, trailing ws newline with ws",
|
||||||
|
orig: "foo\nbar\nbaz\n ",
|
||||||
|
indent: "",
|
||||||
|
lvl: 1,
|
||||||
|
ws: true,
|
||||||
|
empty: false,
|
||||||
|
tgt: "\tfoo\n\tbar\n\tbaz\n\t ",
|
||||||
|
},
|
||||||
|
testIndentSet{
|
||||||
|
name: "standard, trailing ws newline with ws and empty",
|
||||||
|
orig: "foo\nbar\nbaz\n \n",
|
||||||
|
indent: "",
|
||||||
|
lvl: 1,
|
||||||
|
ws: true,
|
||||||
|
empty: true,
|
||||||
|
tgt: "\tfoo\n\tbar\n\tbaz\n\t \n\t",
|
||||||
|
},
|
||||||
|
testIndentSet{
|
||||||
|
name: "comment",
|
||||||
|
orig: "foo\nbar\nbaz",
|
||||||
|
indent: "# ",
|
||||||
|
lvl: 1,
|
||||||
|
ws: false,
|
||||||
|
empty: false,
|
||||||
|
tgt: "# foo\n# bar\n# baz",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, ts := range tests {
|
||||||
|
out = Indent(ts.orig, ts.indent, ts.lvl, ts.ws, ts.empty)
|
||||||
|
if out == ts.tgt {
|
||||||
|
t.Logf("[%d] OK (%s): %#v: got %#v", idx, ts.name, ts.orig, out)
|
||||||
|
} else {
|
||||||
|
t.Errorf(
|
||||||
|
"[%d] FAIL (%s): %#v (len %d):\n"+
|
||||||
|
"\t\t\texpected (len %d): %#v\n"+
|
||||||
|
"\t\t\tgot (len %d): %#v\n"+
|
||||||
|
"\t\t%#v",
|
||||||
|
idx, ts.name, ts.orig, len(ts.orig),
|
||||||
|
len(ts.tgt), ts.tgt,
|
||||||
|
len(out), out,
|
||||||
|
ts,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedact(t *testing.T) {
|
||||||
|
|
||||||
|
var out string
|
||||||
|
var tests []testRedactSet = []testRedactSet{
|
||||||
|
testRedactSet{
|
||||||
|
name: "empty in, empty out",
|
||||||
|
orig: "",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "standard",
|
||||||
|
orig: "password",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "************************",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "standard with newline",
|
||||||
|
orig: "pass\nword",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "************\n************",
|
||||||
|
newline: true,
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "standard with Windows newline",
|
||||||
|
orig: "pass\r\nword",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "************\r\n************",
|
||||||
|
newline: true,
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "standard with newline without newlines",
|
||||||
|
orig: "pass\nword",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "***************************",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "single leading",
|
||||||
|
orig: "password",
|
||||||
|
leading: 1,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "p*********************",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "single trailing",
|
||||||
|
orig: "password",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 1,
|
||||||
|
tgt: "*********************d",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "three leading",
|
||||||
|
orig: "password",
|
||||||
|
leading: 3,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "pas***************",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "three trailing",
|
||||||
|
orig: "password",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 3,
|
||||||
|
tgt: "***************ord",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "three leading and trailing",
|
||||||
|
orig: "password",
|
||||||
|
leading: 3,
|
||||||
|
trailing: 3,
|
||||||
|
tgt: "pas******ord",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "unmask overflow leading",
|
||||||
|
orig: "password",
|
||||||
|
leading: 5,
|
||||||
|
trailing: 4,
|
||||||
|
tgt: "************************",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "unmask overflow trailing",
|
||||||
|
orig: "password",
|
||||||
|
leading: 4,
|
||||||
|
trailing: 5,
|
||||||
|
tgt: "************************",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "single mask",
|
||||||
|
orig: "password",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "********",
|
||||||
|
mask: "*",
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "standard trailing newline with newlines",
|
||||||
|
orig: "password\n",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "************************\n",
|
||||||
|
newline: true,
|
||||||
|
},
|
||||||
|
testRedactSet{
|
||||||
|
name: "standard trailing newline without newlines",
|
||||||
|
orig: "password\n",
|
||||||
|
leading: 0,
|
||||||
|
trailing: 0,
|
||||||
|
tgt: "***************************",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, ts := range tests {
|
||||||
|
out = Redact(ts.orig, ts.mask, ts.leading, ts.trailing, ts.newline)
|
||||||
|
if out == ts.tgt {
|
||||||
|
t.Logf("[%d] OK (%s): %#v: got %#v", idx, ts.name, ts.orig, out)
|
||||||
|
} else {
|
||||||
|
t.Errorf(
|
||||||
|
"[%d] FAIL (%s): %#v (len %d):\n"+
|
||||||
|
"\t\t\texpected (len %d): %#v\n"+
|
||||||
|
"\t\t\tgot (len %d): %#v\n"+
|
||||||
|
"\t\t%#v",
|
||||||
|
idx, ts.name, ts.orig, len(ts.orig),
|
||||||
|
len(ts.tgt), ts.tgt,
|
||||||
|
len(out), out,
|
||||||
|
ts,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimLines(t *testing.T) {
|
||||||
|
|
||||||
|
var out string
|
||||||
|
var tests []testTrimLinesSet = []testTrimLinesSet{
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "none",
|
||||||
|
orig: " foo \n bar \n baz ",
|
||||||
|
left: false,
|
||||||
|
right: false,
|
||||||
|
tgt: " foo \n bar \n baz ",
|
||||||
|
},
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "standard",
|
||||||
|
orig: " foo \n bar \n baz ",
|
||||||
|
left: true,
|
||||||
|
right: true,
|
||||||
|
tgt: "foo\nbar\nbaz",
|
||||||
|
},
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "left only",
|
||||||
|
orig: " foo \n bar \n baz ",
|
||||||
|
left: true,
|
||||||
|
right: false,
|
||||||
|
tgt: "foo \nbar \nbaz ",
|
||||||
|
},
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "right only",
|
||||||
|
orig: " foo \n bar \n baz ",
|
||||||
|
left: false,
|
||||||
|
right: true,
|
||||||
|
tgt: " foo\n bar\n baz",
|
||||||
|
},
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "standard, trailing newline",
|
||||||
|
orig: " foo \n bar \n baz \n",
|
||||||
|
left: true,
|
||||||
|
right: true,
|
||||||
|
tgt: "foo\nbar\nbaz\n",
|
||||||
|
},
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "left only, trailing newline",
|
||||||
|
orig: " foo \n bar \n baz \n",
|
||||||
|
left: true,
|
||||||
|
right: false,
|
||||||
|
tgt: "foo \nbar \nbaz \n",
|
||||||
|
},
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "right only, trailing newline",
|
||||||
|
orig: " foo \n bar \n baz \n",
|
||||||
|
left: false,
|
||||||
|
right: true,
|
||||||
|
tgt: " foo\n bar\n baz\n",
|
||||||
|
},
|
||||||
|
// Since there's no "non-space" boundary, both of these condition tests do the same thing.
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "left only, trailing newline and ws",
|
||||||
|
orig: " foo \n bar \n baz \n ",
|
||||||
|
left: true,
|
||||||
|
right: false,
|
||||||
|
tgt: "foo \nbar \nbaz \n",
|
||||||
|
},
|
||||||
|
testTrimLinesSet{
|
||||||
|
name: "right only, trailing newline and ws",
|
||||||
|
orig: " foo \n bar \n baz \n ",
|
||||||
|
left: false,
|
||||||
|
right: true,
|
||||||
|
tgt: " foo\n bar\n baz\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, ts := range tests {
|
||||||
|
out = TrimLines(ts.orig, ts.left, ts.right)
|
||||||
|
if out == ts.tgt {
|
||||||
|
t.Logf("[%d] OK (%s): %#v: got %#v", idx, ts.name, ts.orig, out)
|
||||||
|
} else {
|
||||||
|
t.Errorf(
|
||||||
|
"[%d] FAIL (%s): %#v (len %d):\n"+
|
||||||
|
"\t\t\texpected (len %d): %#v\n"+
|
||||||
|
"\t\t\tgot (len %d): %#v\n"+
|
||||||
|
"\t\t%#v",
|
||||||
|
idx, ts.name, ts.orig, len(ts.orig),
|
||||||
|
len(ts.tgt), ts.tgt,
|
||||||
|
len(out), out,
|
||||||
|
ts,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimSpaceLeft(t *testing.T) {
|
||||||
|
|
||||||
|
var out string
|
||||||
|
var tests []testTrimSet = []testTrimSet{
|
||||||
|
testTrimSet{
|
||||||
|
name: "standard",
|
||||||
|
orig: " foo ",
|
||||||
|
tgt: "foo ",
|
||||||
|
},
|
||||||
|
testTrimSet{
|
||||||
|
name: "tabs",
|
||||||
|
orig: "\t\tfoo\t\t",
|
||||||
|
tgt: "foo\t\t",
|
||||||
|
},
|
||||||
|
testTrimSet{
|
||||||
|
name: "newlines",
|
||||||
|
orig: "\n\nfoo\n\n",
|
||||||
|
tgt: "foo\n\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, ts := range tests {
|
||||||
|
out = TrimSpaceLeft(ts.orig)
|
||||||
|
if out == ts.tgt {
|
||||||
|
t.Logf("[%d] OK (%s): %#v: got %#v", idx, ts.name, ts.orig, out)
|
||||||
|
} else {
|
||||||
|
t.Errorf(
|
||||||
|
"[%d] FAIL (%s): %#v (len %d):\n"+
|
||||||
|
"\t\t\texpected (len %d): %#v\n"+
|
||||||
|
"\t\t\tgot (len %d): %#v\n"+
|
||||||
|
"\t\t%#v",
|
||||||
|
idx, ts.name, ts.orig, len(ts.orig),
|
||||||
|
len(ts.tgt), ts.tgt,
|
||||||
|
len(out), out,
|
||||||
|
ts,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimSpaceRight(t *testing.T) {
|
||||||
|
|
||||||
|
var out string
|
||||||
|
var tests []testTrimSet = []testTrimSet{
|
||||||
|
testTrimSet{
|
||||||
|
name: "standard",
|
||||||
|
orig: " foo ",
|
||||||
|
tgt: " foo",
|
||||||
|
},
|
||||||
|
testTrimSet{
|
||||||
|
name: "tabs",
|
||||||
|
orig: "\t\tfoo\t\t",
|
||||||
|
tgt: "\t\tfoo",
|
||||||
|
},
|
||||||
|
testTrimSet{
|
||||||
|
name: "newlines",
|
||||||
|
orig: "\n\nfoo\n\n",
|
||||||
|
tgt: "\n\nfoo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, ts := range tests {
|
||||||
|
out = TrimSpaceRight(ts.orig)
|
||||||
|
if out == ts.tgt {
|
||||||
|
t.Logf("[%d] OK (%s): %#v: got %#v", idx, ts.name, ts.orig, out)
|
||||||
|
} else {
|
||||||
|
t.Errorf(
|
||||||
|
"[%d] FAIL (%s): %#v (len %d):\n"+
|
||||||
|
"\t\t\texpected (len %d): %#v\n"+
|
||||||
|
"\t\t\tgot (len %d): %#v\n"+
|
||||||
|
"\t\t%#v",
|
||||||
|
idx, ts.name, ts.orig, len(ts.orig),
|
||||||
|
len(ts.tgt), ts.tgt,
|
||||||
|
len(out), out,
|
||||||
|
ts,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user