stubbed out
This commit is contained in:
parent
c1c8037b88
commit
1cb6879786
31
README.md
31
README.md
@ -1,3 +1,32 @@
|
|||||||
# PWGen
|
# PWGen
|
||||||
|
|
||||||
A password generator written in Golang that offers highly-customizable generated password schemes.
|
A password generator written in Golang that offers highly-customizable generated password schemes.
|
||||||
|
|
||||||
|
## What it Does Do
|
||||||
|
|
||||||
|
## What it Doesn't Do
|
||||||
|
PWGen does not, and will not, generate "Correct Horse Battery Stapler" passphrases (as demonstrated in [XKCD #936](https://xkcd.com/936/)).
|
||||||
|
|
||||||
|
The author of this library believes that strong, trustworthy password managers (such as [HashiCorp's](https://www.hashicorp.com/) [Vault](https://www.vaultproject.io/)) should be used to store passwords that are completely randomly (or, more likely, pseudo-randomly to be pedantic) generated with a large character space and length rather than using a combination of real words. Using real words is still susceptible to a social engineering/OSINT attack and/or dictionary attack (albeit certainly better than just using a single word, regardless of length).
|
||||||
|
|
||||||
|
The author is not unique in this belief, either. For example:
|
||||||
|
|
||||||
|
* [Rob Black](https://fractionalciso.com/correct-horse-battery-staple-review/) offers pragmatic criticism
|
||||||
|
* ["@procrastilearner" on Steemit](https://steemit.com/steemstem/@procrastilearner/correct-horse-battery-staple-is-wrong) offers mathematical/algorithmic criticism
|
||||||
|
* [Ken Munro](https://www.pentestpartners.com/security-blog/correcthorsebatterystaple-isnt-a-good-password-heres-why/) speaks a little about hashcat, which offers far more advanced cracking rules than Randall Munroe (author of XKCD) may have had in mind at the time of creating that comic.
|
||||||
|
* Even the venerable and well-respected Bruce Schneier [has spoken on this scheme](https://www.schneier.com/blog/archives/2014/03/choosing_secure_1.html) -- back in 2014.
|
||||||
|
* (and so on.)
|
||||||
|
|
||||||
|
If you decide that you still need this functionality, however, I recommend using something like [the Babble library](https://github.com/tjarratt/babble).
|
||||||
|
|
||||||
|
### Other Tips
|
||||||
|
|
||||||
|
#### Password Hints
|
||||||
|
|
||||||
|
Many services offer "password hints". These are useless at best and provide a vulnerability at worst.
|
||||||
|
|
||||||
|
If you are prompted for these and they are required (as they usually are), generate and use strong unique passwords for each question and store those "answers" in your password manager as well. This slightly weakens your account's access security (as you now have 3 -- or however many hint prompts are required -- that can be guessed instead of just 1) potentially, depending on how they implement the hint system, but there is absolutely no requirement that they be real answers. Doing so would lead to a more easily socially-engineered access of your account.
|
||||||
|
|
||||||
|
#### 2FA/MFA
|
||||||
|
|
||||||
|
If the service offers it, enable it. No arguments or excuses. It is the single most effective action you can take to protect your account's access and is well worth the slightly added complication of an additional auth method.
|
||||||
|
134
pwgenerator/consts.go
Normal file
134
pwgenerator/consts.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package pwgenerator
|
||||||
|
|
||||||
|
// Pre-defined charsets.
|
||||||
|
var (
|
||||||
|
// upper contains the characters from 0x41 to 0x5a ([A-Z]).
|
||||||
|
upper CharSet = CharSet{
|
||||||
|
'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',
|
||||||
|
}
|
||||||
|
// lower contains the characters from 0x61 to 0x7a ([a-z]).
|
||||||
|
lower CharSet = CharSet{
|
||||||
|
'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',
|
||||||
|
}
|
||||||
|
// alpha contains upper and lower.
|
||||||
|
alpha CharSet = append(upper, lower...)
|
||||||
|
// numeric contains the characters from 0x30 to 0x39 ([0-9]).
|
||||||
|
numeric CharSet = CharSet{
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
}
|
||||||
|
// alphaNumeric combines alpha and numeric. This is, by far, the most widely-supported charset.
|
||||||
|
alphaNumeric CharSet = append(alpha, numeric...)
|
||||||
|
/*
|
||||||
|
symbols contains the characters from:
|
||||||
|
0x21 to 0x2f
|
||||||
|
0x3a to 0x40
|
||||||
|
0x5b to 0x60
|
||||||
|
0x7b to 0x7e
|
||||||
|
*/
|
||||||
|
symbols CharSet = CharSet{
|
||||||
|
// 0x21 to 0x2f
|
||||||
|
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
|
||||||
|
// 0x3a to 0x40
|
||||||
|
':', ';', '<', '=', '>', '?', '@',
|
||||||
|
// 0x5b to 0x60
|
||||||
|
'[', '\\', ']', '^', '_', '`',
|
||||||
|
// 0x7b to 0x7e
|
||||||
|
'{', '|', '}', '~',
|
||||||
|
}
|
||||||
|
// safeAscii can be expected to support strong passwords while also be reasonably supported JUST about in every application.
|
||||||
|
safeAscii CharSet = append(alphaNumeric, symbols...)
|
||||||
|
/*
|
||||||
|
extendedSymbols contains the characters from 0x80 to 0xff, excluding:
|
||||||
|
0x81, 0x8d, 0x8f, 0x90, 0x9d, 0xa0, 0xad
|
||||||
|
as they have no defined ASCII mapping.
|
||||||
|
*/
|
||||||
|
extendedSymbols CharSet = CharSet{
|
||||||
|
'€', '‚', 'ƒ', '„', '…', '†', '‡', 'ˆ', '‰', 'Š', '‹', 'Œ',
|
||||||
|
'Ž', '‘', '’', '“', '”', '•', '–', '—', '˜', '™', 'š', '›',
|
||||||
|
'œ', 'ž', 'Ÿ', '¡', '¢', '£', '¤', '¥', '¦', '§', '¨', '©',
|
||||||
|
'ª', '«', '¬', '®', '¯', '°', '±', '²', '³', '´', 'µ', '¶',
|
||||||
|
'·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', 'À', 'Á', 'Â',
|
||||||
|
'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î',
|
||||||
|
'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú',
|
||||||
|
'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ',
|
||||||
|
'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò',
|
||||||
|
'ó', 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ',
|
||||||
|
}
|
||||||
|
// allSymbols contains all printable non-alphanumerics.
|
||||||
|
allSymbols CharSet = append(symbols, extendedSymbols...)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
/*
|
||||||
|
AmbiguousChars are visually-ambiguous characters that may be hard to visually differentiate
|
||||||
|
depending on the font (especially in monospace fonts).
|
||||||
|
|
||||||
|
To be specifically clear, each character is provided with its ASCII hex value.
|
||||||
|
If you need the decimal/octal/etc. reference instead, you can cross-reference
|
||||||
|
it via https://square-r00t.net/ascii.html.
|
||||||
|
*/
|
||||||
|
// TODO: finish this; left off at 0xaa.
|
||||||
|
AmbiguousChars []Char = []Char{
|
||||||
|
'`', // 0x60
|
||||||
|
'\'', // 0x27
|
||||||
|
'‘', // 0x91
|
||||||
|
'’', // 0x92
|
||||||
|
'"', // 0x22
|
||||||
|
'“', // 0x93
|
||||||
|
'”', // 0x94
|
||||||
|
'¨', // 0xa8
|
||||||
|
'*', // 0x2a
|
||||||
|
'•', // 0x95
|
||||||
|
',', // 0x2c
|
||||||
|
'.', // 0x2e
|
||||||
|
'‚', // 0x82
|
||||||
|
'…', // 0x85
|
||||||
|
'!', // 0x21
|
||||||
|
'¡', // 0xa1
|
||||||
|
'1', // 0x31
|
||||||
|
'I', // 0x49
|
||||||
|
'l', // 0x6c
|
||||||
|
'|', // 0x7c
|
||||||
|
'¦', // 0xa6
|
||||||
|
'c', // 0x63
|
||||||
|
'¢', // 0xa2
|
||||||
|
'0', // 0x30
|
||||||
|
'O', // 0x4f
|
||||||
|
'o', // 0x6f
|
||||||
|
'$', // 0x24
|
||||||
|
'S', // 0x53
|
||||||
|
's', // 0x73
|
||||||
|
'Š', // 0x8a
|
||||||
|
'š', // 0x9a
|
||||||
|
'§', // 0xa7
|
||||||
|
'-', // 0x2d
|
||||||
|
'_', // 0x5f
|
||||||
|
'–', // 0x96
|
||||||
|
'—', // 0x97
|
||||||
|
'~', // 0x7e
|
||||||
|
'˜', // 0x98
|
||||||
|
'^', // 0x5e
|
||||||
|
'ˆ', // 0x88
|
||||||
|
'%', // 0x25
|
||||||
|
'‰', // 0x89
|
||||||
|
'<', // 0x3c
|
||||||
|
'‹', // 0x8b
|
||||||
|
'>', // 0x3e
|
||||||
|
'›', // 0x9b
|
||||||
|
'Y', // 0x59
|
||||||
|
'Ÿ', // 0x9f
|
||||||
|
'¥', // 0xa5
|
||||||
|
'Z', // 0x5a
|
||||||
|
'z', // 0x7a
|
||||||
|
'Ž', // 0x8e
|
||||||
|
'ž', // 0x9e
|
||||||
|
'(', // 0x28
|
||||||
|
'[', // 0x5b
|
||||||
|
'{', // 0x7b
|
||||||
|
')', // 0x29
|
||||||
|
']', // 0x5d
|
||||||
|
'}', // 0x7d
|
||||||
|
}
|
||||||
|
)
|
9
pwgenerator/errs.go
Normal file
9
pwgenerator/errs.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package pwgenerator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadType error = errors.New("cannot typeswitch; unsupported type")
|
||||||
|
)
|
33
pwgenerator/funcs.go
Normal file
33
pwgenerator/funcs.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package pwgenerator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sortDedupe sorts a slice of runes and deduplicates them.
|
||||||
|
func sortDedupe(charset *CharSet) {
|
||||||
|
|
||||||
|
var vals map[Char]bool = make(map[Char]bool)
|
||||||
|
var sorted CharSet
|
||||||
|
var idx int
|
||||||
|
|
||||||
|
// This inherently dedupes.
|
||||||
|
for _, i := range *charset {
|
||||||
|
vals[i] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handles sorting.
|
||||||
|
sorted = make(CharSet, len(vals))
|
||||||
|
// First we need a slice (again).
|
||||||
|
for k := range vals {
|
||||||
|
sorted[idx] = k
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
// And now we can sort...
|
||||||
|
sort.Sort(&sorted)
|
||||||
|
|
||||||
|
// And replace the original charset with the sorted, deduplicated one.
|
||||||
|
*charset = sorted
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
41
pwgenerator/funcs_charset.go
Normal file
41
pwgenerator/funcs_charset.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package pwgenerator
|
||||||
|
|
||||||
|
// Len returns the length of a CharSet (needed for sort.Interface).
|
||||||
|
func (c *CharSet) Len() (l int) {
|
||||||
|
|
||||||
|
l = len(*c)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns true if item at index i is "less than" (sorts before) item at index j (needed for sort.Interface).
|
||||||
|
func (c *CharSet) Less(i, j int) (isBefore bool) {
|
||||||
|
|
||||||
|
if (*c)[i] < (*c)[j] {
|
||||||
|
isBefore = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string from a CharSet.
|
||||||
|
func (c *CharSet) String() (s string) {
|
||||||
|
|
||||||
|
for _, i := range *c {
|
||||||
|
s += string(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap will swap the position of the item at index i and the item at index j in a CharSet (needed for sort.Interface).
|
||||||
|
func (c *CharSet) Swap(i, j int) {
|
||||||
|
|
||||||
|
var iVal Char = (*c)[i]
|
||||||
|
var jVal Char = (*c)[j]
|
||||||
|
|
||||||
|
(*c)[i] = jVal
|
||||||
|
(*c)[j] = iVal
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
99
pwgenerator/funcs_genopts.go
Normal file
99
pwgenerator/funcs_genopts.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package pwgenerator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generate generates a list of passwords for a GenOpts.
|
||||||
|
func (o *GenOpts) Generate() (passwords []string, err error) {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chars returns the list of evaluated characters that would be used in a GenOpts.
|
||||||
|
func (o *GenOpts) Chars() (chars CharSet) {
|
||||||
|
|
||||||
|
chars = make(CharSet, 0)
|
||||||
|
|
||||||
|
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...)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
55
pwgenerator/funcs_predefs.go
Normal file
55
pwgenerator/funcs_predefs.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package pwgenerator
|
||||||
|
|
||||||
|
// Upper returns the set of uppercase letters.
|
||||||
|
func Upper() CharSet {
|
||||||
|
return upper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower returns the set of lowercase letters.
|
||||||
|
func Lower() CharSet {
|
||||||
|
return upper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alpha returns the set of all letters (upper/lowercase).
|
||||||
|
func Alpha() CharSet {
|
||||||
|
return alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric returns the set of all numbers.
|
||||||
|
func Numeric() CharSet {
|
||||||
|
return numeric
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlphaNumeric returns the set of all letters and numbers.
|
||||||
|
func AlphaNumeric() CharSet {
|
||||||
|
return alphaNumeric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbols returns the set of all "simple" symbols.
|
||||||
|
func Symbols() CharSet {
|
||||||
|
return symbols
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ExtendedSymbols returns the set of the "extended" symbols.
|
||||||
|
|
||||||
|
Don't be fooled; these are still ASCII but are unlikely to be supported in an application consuming passwords.
|
||||||
|
|
||||||
|
If it does, however, adding just one to your password GREATLY increases the protection it has against bruteforce/dictionary attacks.
|
||||||
|
*/
|
||||||
|
func ExtendedSymbols() CharSet {
|
||||||
|
return extendedSymbols
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllSymbols returns both simple and extended symbols.
|
||||||
|
func AllSymbols() CharSet {
|
||||||
|
return allSymbols
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SafeAscii returns alphanumeric and simple symbol charset; these have
|
||||||
|
the highest yield of good character space while ensuring that MOST applications will accept them as input.
|
||||||
|
*/
|
||||||
|
func SafeAscii() CharSet {
|
||||||
|
return safeAscii
|
||||||
|
}
|
52
pwgenerator/types.go
Normal file
52
pwgenerator/types.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package pwgenerator
|
||||||
|
|
||||||
|
// Char is implemented as a rune.
|
||||||
|
type Char rune
|
||||||
|
|
||||||
|
// CharSet is a collection of characters (Char) but with additional methods (e.g. sort.Sort interface conformance).
|
||||||
|
type CharSet []Char
|
||||||
|
|
||||||
|
// GenOpts controls what kind of (and how many) password(s) should be generated.
|
||||||
|
type GenOpts struct {
|
||||||
|
/*
|
||||||
|
Some references below contain a reference to the character's hex ASCII.
|
||||||
|
If you need the decimal/octal/etc. reference instead, you can cross-reference
|
||||||
|
it via https://square-r00t.net/ascii.html.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
HumanOnly avoids visually-ambiguous characters if enabled, ensuring a more readable, visually-distinguishable,
|
||||||
|
and accessible but MUCH smaller password character space (from about 220 characters to ), thus much less secure.
|
||||||
|
|
||||||
|
TODO: Get feedback on this. I feel like I'm trimming out WAY too many chars.
|
||||||
|
*/
|
||||||
|
// HumanOnly bool `json:"do_human_readable"`
|
||||||
|
// Alpha is true if letters (0x41 to 0x5a, 0x61 to 0x7a) should be included.
|
||||||
|
Alpha bool `json:"do_alpha"`
|
||||||
|
// Numeric is true if numbers (0x30 to 0x39) should be included.
|
||||||
|
Numeric bool `json:"do_numeric"`
|
||||||
|
// Symbols is true if non-alphanumeric characters (between 0x21 and 0x7e) should be included.
|
||||||
|
Symbols bool `json:"do_symbols"`
|
||||||
|
// ExtendedSymbols is true if non-alphanumeric characters in the "extended ASCII" set (0x80 to 0xff) should be included.
|
||||||
|
ExtendedSymbols bool `json:"do_extended"`
|
||||||
|
// CountUpper specifies how many uppercase letters (0x41 to 0x5a) should be specified at a minimum.
|
||||||
|
CountUpper uint `json:"uppers"`
|
||||||
|
// CountLower specifies how many lowercase letters (0x61 to 0x7a) should be specified at a minimum.
|
||||||
|
CountLower uint `json:"lowers"`
|
||||||
|
// CountSymbols specifies how many symbols (0x21 to 0x7e) should be specified at a minimum.
|
||||||
|
CountSymbols uint `json:"symbols"`
|
||||||
|
// CountExtended specifies how many extended symbols (0x80 to 0xff) should be specified at a minimum.
|
||||||
|
CountExtended uint `json:"extended"`
|
||||||
|
// DisabledChars includes characters that should NOT be included from the above selection options.
|
||||||
|
DisabledChars CharSet `json:"disabled_chars"`
|
||||||
|
// LengthMin specifies how long (in characters/bytes) each password should be at minimum. Use 0 for no minimum.
|
||||||
|
LengthMin uint `json:"length_min"`
|
||||||
|
/*
|
||||||
|
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).
|
||||||
|
*/
|
||||||
|
LengthMax uint `json:"length_max"`
|
||||||
|
// Count specifies how many passwords to generate. If 0, the default is 1.
|
||||||
|
Count uint `json:"count"`
|
||||||
|
// explicitCharset is the collection of acceptable characters as explicitly defined by the caller, if any.
|
||||||
|
explicitCharset CharSet
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user