FIXED:
* XML, YAML marshaling/unmarshaling of pwgenerator.CharSet.
This commit is contained in:
brent saner 2024-07-17 04:03:31 -04:00
parent c6050a2dc4
commit cbecd9caec
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
10 changed files with 465 additions and 178 deletions

18
go.mod
View File

@ -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
View File

@ -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=

View 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
}

View 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

View 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"
/>

View 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

View File

@ -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
}

View File

@ -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
View 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())
}

View File

@ -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
} }