Files
QSfera/Server/vendor/github.com/trustelem/zxcvbn/matching/leet.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

169 lines
3.8 KiB
Go

package matching
import (
"bytes"
// "github.com/trustelem/zxcvbn/entropy"
"github.com/trustelem/zxcvbn/match"
"sort"
"strings"
)
type l33tMatch struct {
dm dictionaryMatch
table map[string][]string
}
func (lm l33tMatch) Matches(password string) []*match.Match {
matches := []*match.Match{}
substitutions := relevantSubtable(password, lm.table)
for _, sub := range enumerateLeetSubs(substitutions) {
if len(sub) == 0 {
break
}
subbedPassword := translate(password, sub)
for _, m := range lm.dm.Matches(subbedPassword) {
token := password[m.I : m.J+1]
if len(token) <= 1 {
// filter single-character l33t matches to reduce noise.
// otherwise '1' matches 'i', '4' matches 'a', both very common English words
continue
}
if strings.ToLower(token) == m.MatchedWord {
continue // only return the matches that return an actual substitution
}
m.Sub = make(map[string]string)
for subbed, chr := range sub {
if strings.Contains(token, subbed) {
m.Sub[subbed] = chr
}
}
m.L33t = true
m.Token = token
matches = append(matches, m)
}
}
match.Sort(matches)
return matches
}
func translate(password string, sub map[string]string) string {
var res string
for _, s := range password {
if v, ok := sub[string(s)]; ok {
res = res + v
} else {
res = res + string(s)
}
}
return res
}
type kv struct {
k string
v string
}
func dedup(subs [][]kv) [][]kv {
var res [][]kv
var b bytes.Buffer
members := make(map[string]bool)
for _, sub := range subs {
sort.SliceStable(sub, func(i, j int) bool {
return sub[i].k < sub[j].k
})
b.Reset()
for _, x := range sub {
b.WriteString(x.k)
b.WriteString(",")
b.WriteString(x.v)
}
key := b.String()
if !members[key] {
res = append(res, sub)
members[key] = true
}
}
return res
}
// enumerateLeetSubs returns the list of possible 1337 replacement dictionaries for a given password
func enumerateLeetSubs(table map[string][]string) []map[string]string {
var keys []string
for k := range table {
keys = append(keys, k)
}
sort.Strings(keys)
var subs = [][]kv{[]kv{}}
var helper func(keys []string)
helper = func(keys []string) {
if len(keys) == 0 {
return
}
firstKey := keys[0]
restKeys := keys[1:]
var nextSubs [][]kv
for _, l33tChr := range table[firstKey] {
for _, sub := range subs {
dupL33tIndex := -1
for i := 0; i < len(sub); i++ {
if sub[i].k == l33tChr {
dupL33tIndex = i
break
}
}
if dupL33tIndex == -1 {
subExtension := append(sub, kv{k: l33tChr, v: firstKey})
nextSubs = append(nextSubs, subExtension)
} else {
subAlternative := make([]kv, 0, len(sub))
subAlternative = append(subAlternative, sub[0:dupL33tIndex]...)
subAlternative = append(subAlternative, sub[dupL33tIndex+1:]...)
subAlternative = append(subAlternative, kv{k: l33tChr, v: firstKey})
// subAlternative := make([]kv, 0, len(sub))
// subAlternative = append(subAlternative, sub)
// subAlternative[dupL33tIndex] = {k:l33tChr,v:firstKey}
nextSubs = append(nextSubs, sub)
nextSubs = append(nextSubs, subAlternative)
}
}
}
subs = dedup(nextSubs)
helper(restKeys)
}
helper(keys)
var subDicts []map[string]string
for _, sub := range subs {
subDict := make(map[string]string)
for _, x := range sub {
subDict[x.k] = x.v
}
subDicts = append(subDicts, subDict)
}
return subDicts
}
func relevantSubtable(password string, table map[string][]string) map[string][]string {
passwordChars := make(map[rune]bool)
for _, chr := range password {
passwordChars[chr] = true
}
relevantSubs := make(map[string][]string)
for key, values := range table {
for _, value := range values {
if passwordChars[rune(value[0])] {
relevantSubs[key] = append(relevantSubs[key], value)
}
}
}
return relevantSubs
}