v0.1.3
FIXED: * XML, YAML marshaling/unmarshaling of pwgenerator.CharSet.
This commit is contained in:
parent
c6050a2dc4
commit
cbecd9caec
18
go.mod
18
go.mod
@ -1,17 +1,23 @@
|
|||||||
module r00t2.io/pwgen
|
module r00t2.io/pwgen
|
||||||
|
|
||||||
go 1.17
|
go 1.22.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.4.0
|
||||||
|
github.com/goccy/go-yaml v1.12.0
|
||||||
github.com/hlandau/passlib v1.0.11
|
github.com/hlandau/passlib v1.0.11
|
||||||
github.com/jessevdk/go-flags v1.5.0
|
github.com/jessevdk/go-flags v1.6.1
|
||||||
gopkg.in/yaml.v3 v3.0.0
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
r00t2.io/goutils v1.3.1
|
r00t2.io/goutils v1.7.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||||
gopkg.in/hlandau/easymetric.v1 v1.0.0 // indirect
|
gopkg.in/hlandau/easymetric.v1 v1.0.0 // indirect
|
||||||
gopkg.in/hlandau/measurable.v1 v1.0.1 // indirect
|
gopkg.in/hlandau/measurable.v1 v1.0.1 // indirect
|
||||||
gopkg.in/hlandau/passlib.v1 v1.0.11 // indirect
|
gopkg.in/hlandau/passlib.v1 v1.0.11 // indirect
|
||||||
|
52
go.sum
52
go.sum
@ -1,21 +1,39 @@
|
|||||||
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
|
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
|
||||||
|
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hlandau/passlib v1.0.11 h1:GNcnM0Iwqx5M4IDCdKi9pJI/jmf6Z4NooIh8ND7rRBg=
|
github.com/hlandau/passlib v1.0.11 h1:GNcnM0Iwqx5M4IDCdKi9pJI/jmf6Z4NooIh8ND7rRBg=
|
||||||
github.com/hlandau/passlib v1.0.11/go.mod h1:77ovAz+VLR4VrRNrNhFTSSzYhZ4iUrGpXcBeC7cVRIU=
|
github.com/hlandau/passlib v1.0.11/go.mod h1:77ovAz+VLR4VrRNrNhFTSSzYhZ4iUrGpXcBeC7cVRIU=
|
||||||
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
|
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk=
|
||||||
|
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/hlandau/easymetric.v1 v1.0.0 h1:ZbfbH7W3giuVDjWUoFhDOjjv20hiPr5HZ2yMV5f9IeE=
|
gopkg.in/hlandau/easymetric.v1 v1.0.0 h1:ZbfbH7W3giuVDjWUoFhDOjjv20hiPr5HZ2yMV5f9IeE=
|
||||||
@ -24,8 +42,8 @@ gopkg.in/hlandau/measurable.v1 v1.0.1 h1:wH5UZKCRUnRr1iD+xIZfwhtxhmr+bprRJttqA1R
|
|||||||
gopkg.in/hlandau/measurable.v1 v1.0.1/go.mod h1:6N+SYJGMTmetsx7wskULP+juuO+++tsHJkAgzvzsbuM=
|
gopkg.in/hlandau/measurable.v1 v1.0.1/go.mod h1:6N+SYJGMTmetsx7wskULP+juuO+++tsHJkAgzvzsbuM=
|
||||||
gopkg.in/hlandau/passlib.v1 v1.0.11 h1:vKeHwGRdWBD9mm4bJ56GAAdBXpFUYvg/BYYkmphjnmA=
|
gopkg.in/hlandau/passlib.v1 v1.0.11 h1:vKeHwGRdWBD9mm4bJ56GAAdBXpFUYvg/BYYkmphjnmA=
|
||||||
gopkg.in/hlandau/passlib.v1 v1.0.11/go.mod h1:wxGAv2CtQHlzWY8NJp+p045yl4WHyX7v2T6XbOcmqjM=
|
gopkg.in/hlandau/passlib.v1 v1.0.11/go.mod h1:wxGAv2CtQHlzWY8NJp+p045yl4WHyX7v2T6XbOcmqjM=
|
||||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
r00t2.io/goutils v1.3.1 h1:mk8B2v7fM3gYoX64CDV206+/j8Z5iSJcszcPFjuvf3o=
|
r00t2.io/goutils v1.7.0 h1:iQluWlkOyBwOKaK94D5QSnSMYpGKtMb/5WjefmdfHgI=
|
||||||
r00t2.io/goutils v1.3.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
r00t2.io/goutils v1.7.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||||
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
||||||
|
15
pwgenerator/_testdata/test.json
Normal file
15
pwgenerator/_testdata/test.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"do_alpha": true,
|
||||||
|
"do_numeric": true,
|
||||||
|
"do_symbols": true,
|
||||||
|
"do_extended": false,
|
||||||
|
"uppers": 0,
|
||||||
|
"lowers": 0,
|
||||||
|
"numbers": 0,
|
||||||
|
"symbols": 0,
|
||||||
|
"extended": 0,
|
||||||
|
"disabled_chars": "abc😊",
|
||||||
|
"length_min": 0,
|
||||||
|
"length_max": 0,
|
||||||
|
"count": 0
|
||||||
|
}
|
13
pwgenerator/_testdata/test.toml
Normal file
13
pwgenerator/_testdata/test.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Letters = true
|
||||||
|
Numbers = true
|
||||||
|
Symbols = true
|
||||||
|
ExtendedSymbols = false
|
||||||
|
CountUpper = 0
|
||||||
|
CountLower = 0
|
||||||
|
CountNumber = 0
|
||||||
|
CountSymbol = 0
|
||||||
|
CountExtended = 0
|
||||||
|
DisabledCharSet = "abc😊"
|
||||||
|
MinLength = 0
|
||||||
|
MaxLength = 0
|
||||||
|
Count = 0
|
14
pwgenerator/_testdata/test.xml
Normal file
14
pwgenerator/_testdata/test.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<GenOpts doAlpha="true"
|
||||||
|
doNumeric="true"
|
||||||
|
doSymbols="true"
|
||||||
|
doExtendedSymbols="false"
|
||||||
|
numUppers="0"
|
||||||
|
numLowers="0"
|
||||||
|
numNumeric="0"
|
||||||
|
numSymbols="0"
|
||||||
|
numXSymbols="0"
|
||||||
|
disabledChars="abc😊"
|
||||||
|
minLen="0"
|
||||||
|
maxLen="0"
|
||||||
|
genCount="0"
|
||||||
|
/>
|
13
pwgenerator/_testdata/test.yml
Normal file
13
pwgenerator/_testdata/test.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Letters: true
|
||||||
|
Numbers: true
|
||||||
|
Symbols: true
|
||||||
|
Extended Symbols: false
|
||||||
|
Number of Uppercase Letters: 0
|
||||||
|
Number of Lowercase Letters: 0
|
||||||
|
Number of Numeric Characters: 0
|
||||||
|
Number of Symbols: 0
|
||||||
|
Number of Extended Symbols: 0
|
||||||
|
Disabled CharSet: abc😊
|
||||||
|
Minimum Length: 0
|
||||||
|
Maximum Length: 0
|
||||||
|
Number of Passwords: 0
|
@ -55,6 +55,30 @@ func (c *CharSet) Less(i, j int) (isBefore bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalText conforms to encoding/TextUnmarshaler.
|
||||||
|
func (c *CharSet) MarshalText() (text []byte, err error) {
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
text = []byte(c.String())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalYAML conforms to (github.com/goccy/go-yaml)yaml.BytesMarshaler. (For some reason, this only works as direct, not pointer.)
|
||||||
|
func (c CharSet) MarshalYAML() (b []byte, err error) {
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b = []byte(c.String())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// RandChar returns a random character from a CharSet.
|
// RandChar returns a random character from a CharSet.
|
||||||
func (c *CharSet) RandChar() (char Char, err error) {
|
func (c *CharSet) RandChar() (char Char, err error) {
|
||||||
|
|
||||||
@ -86,3 +110,39 @@ func (c *CharSet) Swap(i, j int) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalText conforms to an encoding.TextUnmarshaler.
|
||||||
|
func (c *CharSet) UnmarshalText(text []byte) (err error) {
|
||||||
|
|
||||||
|
var cs []Char
|
||||||
|
|
||||||
|
if text == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range string(text) {
|
||||||
|
cs = append(cs, Char(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = cs
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML conforms to (github.com/goccy/go-yaml)yaml.BytesUnmarshaler.
|
||||||
|
func (c *CharSet) UnmarshalYAML(text []byte) (err error) {
|
||||||
|
|
||||||
|
var cs []Char
|
||||||
|
|
||||||
|
if text == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range string(text) {
|
||||||
|
cs = append(cs, Char(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = cs
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -1,13 +1,48 @@
|
|||||||
package pwgenerator
|
package pwgenerator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
`encoding/xml`
|
||||||
"strings"
|
`strings`
|
||||||
"time"
|
`time`
|
||||||
|
|
||||||
"r00t2.io/goutils/multierr"
|
"r00t2.io/goutils/multierr"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Chars returns the list of evaluated characters that would be used in a GenOpts.
|
||||||
|
func (o *GenOpts) Chars() (chars CharSet, err error) {
|
||||||
|
|
||||||
|
chars = make(CharSet, 0)
|
||||||
|
|
||||||
|
if err = o.sanChk(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.explicitCharset != nil && len(o.explicitCharset) != 0 {
|
||||||
|
chars = o.explicitCharset
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Alpha {
|
||||||
|
chars = append(chars, alpha...)
|
||||||
|
}
|
||||||
|
if o.Numeric {
|
||||||
|
chars = append(chars, numeric...)
|
||||||
|
}
|
||||||
|
if o.Symbols {
|
||||||
|
chars = append(chars, symbols...)
|
||||||
|
}
|
||||||
|
if o.ExtendedSymbols {
|
||||||
|
chars = append(chars, extendedSymbols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if chars == nil || len(chars) == 0 {
|
||||||
|
err = ErrEmptyCharsets
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Generate generates a list of passwords for a GenOpts.
|
// Generate generates a list of passwords for a GenOpts.
|
||||||
func (o *GenOpts) Generate() (passwords []string, err error) {
|
func (o *GenOpts) Generate() (passwords []string, err error) {
|
||||||
|
|
||||||
@ -46,49 +81,6 @@ func (o *GenOpts) Generate() (passwords []string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustGenerate is like Generate, but will panic on error instead of returning.
|
|
||||||
func (o *GenOpts) MustGenerate() (passwords []string) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if passwords, err = o.Generate(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
GenOnce generates a *single* password from a GenOpts.
|
|
||||||
|
|
||||||
This method is particularly useful/convenient if GenOpts.Count is 1
|
|
||||||
and you don't want to have to assign to a slice and then get the first index.
|
|
||||||
*/
|
|
||||||
func (o GenOpts) GenerateOnce() (password string, err error) {
|
|
||||||
|
|
||||||
var passwds []string
|
|
||||||
|
|
||||||
o.Count = 1
|
|
||||||
if passwds, err = o.Generate(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
password = passwds[0]
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustGenerateOnce is like GenerateOnce, but will panic on error instead of returning.
|
|
||||||
func (o *GenOpts) MustGenerateOnce() (password string) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if password, err = o.GenerateOnce(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
GenerateCollection returns a PwCollection instead of a slice of password text.
|
GenerateCollection returns a PwCollection instead of a slice of password text.
|
||||||
|
|
||||||
@ -99,8 +91,8 @@ func (o *GenOpts) MustGenerateOnce() (password string) {
|
|||||||
func (o *GenOpts) GenerateCollection(hashAlgos []pwHash) (collection *PwCollection, err error) {
|
func (o *GenOpts) GenerateCollection(hashAlgos []pwHash) (collection *PwCollection, err error) {
|
||||||
|
|
||||||
var charset CharSet
|
var charset CharSet
|
||||||
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
|
||||||
var passwd string
|
var passwd string
|
||||||
|
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
collection = &PwCollection{
|
collection = &PwCollection{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
@ -144,6 +136,109 @@ func (o *GenOpts) GenerateCollection(hashAlgos []pwHash) (collection *PwCollecti
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GenOnce generates a *single* password from a GenOpts.
|
||||||
|
|
||||||
|
This method is particularly useful/convenient if GenOpts.Count is 1
|
||||||
|
and you don't want to have to assign to a slice and then get the first index.
|
||||||
|
*/
|
||||||
|
func (o *GenOpts) GenerateOnce() (password string, err error) {
|
||||||
|
|
||||||
|
var passwds []string
|
||||||
|
|
||||||
|
o.Count = 1
|
||||||
|
if passwds, err = o.Generate(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
password = passwds[0]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGenerate is like Generate, but will panic on error instead of returning.
|
||||||
|
func (o *GenOpts) MustGenerate() (passwords []string) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if passwords, err = o.Generate(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGenerateOnce is like GenerateOnce, but will panic on error instead of returning.
|
||||||
|
func (o *GenOpts) MustGenerateOnce() (password string) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if password, err = o.GenerateOnce(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SetExplicitCharset sets a GenOpts' charset to an explicit selection. It is deduplicated before being used unless inclDupes is true.
|
||||||
|
(Duplicate runes can be used to increase the likelihood of specific characters.)
|
||||||
|
|
||||||
|
Note that if an explicit charset is defined, the following opts will have NO effect:
|
||||||
|
|
||||||
|
Alpha
|
||||||
|
Numeric
|
||||||
|
Symbols
|
||||||
|
ExtendedSymbols
|
||||||
|
CountUpper
|
||||||
|
CountLower
|
||||||
|
CountSymbols
|
||||||
|
CountExtended
|
||||||
|
DisabledChars
|
||||||
|
|
||||||
|
To have these fields be used again, call GenOpts.UnsetExplicitCharset.
|
||||||
|
|
||||||
|
chars can be a CharSet, []rune, []byte, []string, or string
|
||||||
|
*/
|
||||||
|
func (o *GenOpts) SetExplicitCharset(chars interface{}, inclDupes bool) (err error) {
|
||||||
|
|
||||||
|
var charset CharSet
|
||||||
|
|
||||||
|
switch t := chars.(type) {
|
||||||
|
case []rune:
|
||||||
|
charset = CharSet(string(t))
|
||||||
|
case []byte:
|
||||||
|
charset = CharSet(string(t))
|
||||||
|
case []string:
|
||||||
|
s := strings.Join(t, "")
|
||||||
|
charset = CharSet(s)
|
||||||
|
case string:
|
||||||
|
charset = CharSet(t)
|
||||||
|
default:
|
||||||
|
err = ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inclDupes {
|
||||||
|
sortDedupe(&charset)
|
||||||
|
}
|
||||||
|
|
||||||
|
o.explicitCharset = charset
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
UnsetExplicitCharset removes the explicit charset used for generation and instead uses the fields specified in the actual GenOpts.
|
||||||
|
|
||||||
|
See GenOpts.SetExplicitCharset for more details.
|
||||||
|
*/
|
||||||
|
func (o *GenOpts) UnsetExplicitCharset() {
|
||||||
|
|
||||||
|
o.explicitCharset = nil
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// generatePassword generates a single password from CharSet c (plus any minimum requirements).
|
// generatePassword generates a single password from CharSet c (plus any minimum requirements).
|
||||||
func (o *GenOpts) generatePassword(c CharSet) (password string, err error) {
|
func (o *GenOpts) generatePassword(c CharSet) (password string, err error) {
|
||||||
|
|
||||||
@ -281,101 +376,6 @@ func (o *GenOpts) generatePassword(c CharSet) (password string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chars returns the list of evaluated characters that would be used in a GenOpts.
|
|
||||||
func (o *GenOpts) Chars() (chars CharSet, err error) {
|
|
||||||
|
|
||||||
chars = make(CharSet, 0)
|
|
||||||
|
|
||||||
if err = o.sanChk(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.explicitCharset != nil && len(o.explicitCharset) != 0 {
|
|
||||||
chars = o.explicitCharset
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.Alpha {
|
|
||||||
chars = append(chars, alpha...)
|
|
||||||
}
|
|
||||||
if o.Numeric {
|
|
||||||
chars = append(chars, numeric...)
|
|
||||||
}
|
|
||||||
if o.Symbols {
|
|
||||||
chars = append(chars, symbols...)
|
|
||||||
}
|
|
||||||
if o.ExtendedSymbols {
|
|
||||||
chars = append(chars, extendedSymbols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if chars == nil || len(chars) == 0 {
|
|
||||||
err = ErrEmptyCharsets
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
SetExplicitCharset sets a GenOpts' charset to an explicit selection. It is deduplicated before being used unless inclDupes is true.
|
|
||||||
(Duplicate runes can be used to increase the likelihood of specific characters.)
|
|
||||||
|
|
||||||
Note that if an explicit charset is defined, the following opts will have NO effect:
|
|
||||||
|
|
||||||
Alpha
|
|
||||||
Numeric
|
|
||||||
Symbols
|
|
||||||
ExtendedSymbols
|
|
||||||
CountUpper
|
|
||||||
CountLower
|
|
||||||
CountSymbols
|
|
||||||
CountExtended
|
|
||||||
DisabledChars
|
|
||||||
|
|
||||||
To have these fields be used again, call GenOpts.UnsetExplicitCharset.
|
|
||||||
|
|
||||||
chars can be a CharSet, []rune, []byte, []string, or string
|
|
||||||
*/
|
|
||||||
func (o *GenOpts) SetExplicitCharset(chars interface{}, inclDupes bool) (err error) {
|
|
||||||
|
|
||||||
var charset CharSet
|
|
||||||
|
|
||||||
switch t := chars.(type) {
|
|
||||||
case []rune:
|
|
||||||
charset = CharSet(string(t))
|
|
||||||
case []byte:
|
|
||||||
charset = CharSet(string(t))
|
|
||||||
case []string:
|
|
||||||
s := strings.Join(t, "")
|
|
||||||
charset = CharSet(s)
|
|
||||||
case string:
|
|
||||||
charset = CharSet(t)
|
|
||||||
default:
|
|
||||||
err = ErrBadType
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !inclDupes {
|
|
||||||
sortDedupe(&charset)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.explicitCharset = charset
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
UnsetExplicitCharset removes the explicit charset used for generation and instead uses the fields specified in the actual GenOpts.
|
|
||||||
|
|
||||||
See GenOpts.SetExplicitCharset for more details.
|
|
||||||
*/
|
|
||||||
func (o *GenOpts) UnsetExplicitCharset() {
|
|
||||||
|
|
||||||
o.explicitCharset = nil
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFilter gets a filter counter.
|
// getFilter gets a filter counter.
|
||||||
func (o *GenOpts) getFilter() (f *selectFilter) {
|
func (o *GenOpts) getFilter() (f *selectFilter) {
|
||||||
|
|
||||||
|
148
pwgenerator/funcs_test.go
Normal file
148
pwgenerator/funcs_test.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package pwgenerator
|
||||||
|
|
||||||
|
import (
|
||||||
|
`bytes`
|
||||||
|
`embed`
|
||||||
|
`encoding/json`
|
||||||
|
`encoding/xml`
|
||||||
|
`fmt`
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
`github.com/BurntSushi/toml`
|
||||||
|
`github.com/goccy/go-yaml`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed "_testdata"
|
||||||
|
testFiles embed.FS
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testGen *GenOpts = &GenOpts{
|
||||||
|
Alpha: true,
|
||||||
|
Numeric: true,
|
||||||
|
Symbols: true,
|
||||||
|
ExtendedSymbols: false,
|
||||||
|
CountUpper: 0,
|
||||||
|
CountLower: 0,
|
||||||
|
CountNumbers: 0,
|
||||||
|
CountSymbols: 0,
|
||||||
|
CountExtended: 0,
|
||||||
|
DisabledChars: CharSet{
|
||||||
|
Char('a'),
|
||||||
|
Char('b'),
|
||||||
|
Char('c'),
|
||||||
|
Char('😊'),
|
||||||
|
},
|
||||||
|
LengthMin: 0,
|
||||||
|
LengthMax: 0,
|
||||||
|
Count: 0,
|
||||||
|
}
|
||||||
|
testGenNoDisabled *GenOpts = &GenOpts{
|
||||||
|
Alpha: true,
|
||||||
|
Numeric: true,
|
||||||
|
Symbols: true,
|
||||||
|
ExtendedSymbols: false,
|
||||||
|
CountUpper: 0,
|
||||||
|
CountLower: 0,
|
||||||
|
CountNumbers: 0,
|
||||||
|
CountSymbols: 0,
|
||||||
|
CountExtended: 0,
|
||||||
|
DisabledChars: nil,
|
||||||
|
LengthMin: 0,
|
||||||
|
LengthMax: 0,
|
||||||
|
Count: 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
|
||||||
|
var cmpB []byte
|
||||||
|
var err error
|
||||||
|
var g *GenOpts
|
||||||
|
var mB map[string][]byte = make(map[string][]byte)
|
||||||
|
|
||||||
|
if cmpB, err = json.Marshal(testGen); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range []string{
|
||||||
|
"json",
|
||||||
|
"xml",
|
||||||
|
"yml",
|
||||||
|
"toml",
|
||||||
|
} {
|
||||||
|
g = new(GenOpts)
|
||||||
|
|
||||||
|
if mB[i], err = testFiles.ReadFile(fmt.Sprintf("_testdata/test.%s", i)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i {
|
||||||
|
case "json":
|
||||||
|
if err = json.Unmarshal(mB[i], g); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
case "xml":
|
||||||
|
if err = xml.Unmarshal(mB[i], g); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
case "yml":
|
||||||
|
if err = yaml.Unmarshal(mB[i], g); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
case "toml":
|
||||||
|
if err = toml.Unmarshal(mB[i], g); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mB[i], err = json.Marshal(g); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(mB[i], cmpB) {
|
||||||
|
t.Fatalf("Syntax/testfile %s failed comparison.", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRender(t *testing.T) {
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
var err error
|
||||||
|
var buf *bytes.Buffer = new(bytes.Buffer)
|
||||||
|
|
||||||
|
// JSON
|
||||||
|
if b, err = json.MarshalIndent(testGen, "", " "); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buf.WriteString("# JSON\n")
|
||||||
|
buf.Write(b)
|
||||||
|
buf.WriteString("\n\n")
|
||||||
|
|
||||||
|
// XML
|
||||||
|
if b, err = xml.MarshalIndent(testGen, "", " "); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buf.WriteString("# XML\n")
|
||||||
|
buf.Write(b)
|
||||||
|
buf.WriteString("\n\n")
|
||||||
|
|
||||||
|
// YAML
|
||||||
|
if b, err = yaml.Marshal(testGen); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buf.WriteString("# YAML\n")
|
||||||
|
buf.Write(b)
|
||||||
|
buf.WriteString("\n\n")
|
||||||
|
|
||||||
|
// TOML
|
||||||
|
if b, err = toml.Marshal(testGen); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buf.WriteString("# TOML\n")
|
||||||
|
buf.Write(b)
|
||||||
|
buf.WriteString("\n\n")
|
||||||
|
|
||||||
|
t.Log(buf.String())
|
||||||
|
}
|
@ -32,34 +32,34 @@ type GenOpts struct {
|
|||||||
*/
|
*/
|
||||||
// HumanOnly bool `json:"do_human_readable" toml:"do_human_readable" yaml:"Human-Readable Only" xml:"doHumanReadable,attr"`
|
// HumanOnly bool `json:"do_human_readable" toml:"do_human_readable" yaml:"Human-Readable Only" xml:"doHumanReadable,attr"`
|
||||||
// Alpha is true if letters (0x41 to 0x5a, 0x61 to 0x7a) should be included.
|
// Alpha is true if letters (0x41 to 0x5a, 0x61 to 0x7a) should be included.
|
||||||
Alpha bool `json:"do_alpha" toml:"do_alpha" yaml:"Letters" xml:"doAlpha,attr"`
|
Alpha bool `json:"do_alpha" toml:"Letters" yaml:"Letters" xml:"doAlpha,attr"`
|
||||||
// Numeric is true if numbers (0x30 to 0x39) should be included.
|
// Numeric is true if numbers (0x30 to 0x39) should be included.
|
||||||
Numeric bool `json:"do_numeric" toml:"do_numeric" yaml:"Numbers" xml:"doNumeric,attr"`
|
Numeric bool `json:"do_numeric" toml:"Numbers" yaml:"Numbers" xml:"doNumeric,attr"`
|
||||||
// Symbols is true if non-alphanumeric characters (between 0x21 and 0x7e) should be included.
|
// Symbols is true if non-alphanumeric characters (between 0x21 and 0x7e) should be included.
|
||||||
Symbols bool `json:"do_symbols" toml:"do_symbols" yaml:"Symbols" xml:"doSymbols,attr"`
|
Symbols bool `json:"do_symbols" toml:"Symbols" yaml:"Symbols" xml:"doSymbols,attr"`
|
||||||
// ExtendedSymbols is true if non-alphanumeric characters in the "extended ASCII" set (0x80 to 0xff) should be included.
|
// ExtendedSymbols is true if non-alphanumeric characters in the "extended ASCII" set (0x80 to 0xff) should be included.
|
||||||
ExtendedSymbols bool `json:"do_extended" toml:"do_extended" yaml:"Extended Symbols" xml:"doExtendedSymbols,attr"`
|
ExtendedSymbols bool `json:"do_extended" toml:"ExtendedSymbols" yaml:"Extended Symbols" xml:"doExtendedSymbols,attr"`
|
||||||
// CountUpper specifies how many uppercase letters (0x41 to 0x5a) should be specified at a minimum.
|
// CountUpper specifies how many uppercase letters (0x41 to 0x5a) should be specified at a minimum.
|
||||||
CountUpper uint `json:"uppers" toml:"uppers" yaml:"Number of Uppercase Letters" xml:"numUppers,attr"`
|
CountUpper uint `json:"uppers" toml:"CountUpper" yaml:"Number of Uppercase Letters" xml:"numUppers,attr"`
|
||||||
// CountLower specifies how many lowercase letters (0x61 to 0x7a) should be specified at a minimum.
|
// CountLower specifies how many lowercase letters (0x61 to 0x7a) should be specified at a minimum.
|
||||||
CountLower uint `json:"lowers" toml:"lowers" yaml:"Number of Lowercase Letters" xml:"numLowers,attr"`
|
CountLower uint `json:"lowers" toml:"CountLower" yaml:"Number of Lowercase Letters" xml:"numLowers,attr"`
|
||||||
// CountNumbers specifies how many numbers (0x30 to 0x39) should be specified at a minimum.
|
// CountNumbers specifies how many numbers (0x30 to 0x39) should be specified at a minimum.
|
||||||
CountNumbers uint `json:"numbers" toml:"numbers" yaml:"Number of Numeric Characters" xml:"numNumeric,attr"`
|
CountNumbers uint `json:"numbers" toml:"CountNumber" yaml:"Number of Numeric Characters" xml:"numNumeric,attr"`
|
||||||
// CountSymbols specifies how many symbols (0x21 to 0x7e) should be specified at a minimum.
|
// CountSymbols specifies how many symbols (0x21 to 0x7e) should be specified at a minimum.
|
||||||
CountSymbols uint `json:"symbols" toml:"symbols" yaml:"Number of Symbols" xml:"numSymbols,attr"`
|
CountSymbols uint `json:"symbols" toml:"CountSymbol" yaml:"Number of Symbols" xml:"numSymbols,attr"`
|
||||||
// CountExtended specifies how many extended symbols (0x80 to 0xff) should be specified at a minimum.
|
// CountExtended specifies how many extended symbols (0x80 to 0xff) should be specified at a minimum.
|
||||||
CountExtended uint `json:"extended" toml:"extended" yaml:"Number of Extended Symbols" xml:"numXSymbols,attr"`
|
CountExtended uint `json:"extended" toml:"CountExtended" yaml:"Number of Extended Symbols" xml:"numXSymbols,attr"`
|
||||||
// DisabledChars includes characters that should NOT be included from the above selection options.
|
// DisabledChars includes characters that should NOT be included from the above selection options.
|
||||||
DisabledChars CharSet `json:"disabled_chars,omitempty" toml:"disabled_chars,omitempty" yaml:"Disabled CharSet,omitempty" xml:"disabledChars,omitempty"`
|
DisabledChars CharSet `json:"disabled_chars,omitempty" toml:"DisabledCharSet,omitempty" yaml:"Disabled CharSet,omitempty" xml:"disabledChars,attr,omitempty"`
|
||||||
// LengthMin specifies how long (in characters/bytes) each password should be at minimum. Use 0 for no minimum.
|
// LengthMin specifies how long (in characters/bytes) each password should be at minimum. Use 0 for no minimum.
|
||||||
LengthMin uint `json:"length_min" toml:"length_min" yaml:"Minimum Length" xml:"minLen,attr"`
|
LengthMin uint `json:"length_min" toml:"MinLength" yaml:"Minimum Length" xml:"minLen,attr"`
|
||||||
/*
|
/*
|
||||||
LengthMax specifies the maximum length for each password. Set to 0 for no limit
|
LengthMax specifies the maximum length for each password. Set to 0 for no limit
|
||||||
(the language has a hard limit of 18446744073709551615; this is limited to 256 for performance reasons).
|
(the language has a hard limit of 18446744073709551615; this is limited to 256 for performance reasons).
|
||||||
*/
|
*/
|
||||||
LengthMax uint `json:"length_max" toml:"length_max" yaml:"Maximum Length" xml:"maxLen,attr"`
|
LengthMax uint `json:"length_max" toml:"MaxLength" yaml:"Maximum Length" xml:"maxLen,attr"`
|
||||||
// Count specifies how many passwords to generate. If 0, the default is 1.
|
// Count specifies how many passwords to generate. If 0, the default is 1.
|
||||||
Count uint `json:"count" toml:"count" yaml:"Number of Passwords" xml:"genCount,attr"`
|
Count uint `json:"count" toml:"Count" yaml:"Number of Passwords" xml:"genCount,attr"`
|
||||||
// explicitCharset is the collection of acceptable characters as explicitly defined by the caller, if any.
|
// explicitCharset is the collection of acceptable characters as explicitly defined by the caller, if any.
|
||||||
explicitCharset CharSet
|
explicitCharset CharSet
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user