Compare commits
2 Commits
v1.16.7
...
c6fc692f5e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6fc692f5e
|
||
|
|
4770052b52
|
@@ -15,23 +15,59 @@ for f in $(find . -type f -iname "README.adoc"); do
|
|||||||
nosuffix="${filename%.*}"
|
nosuffix="${filename%.*}"
|
||||||
pfx="${docsdir}/${nosuffix}"
|
pfx="${docsdir}/${nosuffix}"
|
||||||
|
|
||||||
|
# Render HTML, include in commit
|
||||||
newf="${pfx}.html"
|
newf="${pfx}.html"
|
||||||
asciidoctor -a ROOTDIR="${orig}/" -o "${newf}" "${f}"
|
asciidoctor -a ROOTDIR="${orig}/" -o "${newf}" "${f}"
|
||||||
echo "Generated ${newf} from ${f}"
|
echo "Generated ${newf} from ${f}"
|
||||||
git add "${newf}"
|
git add "${newf}"
|
||||||
|
|
||||||
|
# If asciidoctor-pdf is installed, render as PDF for local use
|
||||||
|
# (Does not get added to commit, and *.pdf is in .gitignore for a reason)
|
||||||
if command -v asciidoctor-pdf &> /dev/null;
|
if command -v asciidoctor-pdf &> /dev/null;
|
||||||
then
|
then
|
||||||
newf="${pfx}.pdf"
|
newf="${pfx}.pdf"
|
||||||
|
|
||||||
asciidoctor-pdf -a ROOTDIR="${orig}/" -o "${newf}" "${f}"
|
asciidoctor-pdf -a ROOTDIR="${orig}/" -o "${newf}" "${f}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# If pandoc is installed, render to "GitHub-flavored Markdown" for better rendering on forks/mirrors
|
||||||
|
# and marginally better rendering on https://pkg.go.dev/ and add to commit.
|
||||||
|
#
|
||||||
|
# <rant>
|
||||||
|
# There is no such thing as "Markdown".
|
||||||
|
# The closest thing you have to any sort of standard is https://daringfireball.net/projects/markdown/
|
||||||
|
# but everybody and their mother adds their own "extensions"/"flavor", and sometimes even
|
||||||
|
# change how formatting works compared to the Daring Fireball/John Gruber spec (the original creator of the "syntax").
|
||||||
|
# Ergo "Markdown" inherently has no meaning.
|
||||||
|
# It's one of the worst formatting languages out there - just because it's popular doesn't mean it's good.
|
||||||
|
#
|
||||||
|
# If you're writing docs, you should stick to one of these which have defined, canonical, standardized
|
||||||
|
# syntax:
|
||||||
|
# * AsciiDoc/AsciiDoctor
|
||||||
|
# * Supports much more extensive formatting than any Markdown flavor I've seen
|
||||||
|
# * Source/raw/unrendered still *quite* readable by human eyes
|
||||||
|
# * Somewhat limited parsers/renderers
|
||||||
|
# * https://asciidoc.org/
|
||||||
|
# * https://asciidoctor.org/
|
||||||
|
# * DocBook
|
||||||
|
# * Supports even more extensive and flexible but exact formatting
|
||||||
|
# * Great for publishing, though - especially if you need control over formatting/layout
|
||||||
|
# * XML-based
|
||||||
|
# * Harder to read in plaintext, but fairly doable (XML lends to decent mental rendering)
|
||||||
|
# * Very wide support for parsing/rendering
|
||||||
|
# * https://docbook.org/
|
||||||
|
# * LaTex
|
||||||
|
# * Allows for *very* extensive domain-specific ligature/representation (very common in mathematic/scientific literature)
|
||||||
|
# * But nigh unreadable by human eyes unless you've rather familiar with it
|
||||||
|
# * Parsing/rendering support about on-par with DocBook
|
||||||
|
# * https://www.latex-project.org/
|
||||||
|
# </rant>
|
||||||
if command -v pandoc &> /dev/null;
|
if command -v pandoc &> /dev/null;
|
||||||
then
|
then
|
||||||
newf="${pfx}.md"
|
newf="${pfx}.md"
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
#asciidoctor -a ROOTDIR="${orig}/" -b docbook -o - "${f}" | pandoc -f docbook -t markdown_strict -o "${newf}"
|
#asciidoctor -a ROOTDIR="${orig}/" -b docbook -o - "${f}" | pandoc -f docbook -t markdown_strict -o "${newf}"
|
||||||
#asciidoctor -a ROOTDIR="${orig}/" -b html -o - "${f}" | pandoc -f html -t markdown_strict -o "${newf}"
|
|
||||||
asciidoctor -a ROOTDIR="${orig}/" -b html -o - "${f}" | pandoc -f html -t gfm -o "${newf}"
|
asciidoctor -a ROOTDIR="${orig}/" -b html -o - "${f}" | pandoc -f html -t gfm -o "${newf}"
|
||||||
if [ $? -eq 0 ];
|
if [ $? -eq 0 ];
|
||||||
then
|
then
|
||||||
|
|||||||
12
netx/dnsx/errors.go
Normal file
12
netx/dnsx/errors.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package dnsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadChars error = errors.New("netx/dnsx: invalid characters/encoding were encountered")
|
||||||
|
ErrBadLabelLen error = errors.New("netx/dnsx: a label with invalid length was encountered")
|
||||||
|
ErrBadPtrLen error = errors.New("netx/dnsx: a PTR record with invalid length was encountered")
|
||||||
|
ErrBadPtrRoot error = errors.New("netx/dnsx: a PTR record with invalid root encountered")
|
||||||
|
)
|
||||||
571
netx/dnsx/funcs.go
Normal file
571
netx/dnsx/funcs.go
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
package dnsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`bytes`
|
||||||
|
`encoding/base32`
|
||||||
|
`fmt`
|
||||||
|
`math`
|
||||||
|
`net`
|
||||||
|
`net/netip`
|
||||||
|
`strings`
|
||||||
|
|
||||||
|
`go4.org/netipx`
|
||||||
|
`r00t2.io/goutils/stringsx`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
AddrFromPtr returns a [net/netip.Addr] from a PTR record.
|
||||||
|
|
||||||
|
It is the inverse of [AddrToPtr].
|
||||||
|
|
||||||
|
See also [IpFromPtr].
|
||||||
|
*/
|
||||||
|
func AddrFromPtr(s string) (ip netip.Addr, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var ipStr string
|
||||||
|
var tmpStr string
|
||||||
|
var spl []string = strings.Split(strings.TrimSuffix(s, "."), ".")
|
||||||
|
|
||||||
|
switch len(spl) {
|
||||||
|
case 6:
|
||||||
|
if strings.Join(spl[4:], ".") != "in-addr.arpa" {
|
||||||
|
err = ErrBadPtrRoot
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ipStr = fmt.Sprintf("%s.%s.%s.%s", spl[3], spl[2], spl[1], spl[0])
|
||||||
|
case 34:
|
||||||
|
if strings.Join(spl[32:], ".") != "ip6.arpa" {
|
||||||
|
err = ErrBadPtrRoot
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmpStr = stringsx.Reverse(strings.ReplaceAll(strings.Join(spl[:32], ""), ".", ""))
|
||||||
|
for idx = 0; idx < len(tmpStr); idx++ {
|
||||||
|
if idx%4 == 0 && idx != 0 {
|
||||||
|
ipStr += ":"
|
||||||
|
}
|
||||||
|
ipStr += string(rune(tmpStr[idx]))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = ErrBadPtrLen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip, err = netip.ParseAddr(ipStr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
AddrToPtr returns a PTR record from ip.
|
||||||
|
|
||||||
|
It is the inverse of [AddrFromPtr].
|
||||||
|
|
||||||
|
It includes the root label at the end (the trailing period).
|
||||||
|
*/
|
||||||
|
func AddrToPtr(ip netip.Addr) (s string) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var b []byte
|
||||||
|
var ipStr string
|
||||||
|
|
||||||
|
if ip.Is6() {
|
||||||
|
ipStr = stringsx.Reverse(strings.ReplaceAll(ip.StringExpanded(), ":", ""))
|
||||||
|
ipStr = strings.Join(
|
||||||
|
strings.Split(ipStr, ""),
|
||||||
|
".",
|
||||||
|
)
|
||||||
|
s = fmt.Sprintf("%s.ip6.arpa.", ipStr)
|
||||||
|
} else {
|
||||||
|
b = make([]byte, 4)
|
||||||
|
copy(b, ip.AsSlice())
|
||||||
|
for idx = len(b) - 1; idx >= 0; idx-- {
|
||||||
|
ipStr += fmt.Sprintf("%d.", b[idx])
|
||||||
|
}
|
||||||
|
s = fmt.Sprintf("%s.in-addr.arpa.", ipStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
DnsStrToWire returns a wire-format of a DNS name.
|
||||||
|
|
||||||
|
No validation or conversion (other than to wire format) is performed,
|
||||||
|
and it is expected that any IDN(A)/Punycode translation has
|
||||||
|
*already been performed* such that recordNm is in the ASCII form.
|
||||||
|
(See [IsFqdn] for more information on IDN(A)/Punycode.)
|
||||||
|
|
||||||
|
For encoding reasons, if any given label/segment has a length of 0 or greater than 255 ([math.MaxUint8]),
|
||||||
|
[ErrBadLabelLen] will be returned.
|
||||||
|
|
||||||
|
See [DnsWireToStr] for the inverse.
|
||||||
|
*/
|
||||||
|
func DnsStrToWire(recordNm string) (recordNmBytes []byte, err error) {
|
||||||
|
|
||||||
|
var cLen int
|
||||||
|
var c []byte
|
||||||
|
var cStr string
|
||||||
|
var spl []string
|
||||||
|
var buf *bytes.Buffer = new(bytes.Buffer)
|
||||||
|
|
||||||
|
spl = strings.Split(strings.TrimSuffix(recordNm, "."), ".")
|
||||||
|
for _, cStr = range spl {
|
||||||
|
c = []byte(cStr)
|
||||||
|
cLen = len(c)
|
||||||
|
if !(cLen > 0 && cLen <= math.MaxUint8) {
|
||||||
|
err = ErrBadLabelLen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf.Write(append([]byte{uint8(cLen)}, c...))
|
||||||
|
}
|
||||||
|
|
||||||
|
recordNmBytes = append(buf.Bytes(), 0x00)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
DnsWireToStr is the inverse of [DnsStrToWire]. A trailing . is not included.
|
||||||
|
|
||||||
|
For decoding reasons, it will exit with [ErrBadLabelLen] if recordNmBytes is nil/empty or
|
||||||
|
if no terminating nullbyte is found after 256 label characters have been encountered.
|
||||||
|
*/
|
||||||
|
func DnsWireToStr(recordNmBytes []byte) (recordNm string, err error) {
|
||||||
|
|
||||||
|
var c []byte
|
||||||
|
var cLen uint8
|
||||||
|
var arrLen int
|
||||||
|
var numChars int
|
||||||
|
var labels []string
|
||||||
|
var buf *bytes.Buffer
|
||||||
|
|
||||||
|
if recordNmBytes == nil || len(recordNmBytes) == 0 {
|
||||||
|
err = ErrBadLabelLen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf = bytes.NewBuffer(recordNmBytes)
|
||||||
|
labels = make([]string, 0)
|
||||||
|
|
||||||
|
arrLen = len(recordNmBytes)
|
||||||
|
for {
|
||||||
|
if cLen, err = buf.ReadByte(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cLen == 0x00 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
numChars += int(cLen)
|
||||||
|
if numChars > 255 {
|
||||||
|
err = ErrBadLabelLen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if numChars > arrLen {
|
||||||
|
err = ErrBadLabelLen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c = buf.Next(int(cLen))
|
||||||
|
labels = append(labels, string(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
recordNm = strings.Join(labels, ".")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IpFromPtr is like [AddrFromPtr] but with a [net.IP] instead.
|
||||||
|
|
||||||
|
It is the inverse of [IpToPtr].
|
||||||
|
*/
|
||||||
|
func IpFromPtr(s string) (ip net.IP, err error) {
|
||||||
|
|
||||||
|
var a netip.Addr
|
||||||
|
|
||||||
|
if a, err = AddrFromPtr(s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ip = net.IP(a.AsSlice())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IpToPtr is like [AddrToPtr] but with a [net.IP] instead.
|
||||||
|
|
||||||
|
It is the inverse of [IpFromPtr].
|
||||||
|
*/
|
||||||
|
func IpToPtr(ip net.IP) (s string) {
|
||||||
|
|
||||||
|
var a netip.Addr
|
||||||
|
|
||||||
|
a, _ = netipx.FromStdIP(ip)
|
||||||
|
s = AddrToPtr(a)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsFqdn returns a boolean indicating if s is an FQDN that strictly adheres to RFC format requirements.
|
||||||
|
It performs no lookups/resolution attempts or network operations otherwise.
|
||||||
|
|
||||||
|
It will return true for the "apex record" (e.g. the "naked domain"), as this is a valid assignable FQDN.
|
||||||
|
|
||||||
|
It will return false for wildcard records (see [IsFqdnWildcard]).
|
||||||
|
|
||||||
|
s may or may not end in a period (the root zone; "absolute" FQDNs) (0x00 in wire format).
|
||||||
|
|
||||||
|
# TLDs
|
||||||
|
|
||||||
|
Because valid TLDs are fairly dynamic and can change frequently,
|
||||||
|
validation is *not* performed against the TLD itself.
|
||||||
|
This only ensures that s has a TLD label conforming to the character rules in the referenced RFCs.
|
||||||
|
See [golang.org/x/net/publicsuffix] if precise TLD validation is required (though true TLD validation generally
|
||||||
|
requires fetching the current TLD lists from IANA at runtime like [github.com/bombsimon/tld-validator]).
|
||||||
|
|
||||||
|
# Special RFC-Defined Accommodations
|
||||||
|
|
||||||
|
RFC 2181 [§ 11] specifies that site-local DNS software may accommodate non-RFC-conforming rules.
|
||||||
|
This function may and likely will return false for these site-local deviations.
|
||||||
|
The Lookup* functions/mthods in [net] should be used to validate in these casts
|
||||||
|
if that accommodation is necessary.
|
||||||
|
|
||||||
|
Note that underscores are not valid for "true" FQDNs as they are only valid for e.g. SRV record names,
|
||||||
|
TXT records, etc. - not A/AAAA/CNAME, etc. - see RFC 8553 for details.
|
||||||
|
|
||||||
|
See the following functions for allowing additional syntax/rule validation
|
||||||
|
that have record-type-specific accommodations made:
|
||||||
|
|
||||||
|
* [IsFqdnDefinedTxt]
|
||||||
|
* [IsFqdnNsec3]
|
||||||
|
* [IsFqdnSrv]
|
||||||
|
* [IsFqdnWildcard]
|
||||||
|
|
||||||
|
# RFC Coverage
|
||||||
|
|
||||||
|
This function should conform properly to:
|
||||||
|
|
||||||
|
* RFC 952
|
||||||
|
* RFC 1034 and RFC 1035
|
||||||
|
* RFC 1123
|
||||||
|
* RFC 2181 (selectively, see above)
|
||||||
|
|
||||||
|
preferring the most up-to-date rules where relevant (e.g. labels may start with digits, as per RFC 1123).
|
||||||
|
It enforces/checks label and overall length limits as defined by RFC.
|
||||||
|
|
||||||
|
# IDN(A) and Punycode
|
||||||
|
|
||||||
|
Note that it expects the ASCII-only/presentation form of a record name and
|
||||||
|
will not perform any IDN/IDNA nor Punycode translation.
|
||||||
|
If a caller anticipates FQDNs in their localized format,
|
||||||
|
the caller must perform translation first
|
||||||
|
(via e.g. [gitlab.com/golang-commonmark/puny], [golang.org/x/net/idna], etc.).
|
||||||
|
|
||||||
|
To reiterate, IDN/IDNA:
|
||||||
|
|
||||||
|
* RFC 3490
|
||||||
|
* RFC 5890
|
||||||
|
* RFC 5891
|
||||||
|
* RFC 5892
|
||||||
|
* RFC 5893
|
||||||
|
* RFC 5894
|
||||||
|
|
||||||
|
and Punycode (RFC 3492) *MUST* use their ASCII forms, NOT the localized/Unicode formats.
|
||||||
|
|
||||||
|
[§ 11]: https://datatracker.ietf.org/doc/html/rfc2181#section-11
|
||||||
|
*/
|
||||||
|
func IsFqdn(s string) (fqdn bool) {
|
||||||
|
|
||||||
|
var lbl string
|
||||||
|
var lbls []string = strings.Split(strings.TrimSuffix(s, "."), ".")
|
||||||
|
|
||||||
|
if !commonFqdn(s) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, lbl = range lbls {
|
||||||
|
if !IsLabel(lbl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fqdn = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsFqdnDefinedTxt is like [IsFqdn] but explicitly *only* allows fully-qualified
|
||||||
|
RFC-defined TXT "subtypes":
|
||||||
|
|
||||||
|
* ACME DNS-01 (RFC 8555)
|
||||||
|
* BIMI (RFC draft [bimi])
|
||||||
|
* DKIM (RFC 6376, RFC 8301, RFC 8463)
|
||||||
|
* DKIM ATPS (RFC 6541)
|
||||||
|
* DMARC (RFC 7489, RFC 9091, RFC 9616)
|
||||||
|
* MTA-STS (RFC 8461)
|
||||||
|
* TLSRPT (RFC 8460)
|
||||||
|
|
||||||
|
Note that the following TXT "subtypes" do not have special formatting in labels/name,
|
||||||
|
and thus are not covered by this function:
|
||||||
|
|
||||||
|
* SPF (RFC 4408, RFC 7208)
|
||||||
|
|
||||||
|
[bimi]: https://datatracker.ietf.org/doc/html/draft-brand-indicators-for-message-identification
|
||||||
|
*/
|
||||||
|
func IsFqdnDefinedTxt(fqdn string) (isOk bool) {
|
||||||
|
|
||||||
|
var lbls []string = strings.Split(fqdn, ".")
|
||||||
|
|
||||||
|
if lbls == nil || len(lbls) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch lbls[0] {
|
||||||
|
case "_dmarc", "_mta-sts", "_acme-challenge":
|
||||||
|
if len(lbls) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isOk = IsFqdn(strings.Join(lbls[1:], "."))
|
||||||
|
case "_smtp":
|
||||||
|
if len(lbls) <= 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lbls[1] != "_tls" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isOk = IsFqdn(strings.Join(lbls[2:], "."))
|
||||||
|
default:
|
||||||
|
if !IsLabel(lbls[0]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch lbls[1] {
|
||||||
|
case "_domainkey", "_atps", "_bimi":
|
||||||
|
isOk = IsFqdn(strings.Join(lbls[2:], "."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsFqdnNsec3 confirms (partially) that s is a valid NSEC3 record name.
|
||||||
|
|
||||||
|
Note that due to the record name being a base32 encoding of a *hash*, the validity
|
||||||
|
can't be 100% confirmed with certainty - only basic checks can be done.
|
||||||
|
|
||||||
|
NSEC3 can be found via:
|
||||||
|
|
||||||
|
* RFC 5155
|
||||||
|
* RFC 6840
|
||||||
|
* RFC 6944
|
||||||
|
* RFC 7129
|
||||||
|
* RFC 8198
|
||||||
|
* RFC 9077
|
||||||
|
* RFC 9157
|
||||||
|
* RFC 9276
|
||||||
|
* RFC 9905
|
||||||
|
|
||||||
|
At the time of writing, only one hashing algorithm (SHA-1) has been specified.
|
||||||
|
However, because this function does not check against the IANA registration at runtime,
|
||||||
|
it's possible that this changes but the library may not immediately reflect this.
|
||||||
|
*/
|
||||||
|
func IsFqdnNsec3(s string) (maybeNsec3 bool) {
|
||||||
|
|
||||||
|
var h []byte
|
||||||
|
var err error
|
||||||
|
var isAscii bool
|
||||||
|
var lbls []string = strings.Split(strings.TrimSuffix(s, "."), ".")
|
||||||
|
|
||||||
|
if !commonFqdn(s) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(lbls) <= 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(lbls[0]) != 32 { // SHA1 is 160 bits/20 bytes digest, which is always 32 chars in base32(hex)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h, err = base32.StdEncoding.DecodeString(strings.ToUpper(lbls[0])); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isAscii, err = stringsx.IsAsciiSpecial(
|
||||||
|
strings.ToLower(lbls[0]),
|
||||||
|
false, false, false, false,
|
||||||
|
[]byte{
|
||||||
|
// Normally, Base32 goes A-Z, 2-7
|
||||||
|
// but NSEC3 uses Base32Hex (RFC 4648 § 7),
|
||||||
|
// which is 0-9A-V
|
||||||
|
'0', '1', '2', '3', '4', '5',
|
||||||
|
'6', '7', '8', '9', 'a', 'b',
|
||||||
|
'c', 'd', 'e', 'f', 'g', 'h',
|
||||||
|
'j', 'k', 'm', 'n', 'p', 'q',
|
||||||
|
'r', 's', 't', 'u', 'v',
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isAscii {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(h) != 20 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
maybeNsec3 = IsFqdn(strings.Join(lbls[1:], "."))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsFqdnSrv is like [IsFqdn], but explicitly *only* allows fully-qualified SRV records
|
||||||
|
(i.e. underscores must start the first two labels, and there must be at least two additional
|
||||||
|
labels after these labels).
|
||||||
|
|
||||||
|
Note that the protocol is not checked for validity, as that would require runtime
|
||||||
|
validation against a resource liable to change and would need to be fetched dynamically - see
|
||||||
|
the [IANA Protocol Numbers registry].
|
||||||
|
|
||||||
|
[IANA Protocol Numbers registry]: https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
|
||||||
|
*/
|
||||||
|
func IsFqdnSrv(s string) (srv bool) {
|
||||||
|
|
||||||
|
var lbls []string = strings.Split(strings.TrimSuffix(s, "."), ".")
|
||||||
|
|
||||||
|
if !commonFqdn(s) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(lbls) <= 4 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(lbls[0], "_") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(lbls[1], "_") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srv = IsFqdn(strings.Join(lbls[2:], "."))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFqdnWildcard is like [IsFqdn] but explicitly *only* allows fully-qualified wildcard records.
|
||||||
|
func IsFqdnWildcard(s string) (wildcard bool) {
|
||||||
|
|
||||||
|
var lbls []string = strings.Split(strings.TrimSuffix(s, "."), ".")
|
||||||
|
|
||||||
|
if len(lbls) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lbls[0] != "*" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wildcard = IsFqdn(strings.Join(lbls[1:], "."))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLabel returns true if s is a valid DNS label for standard records.
|
||||||
|
func IsLabel(s string) (isLbl bool) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if strings.HasPrefix(s, "-") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(s, "-") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isLbl, err = stringsx.IsAsciiSpecial(
|
||||||
|
s,
|
||||||
|
false, false, false, false,
|
||||||
|
[]byte{
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l',
|
||||||
|
'm', 'n', 'o', 'p', 'q', 'r',
|
||||||
|
's', 't', 'u', 'v', 'w', 'x',
|
||||||
|
'y', 'z', '0', '1', '2', '3',
|
||||||
|
'4', '5', '6', '7', '8', '9',
|
||||||
|
'-',
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsPtr returns true if s is a PTR (also called an "rDNS" or "reverse DNS" record) name.
|
||||||
|
|
||||||
|
If true, the IP is returned as well (otherwise it will be nil).
|
||||||
|
*/
|
||||||
|
func IsPtr(s string) (isPtr bool, addr net.IP) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var lbls []string = strings.Split(strings.TrimSuffix(s, "."), ".")
|
||||||
|
|
||||||
|
if len(lbls) < 6 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = AddrFromPtr(s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isPtr = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// commonFqdn is used to validate some rules common to all record names.
|
||||||
|
func commonFqdn(s string) (isOk bool) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var lbl string
|
||||||
|
var isAscii bool
|
||||||
|
var labels []string
|
||||||
|
var domstr string = strings.ToLower(strings.TrimSuffix(s, "."))
|
||||||
|
|
||||||
|
if isAscii, err = stringsx.IsAsciiSpecial(
|
||||||
|
domstr, false, false, false, false,
|
||||||
|
[]byte{
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l',
|
||||||
|
'm', 'n', 'o', 'p', 'q', 'r',
|
||||||
|
's', 't', 'u', 'v', 'w', 'x',
|
||||||
|
'y', 'z', '0', '1', '2', '3',
|
||||||
|
'4', '5', '6', '7', '8', '9',
|
||||||
|
'-', '_', '.',
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !isAscii {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(domstr) + 1) > 255 { // +1 for root label
|
||||||
|
return
|
||||||
|
}
|
||||||
|
labels = strings.Split(domstr, ".")
|
||||||
|
|
||||||
|
for _, lbl = range labels {
|
||||||
|
if len(lbl) < 1 || len(lbl) > 63 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO?
|
||||||
|
|
||||||
|
isOk = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
29
netx/dnsx/funcs_test.go
Normal file
29
netx/dnsx/funcs_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package dnsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`net/netip`
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPtr(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var ptr string
|
||||||
|
var ip netip.Addr
|
||||||
|
var ipStr string = "::ffff:192.168.0.1"
|
||||||
|
var ptrStr string = "1.0.0.0.8.a.0.c.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."
|
||||||
|
|
||||||
|
if ip, err = AddrFromPtr(ptrStr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("PTR -> Addr: %s -> %s", ptrStr, ip.String())
|
||||||
|
if ip.String() != ipStr {
|
||||||
|
t.Fatalf("expect IP %v, got %v", ipStr, ip.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = AddrToPtr(ip)
|
||||||
|
if ptr != ptrStr {
|
||||||
|
t.Fatalf("expect PTR %v, got %v", ptrStr, ptr)
|
||||||
|
}
|
||||||
|
t.Logf("Addr -> PTR: %s -> %s", ip.String(), ptr)
|
||||||
|
}
|
||||||
220
netx/funcs.go
220
netx/funcs.go
@@ -102,11 +102,37 @@ func Cidr4ToStr(cidr uint8) (maskStr string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FamilyToVer returns a more "human-friendly" IP version from a system/lower-level IP family
|
||||||
|
([AFUnspec], [AFInet], [AFInet6]).
|
||||||
|
|
||||||
|
ipVer will be int(4) for [AFInet], int(6) for [AFInet6], int(0) for [AFUnspec], or
|
||||||
|
int(-1) for an unknown family.
|
||||||
|
*/
|
||||||
|
func FamilyToVer(family uint16) (ipVer int) {
|
||||||
|
|
||||||
|
switch family {
|
||||||
|
case AFInet:
|
||||||
|
ipVer = 4
|
||||||
|
case AFInet6:
|
||||||
|
ipVer = 6
|
||||||
|
case AFUnspec:
|
||||||
|
ipVer = 0
|
||||||
|
default:
|
||||||
|
ipVer = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
GetAddrFamily returns the network family of a [net/netip.Addr].
|
GetAddrFamily returns the network family of a [net/netip.Addr].
|
||||||
|
|
||||||
See also [GetIpFamily].
|
See also [GetIpFamily].
|
||||||
|
|
||||||
|
Note that this returns [AFInet] or [AFInet6], NOT uint16(4) or uint16(6).
|
||||||
|
(See [FamilyToVer] to get the associated higher-level value.)
|
||||||
|
|
||||||
If addr is not a "valid" IP address or the version can't be determined, family will be AFUnspec (usually 0x00/0).
|
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) {
|
func GetAddrFamily(addr netip.Addr) (family uint16) {
|
||||||
@@ -131,6 +157,9 @@ func GetAddrFamily(addr netip.Addr) (family uint16) {
|
|||||||
/*
|
/*
|
||||||
GetIpFamily returns the network family of a [net.IP].
|
GetIpFamily returns the network family of a [net.IP].
|
||||||
|
|
||||||
|
Note that this returns [AFInet] or [AFInet6], NOT uint16(4) or uint16(6).
|
||||||
|
(See [FamilyToVer] to get the associated higher-level value.)
|
||||||
|
|
||||||
See also [GetAddrFamily].
|
See also [GetAddrFamily].
|
||||||
|
|
||||||
If ip is not a "valid" IP address or the version can't be determined,
|
If ip is not a "valid" IP address or the version can't be determined,
|
||||||
@@ -158,6 +187,8 @@ If ip is an IPv4 address, it will simmply be the string representation (e.g. "20
|
|||||||
If ip is an IPv6 address, it will be enclosed in brackets (e.g. "[2001:db8::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.
|
If the version can't be determined, rfcStr will be an empty string.
|
||||||
|
|
||||||
|
See also [IpRfcStr] for providing an IP address as a string.
|
||||||
*/
|
*/
|
||||||
func IpRfc(ip net.IP) (rfcStr string) {
|
func IpRfc(ip net.IP) (rfcStr string) {
|
||||||
|
|
||||||
@@ -170,6 +201,56 @@ func IpRfc(ip net.IP) (rfcStr string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IpRfcStr implements [IpRfc]/[AddrRfc] for string representations of an IP address s.
|
||||||
|
|
||||||
|
If s is an IPv6 address already in the bracketed RFC format,
|
||||||
|
then rfcStr will be equal to s.
|
||||||
|
|
||||||
|
If s is not a string representation of an IP address, rfcStr will be empty.
|
||||||
|
|
||||||
|
See [IpStripRfcStr] for the inverse (removing any brackets from s if present).
|
||||||
|
*/
|
||||||
|
func IpRfcStr(s string) (rfcStr string) {
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
|
||||||
|
if !IsIpAddr(s) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if IsBracketedIp6(s) {
|
||||||
|
rfcStr = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ip = net.ParseIP(s)
|
||||||
|
if ip == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rfcStr = IpRfc(ip)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IpStripRfcStr returns IP address string s without any brackets.
|
||||||
|
|
||||||
|
If s is not a valid IP address, stripStr will be empty.
|
||||||
|
*/
|
||||||
|
func IpStripRfcStr(s string) (stripStr string) {
|
||||||
|
|
||||||
|
if !IsIpAddr(s) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !IsBracketedIp6(s) {
|
||||||
|
stripStr = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stripStr = strings.TrimPrefix(s, "[")
|
||||||
|
stripStr = strings.TrimSuffix(stripStr, "]")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
IPMask4ToCidr returns a CIDR prefix size/bit size/bit length from a [net.IPMask].
|
IPMask4ToCidr returns a CIDR prefix size/bit size/bit length from a [net.IPMask].
|
||||||
|
|
||||||
@@ -257,6 +338,123 @@ func IPMask4ToStr(ipMask net.IPMask) (maskStr string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IpVerStr provides the IP family of IP address/network string s.
|
||||||
|
|
||||||
|
s may be one of the following formats/syntaxes:
|
||||||
|
|
||||||
|
* 203.0.113.0
|
||||||
|
* 203.0.113.1
|
||||||
|
* 203.0.113.0/24
|
||||||
|
* 203.0.113.1/24
|
||||||
|
* 2001:db8::
|
||||||
|
* 2001:db8::1
|
||||||
|
* 2001:db8::/32
|
||||||
|
* 2001:db8::1/32
|
||||||
|
* [2001:db8::]
|
||||||
|
* [2001:db8::1]
|
||||||
|
|
||||||
|
|
||||||
|
Unlike [GetAddrFamily]/[GetIpFamily], this returns a more "friendly"
|
||||||
|
version - if s is not valid syntax, ipVer will be int(0),
|
||||||
|
otherwise ipVer will be int(4) for family IPv4 and int(6) for family IPv6.
|
||||||
|
(See [VerToFamily] to get the associated system/lower-level value.)
|
||||||
|
*/
|
||||||
|
func IpVerStr(s string) (ipVer int) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var ipstr string
|
||||||
|
var p netip.Prefix
|
||||||
|
|
||||||
|
ipstr = strings.TrimPrefix(s, "[")
|
||||||
|
ipstr = strings.TrimSuffix(ipstr, "]")
|
||||||
|
|
||||||
|
if p, err = netip.ParsePrefix(ipstr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.Addr().Is6() {
|
||||||
|
ipVer = 6
|
||||||
|
} else {
|
||||||
|
ipVer = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsBracketedIp6 returns a boolean indicating if s is a valid bracket-enclosed IPv6 in string format
|
||||||
|
(e.g. "[2001:db8::1]").
|
||||||
|
|
||||||
|
It will return false for *non-bracketed* IPv6 addresses (e.g. "2001:db8::1"), IPv4 addresses,
|
||||||
|
or if s is not a valid IPv6 address string.
|
||||||
|
|
||||||
|
[IpRfcStr] or [IpStripRfcStr] can be used to coerce a string to a specific format.
|
||||||
|
*/
|
||||||
|
func IsBracketedIp6(s string) (isBrktdIp bool) {
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
var ipstr string
|
||||||
|
|
||||||
|
if IpVerStr(s) != 6 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ipstr = strings.TrimPrefix(s, "[")
|
||||||
|
ipstr = strings.TrimSuffix(ipstr, "]")
|
||||||
|
|
||||||
|
if ip = net.ParseIP(ipstr); ip == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isBrktdIp = ipstr == s
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsIpAddr returns a boolean indicating if s is an IP address (either IPv4 or IPv6) in string format.
|
||||||
|
|
||||||
|
For IPv6, it will return true for both of these formats:
|
||||||
|
|
||||||
|
* 2001:db8::1
|
||||||
|
* [2001:db8::1]
|
||||||
|
|
||||||
|
[IsBracketedIp6] can be used to narrow down which form.
|
||||||
|
*/
|
||||||
|
func IsIpAddr(s string) (isIp bool) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var a netip.Addr
|
||||||
|
|
||||||
|
if a, err = netip.ParseAddr(s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isIp = a.IsValid()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsPrefixNet returns true if s is a (valid) IP address or network (either IPv4 or IPv6) in:
|
||||||
|
|
||||||
|
<addr_or_net>/<prefix_len>
|
||||||
|
|
||||||
|
format.
|
||||||
|
*/
|
||||||
|
func IsPrefixNet(s string) (isNet bool) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var p netip.Prefix
|
||||||
|
|
||||||
|
if p, err = netip.ParsePrefix(s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isNet = p.Masked().IsValid()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mask4ToCidr converts an IPv4 netmask *in bitmask form* to a CIDR prefix size/bit size/bit length.
|
Mask4ToCidr converts an IPv4 netmask *in bitmask form* to a CIDR prefix size/bit size/bit length.
|
||||||
|
|
||||||
@@ -408,3 +606,25 @@ func Mask4StrToMask(maskStr string) (mask uint32, err error) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
VerToFamily takes a "human-readable" IP version ipVer (4 or 6) and returns
|
||||||
|
a system-level constant (e.g. [AFUnspec], [AFInet], [AFInet6]).
|
||||||
|
|
||||||
|
If not a known IP version (i.e. neither 4 nor 6), family will be [AFUnspec].
|
||||||
|
|
||||||
|
It is the inverse of [FamilyToVer].
|
||||||
|
*/
|
||||||
|
func VerToFamily(ipVer int) (family uint16) {
|
||||||
|
|
||||||
|
switch ipVer {
|
||||||
|
case 4:
|
||||||
|
family = AFInet
|
||||||
|
case 6:
|
||||||
|
family = AFInet6
|
||||||
|
default:
|
||||||
|
family = AFUnspec
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,29 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestFuncsDns(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var domBin []byte
|
||||||
|
var domStr string
|
||||||
|
var domEx string = "foo.r00t2.io"
|
||||||
|
|
||||||
|
if domBin, err = DnsStrToWire(domEx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("Domain %s to wire: %#x\n", domEx, domBin)
|
||||||
|
if domStr, err = DnsWireToStr(domBin); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("Domain wire %#x to string: %s\n", domBin, domStr)
|
||||||
|
if domStr != domEx {
|
||||||
|
t.Fatalf("DNS str wrong (%s != %s)\n)", domStr, domEx)
|
||||||
|
}
|
||||||
|
t.Logf("Domain string %s matches %s", domStr, domEx)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func TestFuncsIP(t *testing.T) {
|
func TestFuncsIP(t *testing.T) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
18
stringsx/func_asciiinvaliderror.go
Normal file
18
stringsx/func_asciiinvaliderror.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package stringsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`fmt`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error conforms an [AsciiInvalidError] to an error interface.
|
||||||
|
func (a *AsciiInvalidError) Error() (errStr string) {
|
||||||
|
|
||||||
|
errStr = fmt.Sprintf(
|
||||||
|
"non-ASCII character '%c' at line:linepos %d:%d (byte %d), "+
|
||||||
|
"string position %d (byte %d): bytes %#x, UTF-8 codepoint U+%04X",
|
||||||
|
a.BadChar, a.Line, a.LineChar, a.LineByte,
|
||||||
|
a.Char, a.Byte, a.BadBytes, a.BadChar,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,11 +1,165 @@
|
|||||||
package stringsx
|
package stringsx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
`bytes`
|
||||||
|
`errors`
|
||||||
`fmt`
|
`fmt`
|
||||||
|
`io`
|
||||||
|
`slices`
|
||||||
`strings`
|
`strings`
|
||||||
`unicode`
|
`unicode`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsAscii returns true if all characters in string s are ASCII.
|
||||||
|
|
||||||
|
This simply wraps [IsAsciiSpecial]:
|
||||||
|
|
||||||
|
isAscii, err = IsAsciiSpecial(s, allowCtl, true, allowExt, true, nil, nil)
|
||||||
|
*/
|
||||||
|
func IsAscii(s string, allowCtl, allowExt bool) (isAscii bool, err error) {
|
||||||
|
|
||||||
|
if isAscii, err = IsAsciiSpecial(
|
||||||
|
s, allowCtl, true, allowExt, true, nil, nil,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsAsciiBuf returns true if all of buffer buf is valid ASCII.
|
||||||
|
|
||||||
|
Note that the buffer will be consumed/read by this function.
|
||||||
|
|
||||||
|
This simply wraps [IsAsciiBufSpecial]:
|
||||||
|
|
||||||
|
isAscii, err = IsAsciiBufSpecial(r, allowCtl, true, allowExt, true, nil, nil)
|
||||||
|
*/
|
||||||
|
func IsAsciiBuf(r io.RuneReader, allowCtl, allowExt bool) (isAscii bool, err error) {
|
||||||
|
|
||||||
|
if isAscii, err = IsAsciiBufSpecial(
|
||||||
|
r, allowCtl, true, allowExt, true, nil, nil,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsAsciiSpecial allows for specifying specific ASCII ranges.
|
||||||
|
|
||||||
|
allowCtl, if true, will allow control characters (0x00 to 0x1f inclusive).
|
||||||
|
|
||||||
|
allowPrint, if true, will allow printable characters (what most people think of
|
||||||
|
when they say "ASCII") (0x20 to 0x7f inclusive).
|
||||||
|
|
||||||
|
allowExt, if true, will allow for "extended ASCII" - some later dialects expand
|
||||||
|
to a full 8-bit ASCII range (0x80 to 0xff inclusive).
|
||||||
|
|
||||||
|
wsCtl, if true, "shifts" the "whitespace control characters" (\t, \n, \r) to the "printable" space
|
||||||
|
(such that allowPrint controls their validation). Thus:
|
||||||
|
|
||||||
|
IsAsciiSpecial(s, false, true, false, true, nil, nil)
|
||||||
|
|
||||||
|
has the same effect as specifying:
|
||||||
|
|
||||||
|
IsAsciiSpecial(s, false, true, false, (-), []byte("\t\n\r"), nil)
|
||||||
|
|
||||||
|
incl, if non-nil and non-empty, allows *additional* characters to be specified as included
|
||||||
|
that would normally *not* be allowed.
|
||||||
|
|
||||||
|
excl, if non-nil and non-empty, invalidates on additional characters that would normally be allowed.
|
||||||
|
|
||||||
|
excl, if specified, takes precedence over incl if specified.
|
||||||
|
|
||||||
|
An [AsciiInvalidError] will be returned on the first encountered invalid character.
|
||||||
|
*/
|
||||||
|
func IsAsciiSpecial(s string, allowCtl, allowPrint, allowExt, allowWs bool, incl, excl []byte) (isAscii bool, err error) {
|
||||||
|
|
||||||
|
var buf *bytes.Buffer = bytes.NewBufferString(s)
|
||||||
|
|
||||||
|
if isAscii, err = IsAsciiBufSpecial(buf, allowCtl, allowPrint, allowExt, allowWs, incl, excl); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsAsciiBufSpecial is the same as [IsAsciiSpecial] but operates on an [io.RuneReader].
|
||||||
|
|
||||||
|
Note that the buffer will be consumed/read by this function.
|
||||||
|
|
||||||
|
It will not return an [io.EOF] if encountered, but any other errors encountered will be returned.
|
||||||
|
It is expected that r will return an [io.EOF] when exhausted.
|
||||||
|
|
||||||
|
An [AsciiInvalidError] will be returned on the first encountered invalid character.
|
||||||
|
*/
|
||||||
|
func IsAsciiBufSpecial(r io.RuneReader, allowCtl, allowPrint, allowExt, allowWs bool, incl, excl []byte) (isAscii bool, err error) {
|
||||||
|
|
||||||
|
var b rune
|
||||||
|
var bLen int
|
||||||
|
var nextNewline bool
|
||||||
|
var tmpErr *AsciiInvalidError = new(AsciiInvalidError)
|
||||||
|
// I know, I know. This is essentually a lookup table. Keeps it speedy.
|
||||||
|
var allowed [256]bool = getAsciiCharMap(allowCtl, allowPrint, allowExt, allowWs, incl, excl)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if b, bLen, err = r.ReadRune(); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
err = nil
|
||||||
|
isAscii = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Set these *before* OK
|
||||||
|
if nextNewline {
|
||||||
|
tmpErr.Line++
|
||||||
|
tmpErr.LineByte = 0
|
||||||
|
tmpErr.LineChar = 0
|
||||||
|
nextNewline = false
|
||||||
|
} else {
|
||||||
|
tmpErr.LineChar++
|
||||||
|
}
|
||||||
|
tmpErr.Char++
|
||||||
|
|
||||||
|
if b == '\n' {
|
||||||
|
nextNewline = true
|
||||||
|
}
|
||||||
|
if b == rune(0xfffd) {
|
||||||
|
// not even valid unicode
|
||||||
|
tmpErr.BadChar = b
|
||||||
|
tmpErr.BadBytes = []byte(string(b))
|
||||||
|
err = tmpErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if bLen > 2 || b > 0xff {
|
||||||
|
// ASCII only occupies a single byte, ISO-8859-1 occupies 2
|
||||||
|
tmpErr.BadChar = b
|
||||||
|
tmpErr.BadBytes = []byte(string(b))
|
||||||
|
err = tmpErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !allowed[byte(b)] {
|
||||||
|
tmpErr.BadChar = b
|
||||||
|
tmpErr.BadBytes = []byte{byte(b)}
|
||||||
|
err = tmpErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set these *after* OK
|
||||||
|
tmpErr.LineByte += uint64(bLen)
|
||||||
|
tmpErr.Byte += uint64(bLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
isAscii = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
LenSplit formats string `s` to break at, at most, every `width` characters.
|
LenSplit formats string `s` to break at, at most, every `width` characters.
|
||||||
|
|
||||||
@@ -252,6 +406,18 @@ func Redact(s, maskStr string, leading, trailing uint, newlines bool) (redacted
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reverse reverses string s. (It's absolutely insane that this isn't in stdlib.)
|
||||||
|
func Reverse(s string) (revS string) {
|
||||||
|
|
||||||
|
var rsl []rune = []rune(s)
|
||||||
|
|
||||||
|
slices.Reverse(rsl)
|
||||||
|
|
||||||
|
revS = string(rsl)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TrimLines is like [strings.TrimSpace] but operates on *each line* of s.
|
TrimLines is like [strings.TrimSpace] but operates on *each line* of s.
|
||||||
It is *NIX-newline (`\n`) vs. Windows-newline (`\r\n`) agnostic.
|
It is *NIX-newline (`\n`) vs. Windows-newline (`\r\n`) agnostic.
|
||||||
@@ -313,6 +479,58 @@ func TrimSpaceRight(s string) (trimmed string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getAsciiCharMap returns a lookup "table" for ASCII characters.
|
||||||
|
func getAsciiCharMap(allowCtl, allowPrint, allowExt, allowWs bool, incl, excl []byte) (charmap [256]bool) {
|
||||||
|
|
||||||
|
var idx uint8
|
||||||
|
|
||||||
|
if allowCtl {
|
||||||
|
for idx < 0x1f {
|
||||||
|
charmap[idx] = true
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idx = 0x1f
|
||||||
|
}
|
||||||
|
if allowPrint {
|
||||||
|
for idx < 0x7f {
|
||||||
|
charmap[idx] = true
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idx = 0x7f
|
||||||
|
}
|
||||||
|
if allowExt {
|
||||||
|
for {
|
||||||
|
charmap[idx] = true
|
||||||
|
if idx == 0xff {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idx = 0xff
|
||||||
|
}
|
||||||
|
if allowWs {
|
||||||
|
charmap['\t'] = true
|
||||||
|
charmap['\n'] = true
|
||||||
|
charmap['\r'] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if incl != nil && len(incl) > 0 {
|
||||||
|
for _, idx = range incl {
|
||||||
|
charmap[idx] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if excl != nil && len(excl) > 0 {
|
||||||
|
for _, idx = range excl {
|
||||||
|
charmap[idx] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// getNewLine is too unpredictable/nuanced to be used as part of a public API promise so it isn't exported.
|
// getNewLine is too unpredictable/nuanced to be used as part of a public API promise so it isn't exported.
|
||||||
func getNewLine(s string) (nl string) {
|
func getNewLine(s string) (nl string) {
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,17 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestFuncsAscii(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// var s string = "This is a §\nmulti-line\nstring 😀 with\nunicode text.\n"
|
||||||
|
var s string = "This is a §\nmulti-line\nstring with\nno unicode text.\n"
|
||||||
|
|
||||||
|
if _, err = IsAscii(s, false, true); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRedact(t *testing.T) {
|
func TestRedact(t *testing.T) {
|
||||||
|
|
||||||
var out string
|
var out string
|
||||||
@@ -171,6 +182,18 @@ func TestRedact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReverse(t *testing.T) {
|
||||||
|
|
||||||
|
var rev string
|
||||||
|
var s string = "012345679abcdef"
|
||||||
|
|
||||||
|
rev = Reverse(s)
|
||||||
|
if rev != "fedcba976543210" {
|
||||||
|
t.Errorf("reverse of s '%s'; expected 'fedcba976543210', got '%s'", s, rev)
|
||||||
|
}
|
||||||
|
t.Logf("s: %s\nReverse: %s", s, rev)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTrimLines(t *testing.T) {
|
func TestTrimLines(t *testing.T) {
|
||||||
|
|
||||||
var out string
|
var out string
|
||||||
|
|||||||
25
stringsx/types.go
Normal file
25
stringsx/types.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package stringsx
|
||||||
|
|
||||||
|
type (
|
||||||
|
/*
|
||||||
|
AsciiInvalidError is an error used to return an error for the IsAscii* validations.
|
||||||
|
|
||||||
|
It is returned on the first found instance of an invalid ASCII character.
|
||||||
|
*/
|
||||||
|
AsciiInvalidError struct {
|
||||||
|
// Line is a 0-indexed line number where the invalid character was found.
|
||||||
|
Line uint64
|
||||||
|
// LineByte is the 0-indexed byte position for the current Line.
|
||||||
|
LineByte uint64
|
||||||
|
// LineChar is a 0-indexed character (rune) position where the invalid character was found on line number Line.
|
||||||
|
LineChar uint64
|
||||||
|
// Byte is the 0-indexed byte position across the entire input.
|
||||||
|
Byte uint64
|
||||||
|
// Char is the 0-indexed character (rune) position across the entire input.
|
||||||
|
Char uint64
|
||||||
|
// BadChar is the invalid rune
|
||||||
|
BadChar rune
|
||||||
|
// BadBytes is BadChar as bytes.
|
||||||
|
BadBytes []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -17,6 +17,9 @@ Last rendered {localdatetime}
|
|||||||
// BEGIN variable attributes
|
// BEGIN variable attributes
|
||||||
:sprig_ver: 3
|
:sprig_ver: 3
|
||||||
:psutil_ver: 4
|
:psutil_ver: 4
|
||||||
|
:git_owner: r00t2
|
||||||
|
:git_repo: go_goutils
|
||||||
|
:git_repo_full: {git_owner}/{git_repo}
|
||||||
:mod_me: r00t2.io/goutils
|
:mod_me: r00t2.io/goutils
|
||||||
:pkg_me: tplx/sprigx
|
:pkg_me: tplx/sprigx
|
||||||
:src_root: https://git.r00t2.io
|
:src_root: https://git.r00t2.io
|
||||||
@@ -25,8 +28,12 @@ Last rendered {localdatetime}
|
|||||||
:mod_sprig: github.com/Masterminds/sprig/v{sprig_ver}
|
:mod_sprig: github.com/Masterminds/sprig/v{sprig_ver}
|
||||||
:mod_psutil: github.com/shirou/gopsutil/v{psutil_ver}
|
:mod_psutil: github.com/shirou/gopsutil/v{psutil_ver}
|
||||||
:import_sprig: {mod_sprig}
|
:import_sprig: {mod_sprig}
|
||||||
:src_base: {src_root}/r00t2/go_goutils/src/branch/master
|
:src_base: {src_root}/{git_repo_full}
|
||||||
:src_dir: {src_base}/{pkg_me}
|
:src_git: {src_base}.git
|
||||||
|
:src_tree: {src_base}/src/branch/master
|
||||||
|
:src_raw: {src_base}/raw/branch/master
|
||||||
|
:src_dir: {src_tree}/{pkg_me}
|
||||||
|
:src_dir_raw: {src_raw}/{pkg_me}
|
||||||
:import_me: {mod_me}/{pkg_me}
|
:import_me: {mod_me}/{pkg_me}
|
||||||
:godoc_me: {godoc_root}/{import_me}
|
:godoc_me: {godoc_root}/{import_me}
|
||||||
:godoc_sprig: {godoc_root}/{import_sprig}
|
:godoc_sprig: {godoc_root}/{import_sprig}
|
||||||
@@ -42,11 +49,57 @@ They provide functions that offer more enriched use cases and domain-specific da
|
|||||||
====
|
====
|
||||||
If you are reading this README on the Go Module Directory documentation ({godoc_me})
|
If you are reading this README on the Go Module Directory documentation ({godoc_me})
|
||||||
or the directory landing page ({src_dir}), it may not render correctly.
|
or the directory landing page ({src_dir}), it may not render correctly.
|
||||||
|
Anchor-links (links within this document to other sections of this document) will likely also not work.
|
||||||
|
|
||||||
Be sure to view it at properly via {src_dir}/README.adoc[the AsciiDoc rendering^]
|
Be sure to view it at properly via {src_dir}/README.adoc[the in-repo AsciiDoc rendering^]
|
||||||
or by downloading and viewing the {src_dir}/README.html[HTML version^] and/or {src_dir}/README.pdf[PDF version^].
|
or by downloading and viewing the {src_dir_raw}/README.html[HTML version^] in a browser locally
|
||||||
|
and/or <<rndr_pdf, rendering a PDF version>>.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[id="rndr"]
|
||||||
|
== How do I Render These Docs?
|
||||||
|
This documentation is written in https://asciidoc.org/[AsciiDoc^] (with https://asciidoctor.org/[AsciiDoctor^] extensions).
|
||||||
|
|
||||||
|
To re-render all docs (including <<rndr_pdf>>):
|
||||||
|
|
||||||
|
[source,bash, subs="attributes"]
|
||||||
|
----
|
||||||
|
git clone {src_git}
|
||||||
|
cd {git_repo}
|
||||||
|
.githooks/pre-commit/01-docgen
|
||||||
|
----
|
||||||
|
|
||||||
|
[id="rndr_html"]
|
||||||
|
=== HTML
|
||||||
|
HTML output is re-rendered and included in git {src_dir}/.githooks/pre-commit/01-docgen[on each commit^] automatically (via https://github.com/gabyx/Githooks[`github:gabyx/Githooks`^])
|
||||||
|
but can be re-rendered on-demand locally via:
|
||||||
|
|
||||||
|
[source,bash, subs="attributes"]
|
||||||
|
----
|
||||||
|
git clone {src_git}
|
||||||
|
cd {git_repo}
|
||||||
|
export orig_dir="$(pwd)"
|
||||||
|
cd {pkg_me}
|
||||||
|
asciidoctor -a ROOTDIR="${orig_dir}/" -o README.html README.adoc
|
||||||
|
----
|
||||||
|
|
||||||
|
[id="rndr_pdf"]
|
||||||
|
=== PDF
|
||||||
|
This documentation can be rendered to PDF via https://docs.asciidoctor.org/pdf-converter/latest/[`asciidoctor-pdf`^].
|
||||||
|
It is not included in git automatically because binary files that change on each commit is not a good idea for git,
|
||||||
|
especially for a repo that gets cloned as part of a library inclusion in a module/package dependency system (like `gomod`).
|
||||||
|
|
||||||
|
To render as PDF:
|
||||||
|
|
||||||
|
[source,bash,subs="attributes"]
|
||||||
|
----
|
||||||
|
git clone {src_git}
|
||||||
|
cd {git_repo}
|
||||||
|
export orig_dir="$(pwd)"
|
||||||
|
cd {pkg_me}
|
||||||
|
asciidoctor-pdf -a ROOTDIR="${orig_dir}/" -o README.pdf README.adoc
|
||||||
|
----
|
||||||
|
|
||||||
[id="use"]
|
[id="use"]
|
||||||
== How do I Use SprigX?
|
== How do I Use SprigX?
|
||||||
|
|
||||||
@@ -81,10 +134,10 @@ var (
|
|||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
They can even be combined/used together.
|
They can even be combined/used together,
|
||||||
|
|
||||||
[%collapsible]
|
[%collapsible]
|
||||||
.Like this.
|
.like this.
|
||||||
====
|
====
|
||||||
[source,go,subs="attributes"]
|
[source,go,subs="attributes"]
|
||||||
----
|
----
|
||||||
@@ -125,10 +178,8 @@ If a `<template>.FuncMap` is added via `.Funcs()` *after* template parsing, it w
|
|||||||
|
|
||||||
For example, if both `sprig` and `sprigx` provide a function `foo`:
|
For example, if both `sprig` and `sprigx` provide a function `foo`:
|
||||||
|
|
||||||
this will use `foo` from `sprigx`
|
|
||||||
|
|
||||||
[%collapsible]
|
[%collapsible]
|
||||||
.(show)
|
.this will use `foo` from `sprigx`
|
||||||
====
|
====
|
||||||
[source,go,subs="attributes"]
|
[source,go,subs="attributes"]
|
||||||
----
|
----
|
||||||
@@ -157,10 +208,8 @@ var (
|
|||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
whereas this will use `foo` from `sprig`
|
|
||||||
|
|
||||||
[%collapsible]
|
[%collapsible]
|
||||||
.(show)
|
.whereas this will use `foo` from `sprig`
|
||||||
====
|
====
|
||||||
[source,go,subs="attributes"]
|
[source,go,subs="attributes"]
|
||||||
----
|
----
|
||||||
@@ -189,10 +238,10 @@ var (
|
|||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
and a function can even be explicitly [[override]]overridden.
|
and a function can even be explicitly [[override]]overridden,
|
||||||
|
|
||||||
[%collapsible]
|
[%collapsible]
|
||||||
.(show)
|
.like this.
|
||||||
====
|
====
|
||||||
This would override a function `foo` and `foo2` in `sprigx` from `foo` and `foo2` from `sprig`, but leave all other `sprig` functions untouched.
|
This would override a function `foo` and `foo2` in `sprigx` from `foo` and `foo2` from `sprig`, but leave all other `sprig` functions untouched.
|
||||||
|
|
||||||
@@ -233,6 +282,7 @@ var (
|
|||||||
|
|
||||||
[id="lib"]
|
[id="lib"]
|
||||||
== Library Functions
|
== Library Functions
|
||||||
|
|
||||||
These are generally intended to be used *outside* the template in the actual Go code.
|
These are generally intended to be used *outside* the template in the actual Go code.
|
||||||
|
|
||||||
[id="lib_cmbfmap"]
|
[id="lib_cmbfmap"]
|
||||||
@@ -261,7 +311,7 @@ func CombinedHtmlFuncMap(preferSprigX bool) (fmap template.FuncMap)
|
|||||||
----
|
----
|
||||||
|
|
||||||
This function returns an {godoc_root}/html/template#FuncMap[`html/template.FuncMap`] function map (like <<lib_hfmap>>) combined with
|
This function returns an {godoc_root}/html/template#FuncMap[`html/template.FuncMap`] function map (like <<lib_hfmap>>) combined with
|
||||||
{godoc_sprig}#HtmlFuncMap[`github.com/Masterminds/sprig/v3.HtmlFuncMap`^].
|
{godoc_sprig}#HtmlFuncMap[`{import_sprig}.HtmlFuncMap`^].
|
||||||
|
|
||||||
If `preferSprigx` is true, SprigX function names will override Sprig functions with the same name.
|
If `preferSprigx` is true, SprigX function names will override Sprig functions with the same name.
|
||||||
If false, Sprig functions will override conflicting SprigX functions with the same name.
|
If false, Sprig functions will override conflicting SprigX functions with the same name.
|
||||||
@@ -275,7 +325,7 @@ func CombinedTxtFuncMap(preferSprigX bool) (fmap template.FuncMap)
|
|||||||
----
|
----
|
||||||
|
|
||||||
This function returns a {godoc_root}/text/template#FuncMap[`text/template.FuncMap`] function map (like <<lib_tfmap>>) combined with
|
This function returns a {godoc_root}/text/template#FuncMap[`text/template.FuncMap`] function map (like <<lib_tfmap>>) combined with
|
||||||
{godoc_sprig}#TxtFuncMap[`github.com/Masterminds/sprig/v3.TxtFuncMap`^].
|
{godoc_sprig}#TxtFuncMap[`{import_sprig}.TxtFuncMap`^].
|
||||||
|
|
||||||
If `preferSprigx` is true, SprigX function names will override Sprig functions with the same name.
|
If `preferSprigx` is true, SprigX function names will override Sprig functions with the same name.
|
||||||
If false, Sprig functions will override conflicting SprigX functions with the same name.
|
If false, Sprig functions will override conflicting SprigX functions with the same name.
|
||||||
@@ -669,6 +719,132 @@ func netipxRange(from, to netip.Addr) (ipRange netipx.IPRange)
|
|||||||
|
|
||||||
`netipxRange` directly calls {godoc_root}/go4.org/netipx#IPRangeFrom[`go4.org/netipx.IPRangeFrom`^].
|
`netipxRange` directly calls {godoc_root}/go4.org/netipx#IPRangeFrom[`go4.org/netipx.IPRangeFrom`^].
|
||||||
|
|
||||||
|
[id="fn_netx"]
|
||||||
|
==== `netx`
|
||||||
|
These template functions contain capabilities from {godoc_root}/{mod_me}/netx[`{mod_me}/netx`^].
|
||||||
|
|
||||||
|
[id="fn_netx_addrrfc"]
|
||||||
|
===== `netxAddrRfc`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxAddrRfc(addr netip.Addr) (rfcStr string)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxAddrRfc` directly calls {godoc_root}/{mod_me}/netx#AddrRfc[`{mod_me}/netx.AddrRfc`^].
|
||||||
|
|
||||||
|
[id="fn_netx_cidr4ipmask"]
|
||||||
|
===== `netxCidr4IpMask`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxCidr4IpMask(cidr uint8) (ipMask net.IPMask, err error)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxCidr4IpMask` directly calls {godoc_root}/{mod_me}/netx#Cidr4ToIPMask[`{mod_me}/netx.Cidr4ToIPMask`^].
|
||||||
|
|
||||||
|
[id="fn_netx_cidr4mask"]
|
||||||
|
===== `netxCidr4Mask`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxCidr4Mask(cidr uint8) (mask uint32, err error)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxCidr4IpMask` directly calls {godoc_root}/{mod_me}/netx#Cidr4ToMask[`{mod_me}/netx.Cidr4ToMask`^].
|
||||||
|
|
||||||
|
[id="fn_netx_cidr4str"]
|
||||||
|
===== `netxCidr4Str`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxCidr4Str(cidr uint8) (maskStr string, err error)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxCidr4Str` directly calls {godoc_root}/{mod_me}/netx#Cidr4ToStr[`{mod_me}/netx.Cidr4ToStr`^].
|
||||||
|
|
||||||
|
[id="fn_netx_familyver"]
|
||||||
|
===== `netxFamilyVer`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxFamilyVer(family uint16) (ipVer int)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxFamilyVer` directly calls {godoc_root}/{mod_me}/netx#FamilyToVer[`{mod_me}/netx.FamilyToVer`^].
|
||||||
|
|
||||||
|
[id="fn_netx_getaddrfam"]
|
||||||
|
===== `netxGetAddrFam`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxGetAddrFam(addr netip.Addr) (family uint16)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxGetAddrFam` directly calls {godoc_root}/{mod_me}/netx#GetAddrFamily[`{mod_me}/netx.GetAddrFamily`^].
|
||||||
|
|
||||||
|
[id="fn_netx_getipfam"]
|
||||||
|
===== `netxGetIpFam`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxGetIpFam(ip net.IP) (family uint16)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxGetIpFam` directly calls {godoc_root}/{mod_me}/netx#GetAddrFamily[`{mod_me}/netx.GetIpFamily`^].
|
||||||
|
|
||||||
|
[id="fn_netx_iprfc"]
|
||||||
|
===== `netxIpRfc`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxIpRfc(ip net.IP) (rfcStr string)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxIpRfc` directly calls {godoc_root}/{mod_me}/netx#IpRfc[`{mod_me}/netx.IpRfc`^].
|
||||||
|
|
||||||
|
[id="fn_netx_iprfcstr"]
|
||||||
|
===== `netxIpRfcStr`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxIpRfcStr(s string) (rfcStr string)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxIpRfcStr` directly calls {godoc_root}/{mod_me}/netx#IpRfcStr[`{mod_me}/netx.IpRfcStr`^].
|
||||||
|
|
||||||
|
[id="fn_netx_ipstriprfc"]
|
||||||
|
===== `netxIpStripRfc`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxIpStripRfc(s string) (stripStr string)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxIpStripRfc` directly calls {godoc_root}/{mod_me}/netx#IpStripRfcStr[`{mod_me}/netx.IpStripRfcStr`^].
|
||||||
|
|
||||||
|
[id="fn_netx_ip4maskcidr"]
|
||||||
|
===== `netxIp4MaskCidr`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxIp4MaskCidr(ipMask net.IPMask) (cidr uint8, err error)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxIp4MaskCidr` directly calls {godoc_root}/{mod_me}/netx#IPMask4ToCidr[`{mod_me}/netx.IPMask4ToCidr`^].
|
||||||
|
|
||||||
|
[id="fn_netx_ip4maskmask"]
|
||||||
|
===== `netxIp4MaskMask`
|
||||||
|
[source,go]
|
||||||
|
.Function Signature
|
||||||
|
----
|
||||||
|
func netxIp4MaskMask(ipMask net.IPMask) (mask uint32, err error)
|
||||||
|
----
|
||||||
|
|
||||||
|
`netxIp4MaskMask` directly calls {godoc_root}/{mod_me}/netx#IPMask4ToMask[`{mod_me}/netx.IPMask4ToMask`^].
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
[id="fn_num"]
|
[id="fn_num"]
|
||||||
=== Numbers/Math
|
=== Numbers/Math
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@ import (
|
|||||||
`github.com/shirou/gopsutil/v4/process`
|
`github.com/shirou/gopsutil/v4/process`
|
||||||
`github.com/shirou/gopsutil/v4/sensors`
|
`github.com/shirou/gopsutil/v4/sensors`
|
||||||
`go4.org/netipx`
|
`go4.org/netipx`
|
||||||
|
`r00t2.io/goutils/netx`
|
||||||
`r00t2.io/goutils/timex`
|
`r00t2.io/goutils/timex`
|
||||||
`r00t2.io/sysutils`
|
`r00t2.io/sysutils`
|
||||||
)
|
)
|
||||||
@@ -67,6 +68,21 @@ var (
|
|||||||
"netipxPfxLast": netipx.PrefixLastIP,
|
"netipxPfxLast": netipx.PrefixLastIP,
|
||||||
"netipxPfxRange": netipx.RangeOfPrefix,
|
"netipxPfxRange": netipx.RangeOfPrefix,
|
||||||
"netipxRange": netipx.IPRangeFrom,
|
"netipxRange": netipx.IPRangeFrom,
|
||||||
|
/*
|
||||||
|
Networking (r00t.io/goutils/netx)
|
||||||
|
*/
|
||||||
|
"netxAddrRfc": netx.AddrRfc,
|
||||||
|
"netxCidr4IpMask": netx.Cidr4ToIPMask,
|
||||||
|
"netxCidr4Mask": netx.Cidr4ToMask,
|
||||||
|
"netxCidr4Str": netx.Cidr4ToStr,
|
||||||
|
"netxFamilyVer": netx.FamilyToVer,
|
||||||
|
"netxGetAddrFam": netx.GetAddrFamily,
|
||||||
|
"netxGetIpFam": netx.GetIpFamily,
|
||||||
|
"netxIpRfc": netx.IpRfc,
|
||||||
|
"netxIpRfcStr": netx.IpRfcStr,
|
||||||
|
"netxIpStripRfc": netx.IpStripRfcStr,
|
||||||
|
"netxIp4MaskCidr": netx.IPMask4ToCidr,
|
||||||
|
"netxIp4MaskMask": netx.IPMask4ToMask,
|
||||||
/*
|
/*
|
||||||
Numbers/Math
|
Numbers/Math
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ COM interfaces are all OVER the place in UUID version, but usually *not* UUIDv4.
|
|||||||
|
|
||||||
A target/expected UUID version can be provided via tgtVer. To disable version analysis, use 0 (or 0x00, etc.).
|
A target/expected UUID version can be provided via tgtVer. To disable version analysis, use 0 (or 0x00, etc.).
|
||||||
It is *highly* recommended to provide a tgtVer if it is known; it can significantly boost confidence in the correct direction.
|
It is *highly* recommended to provide a tgtVer if it is known; it can significantly boost confidence in the correct direction.
|
||||||
A warning, though - if a *wrong* tgtVer IS specified, it can negatively affect confidence accuracy.
|
A warning, though - if a tgtVer IS specified but is wrong (e.g. a UUIDv1 was passed in but tgtVer was specified as 4 for UUIDv4),
|
||||||
|
it can *negatively* affect confidence accuracy.
|
||||||
Thus if you aren't ABSOLUTELY certain of the target UUID version, it's better to use 0/0x00 to disable the check.
|
Thus if you aren't ABSOLUTELY certain of the target UUID version, it's better to use 0/0x00 to disable the check.
|
||||||
Providing a target version is key to breaking some ties (e.g. both cs and csFlip are equal).
|
Providing a target version can be key to breaking some "ties" (e.g. both cs and csFlip are equal).
|
||||||
For example, the given RFC-compliant UUIDv4:
|
For example, the given RFC-compliant UUIDv4:
|
||||||
|
|
||||||
8d8e35ae-58d2-4d28-b09d-ffffffffffff
|
8d8e35ae-58d2-4d28-b09d-ffffffffffff
|
||||||
@@ -67,21 +68,22 @@ which results in a cs == 1 and csFlip == 0 - not very high confidence (but at le
|
|||||||
Providing a tgtVer == 4 changes this to cs == 7 and csFlip == 0, which is *much* more decisive.
|
Providing a tgtVer == 4 changes this to cs == 7 and csFlip == 0, which is *much* more decisive.
|
||||||
|
|
||||||
UUIDs/GUIDs found to be strictly RFC-conforming (via [IsRfc], which returns false for Microsoft GUIDs)
|
UUIDs/GUIDs found to be strictly RFC-conforming (via [IsRfc], which returns false for Microsoft GUIDs)
|
||||||
are *heavily* weighted negatively.
|
are *heavily* weighted negatively in their respective scoring forms.
|
||||||
|
|
||||||
Confidence levels can be generally considered as the following:
|
Confidence levels can be generally considered as the following:
|
||||||
|
|
||||||
cs >= 7: Likely Microsoft GUID (mixed-endian)
|
* cs >= 7: Likely Microsoft GUID (mixed-endian)
|
||||||
cs >= 4: Likely Microsoft GUID
|
* cs >= 4: Likely Microsoft GUID
|
||||||
0 < cs < 4: Leans Microsoft GUID, but untrusted
|
* 0 < cs < 4: Leans Microsoft GUID, but untrusted/ambiguous
|
||||||
cs == 0: Entirely ambiguous/indeterminate
|
* cs == 0: Entirely and completely ambiguous/indeterminate
|
||||||
-4 < cs < 0: Leans UUID/non-Microsoft GUID but untrusted
|
* -4 < cs < 0: Leans UUID/non-Microsoft GUID but untrusted/ambiguous
|
||||||
cs <= -5: Likely UUID/not Microsoft GUID
|
* cs <= -5: Likely UUID/not Microsoft GUID
|
||||||
csFlip >=cs && csFlip >= 4: Likely a pre-flipped (ToggleUuidMsGuid'd) Microsoft GUID
|
* csFlip >= cs && csFlip >= 4: u is likely a pre-flipped (ToggleUuidMsGuid'd) Microsoft GUID
|
||||||
|
(i.e. u is likely directly from a Microsoft API/ data source
|
||||||
|
and no normalization has occurred yet)
|
||||||
|
|
||||||
[§ 4.2]: https://datatracker.ietf.org/doc/html/rfc9562#section-4.2
|
[§ 4.2]: https://datatracker.ietf.org/doc/html/rfc9562#section-4.2
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func DetectMsGuid(u uuid.UUID, tgtVer uuid.Version) (cs, csFlip int) {
|
func DetectMsGuid(u uuid.UUID, tgtVer uuid.Version) (cs, csFlip int) {
|
||||||
|
|
||||||
var isRfc bool
|
var isRfc bool
|
||||||
@@ -168,7 +170,7 @@ func DetectMsGuid(u uuid.UUID, tgtVer uuid.Version) (cs, csFlip int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Equal returns `true` if [uuid.UUID] the two provided [uuid.UUID] are the same.
|
Equal returns `true` if the two provided [uuid.UUID] are the same.
|
||||||
|
|
||||||
Currently it just wraps:
|
Currently it just wraps:
|
||||||
|
|
||||||
@@ -228,7 +230,9 @@ and in MODERN implementations do the endianness flip [ToggleUuidMsGuid] of (USUA
|
|||||||
See [DetectMsGuid] for a more in-depth result that will let you use the confidence level directly,
|
See [DetectMsGuid] for a more in-depth result that will let you use the confidence level directly,
|
||||||
and for details on the weird things that can go wrong with this guesswork.
|
and for details on the weird things that can go wrong with this guesswork.
|
||||||
|
|
||||||
Note that this won't be 100% reliable due to math things, but it should be reliable enough most of the time.
|
Note that this won't be 100% reliable due to math things, but it should be ...somewhat reliable
|
||||||
|
enough most of the time. Maybe. But you are *strongly* recommended to use [DetectMsGuid] instead
|
||||||
|
wherever possible.
|
||||||
|
|
||||||
See also [MsGuidToUuid] and [UuidToMsGuid].
|
See also [MsGuidToUuid] and [UuidToMsGuid].
|
||||||
*/
|
*/
|
||||||
@@ -282,9 +286,10 @@ of an RFC UUID. Use [IsMsGuid] for that.
|
|||||||
|
|
||||||
In the special case of u being a valid NCS UUID, rfc will be false but gen will be [Rfc4122].
|
In the special case of u being a valid NCS UUID, rfc will be false but gen will be [Rfc4122].
|
||||||
This is because RFC 9652 deprecates the NCS UUID. See [IsNcs].
|
This is because RFC 9652 deprecates the NCS UUID. See [IsNcs].
|
||||||
(You are highly unlikely to encounter an NCS UUID "in the wild" unless you are receiving
|
(You are highly unlikely to encounter an NCS UUID "in the wild" unless you are working
|
||||||
a UUID from someone who severely misunderstands that UUIDs are structured/versioned/typed
|
with VERY old systems/data or are receiving a UUID from someone who severely
|
||||||
and thinks they're just random byes in hex with hyphens in certain places.)
|
misunderstands that UUIDs are structured/versioned/typed and thinks they're
|
||||||
|
just random byes in hex with hyphens in certain places.)
|
||||||
(They aren't that, if you're one of those someones.)
|
(They aren't that, if you're one of those someones.)
|
||||||
|
|
||||||
Nil UUID ([IsNilUUID]) and Max UUID ([IsMaxUUID]) return true with RFCs 4122 and RFC 9562 respectively.
|
Nil UUID ([IsNilUUID]) and Max UUID ([IsMaxUUID]) return true with RFCs 4122 and RFC 9562 respectively.
|
||||||
@@ -391,6 +396,9 @@ If [IsMsGuid] is false for msGUID, u will be equal to msGUID.
|
|||||||
|
|
||||||
See [UuidToMsGuid] for the inverse, and [IsRfc] to check
|
See [UuidToMsGuid] for the inverse, and [IsRfc] to check
|
||||||
if the result is a strictly conforming UUID.
|
if the result is a strictly conforming UUID.
|
||||||
|
|
||||||
|
Use [ToggleUuidMsGuid] if you want msGUID explicitly flipped
|
||||||
|
with no gating from [IsMsGuid].
|
||||||
*/
|
*/
|
||||||
func MsGuidToUuid(msGUID uuid.UUID) (u uuid.UUID) {
|
func MsGuidToUuid(msGUID uuid.UUID) (u uuid.UUID) {
|
||||||
|
|
||||||
@@ -441,10 +449,18 @@ func ToggleUuidMsGuid(orig uuid.UUID) (converted uuid.UUID) {
|
|||||||
UuidToMsGuid converts a UUID to a Microsoft GUID.
|
UuidToMsGuid converts a UUID to a Microsoft GUID.
|
||||||
|
|
||||||
If [DetectMsGuid] indicates a good likelihood for u already being a Microsoft GUID
|
If [DetectMsGuid] indicates a good likelihood for u already being a Microsoft GUID
|
||||||
(greater than or equal) to [MsGuidThreshold], msGUID will be equal to u.
|
(cs being greater than or equal to [MsGuidThreshold]), msGUID will be equal to u.
|
||||||
(If it detects it as unflipped endianness, it will automatically be flipped by this function.)
|
|
||||||
|
If [DetectMsGuid] detects it as a Microsoft unflipped endianness (i.e. csFlip > cs),
|
||||||
|
msGUID will be the [ToggleUuidMsGuid]-flipped format of u UNLESS u.Variant == [uuid.Microsoft].
|
||||||
|
Thus this function *should* (with plenty of caveats mentioned elsewhere in other functions/documentation)
|
||||||
|
be usable for flipping a purely non-Microsoft-tainted RFC-conforming UUID, and not flipping it otherwise
|
||||||
|
as it's already a "Microsoft GUID" of some generation (either legacy or newer).
|
||||||
|
|
||||||
See [MsGuidToUuid] for the inverse.
|
See [MsGuidToUuid] for the inverse.
|
||||||
|
|
||||||
|
Use [ToggleUuidMsGuid] if you want u explicitly flipped with no gating from [DetectMsGuid] or
|
||||||
|
via u's [uuid.Variant].
|
||||||
*/
|
*/
|
||||||
func UuidToMsGuid(u uuid.UUID) (msGUID uuid.UUID) {
|
func UuidToMsGuid(u uuid.UUID) (msGUID uuid.UUID) {
|
||||||
|
|
||||||
@@ -455,6 +471,10 @@ func UuidToMsGuid(u uuid.UUID) (msGUID uuid.UUID) {
|
|||||||
msGUID = u
|
msGUID = u
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if u.Variant == uuid.Microsoft {
|
||||||
|
msGUID = u
|
||||||
|
return
|
||||||
|
}
|
||||||
msGUID = ToggleUuidMsGuid(u)
|
msGUID = ToggleUuidMsGuid(u)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user