aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/nbutton23/zxcvbn-go/matching
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-07-04 11:12:55 +0200
committerDmitry Vyukov <dvyukov@google.com>2020-07-04 15:05:30 +0200
commitc7d7f10bdff703e4a3c0414e8a33d4e45c91eb35 (patch)
tree0dff0ee1f98dbfa3ad8776112053a450d176592b /vendor/github.com/nbutton23/zxcvbn-go/matching
parent9573094ce235bd9afe88f5da27a47dd6bcc1e13b (diff)
go.mod: vendor golangci-lint
Diffstat (limited to 'vendor/github.com/nbutton23/zxcvbn-go/matching')
-rw-r--r--vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go209
-rw-r--r--vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go57
-rw-r--r--vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go234
-rw-r--r--vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go82
-rw-r--r--vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go67
-rw-r--r--vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go76
-rw-r--r--vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go88
7 files changed, 813 insertions, 0 deletions
diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go
new file mode 100644
index 000000000..8dfdf2410
--- /dev/null
+++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go
@@ -0,0 +1,209 @@
+package matching
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/nbutton23/zxcvbn-go/entropy"
+ "github.com/nbutton23/zxcvbn-go/match"
+)
+
+const (
+ dateSepMatcherName = "DATESEP"
+ dateWithOutSepMatcherName = "DATEWITHOUT"
+)
+
+var (
+ dateRxYearSuffix = regexp.MustCompile(`((\d{1,2})(\s|-|\/|\\|_|\.)(\d{1,2})(\s|-|\/|\\|_|\.)(19\d{2}|200\d|201\d|\d{2}))`)
+ dateRxYearPrefix = regexp.MustCompile(`((19\d{2}|200\d|201\d|\d{2})(\s|-|/|\\|_|\.)(\d{1,2})(\s|-|/|\\|_|\.)(\d{1,2}))`)
+ dateWithOutSepMatch = regexp.MustCompile(`\d{4,8}`)
+)
+
+//FilterDateSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
+func FilterDateSepMatcher(m match.Matcher) bool {
+ return m.ID == dateSepMatcherName
+}
+
+//FilterDateWithoutSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
+func FilterDateWithoutSepMatcher(m match.Matcher) bool {
+ return m.ID == dateWithOutSepMatcherName
+}
+
+func checkDate(day, month, year int64) (bool, int64, int64, int64) {
+ if (12 <= month && month <= 31) && day <= 12 {
+ day, month = month, day
+ }
+
+ if day > 31 || month > 12 {
+ return false, 0, 0, 0
+ }
+
+ if !((1900 <= year && year <= 2019) || (0 <= year && year <= 99)) {
+ return false, 0, 0, 0
+ }
+
+ return true, day, month, year
+}
+
+func dateSepMatcher(password string) []match.Match {
+ dateMatches := dateSepMatchHelper(password)
+
+ var matches []match.Match
+ for _, dateMatch := range dateMatches {
+ match := match.Match{
+ I: dateMatch.I,
+ J: dateMatch.J,
+ Entropy: entropy.DateEntropy(dateMatch),
+ DictionaryName: "date_match",
+ Token: dateMatch.Token,
+ }
+
+ matches = append(matches, match)
+ }
+
+ return matches
+}
+func dateSepMatchHelper(password string) []match.DateMatch {
+
+ var matches []match.DateMatch
+
+ for _, v := range dateRxYearSuffix.FindAllString(password, len(password)) {
+ splitV := dateRxYearSuffix.FindAllStringSubmatch(v, len(v))
+ i := strings.Index(password, v)
+ j := i + len(v)
+ day, _ := strconv.ParseInt(splitV[0][4], 10, 16)
+ month, _ := strconv.ParseInt(splitV[0][2], 10, 16)
+ year, _ := strconv.ParseInt(splitV[0][6], 10, 16)
+ match := match.DateMatch{Day: day, Month: month, Year: year, Separator: splitV[0][5], I: i, J: j, Token: password[i:j]}
+ matches = append(matches, match)
+ }
+
+ for _, v := range dateRxYearPrefix.FindAllString(password, len(password)) {
+ splitV := dateRxYearPrefix.FindAllStringSubmatch(v, len(v))
+ i := strings.Index(password, v)
+ j := i + len(v)
+ day, _ := strconv.ParseInt(splitV[0][4], 10, 16)
+ month, _ := strconv.ParseInt(splitV[0][6], 10, 16)
+ year, _ := strconv.ParseInt(splitV[0][2], 10, 16)
+ match := match.DateMatch{Day: day, Month: month, Year: year, Separator: splitV[0][5], I: i, J: j, Token: password[i:j]}
+ matches = append(matches, match)
+ }
+
+ var out []match.DateMatch
+ for _, match := range matches {
+ if valid, day, month, year := checkDate(match.Day, match.Month, match.Year); valid {
+ match.Pattern = "date"
+ match.Day = day
+ match.Month = month
+ match.Year = year
+ out = append(out, match)
+ }
+ }
+ return out
+
+}
+
+type dateMatchCandidate struct {
+ DayMonth string
+ Year string
+ I, J int
+}
+
+type dateMatchCandidateTwo struct {
+ Day string
+ Month string
+ Year string
+ I, J int
+}
+
+func dateWithoutSepMatch(password string) []match.Match {
+ dateMatches := dateWithoutSepMatchHelper(password)
+
+ var matches []match.Match
+ for _, dateMatch := range dateMatches {
+ match := match.Match{
+ I: dateMatch.I,
+ J: dateMatch.J,
+ Entropy: entropy.DateEntropy(dateMatch),
+ DictionaryName: "date_match",
+ Token: dateMatch.Token,
+ }
+
+ matches = append(matches, match)
+ }
+
+ return matches
+}
+
+//TODO Has issues with 6 digit dates
+func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) {
+ for _, v := range dateWithOutSepMatch.FindAllString(password, len(password)) {
+ i := strings.Index(password, v)
+ j := i + len(v)
+ length := len(v)
+ lastIndex := length - 1
+ var candidatesRoundOne []dateMatchCandidate
+
+ if length <= 6 {
+ //2-digit year prefix
+ candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[2:], v[0:2], i, j))
+
+ //2-digityear suffix
+ candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-2], v[lastIndex-2:], i, j))
+ }
+ if length >= 6 {
+ //4-digit year prefix
+ candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[4:], v[0:4], i, j))
+
+ //4-digit year sufix
+ candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-3], v[lastIndex-3:], i, j))
+ }
+
+ var candidatesRoundTwo []dateMatchCandidateTwo
+ for _, c := range candidatesRoundOne {
+ if len(c.DayMonth) == 2 {
+ candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:0], c.DayMonth[1:1], c.Year, c.I, c.J))
+ } else if len(c.DayMonth) == 3 {
+ candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:2], c.DayMonth[2:2], c.Year, c.I, c.J))
+ candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:0], c.DayMonth[1:3], c.Year, c.I, c.J))
+ } else if len(c.DayMonth) == 4 {
+ candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:2], c.DayMonth[2:4], c.Year, c.I, c.J))
+ }
+ }
+
+ for _, candidate := range candidatesRoundTwo {
+ intDay, err := strconv.ParseInt(candidate.Day, 10, 16)
+ if err != nil {
+ continue
+ }
+
+ intMonth, err := strconv.ParseInt(candidate.Month, 10, 16)
+
+ if err != nil {
+ continue
+ }
+
+ intYear, err := strconv.ParseInt(candidate.Year, 10, 16)
+ if err != nil {
+ continue
+ }
+
+ if ok, _, _, _ := checkDate(intDay, intMonth, intYear); ok {
+ matches = append(matches, match.DateMatch{Token: password, Pattern: "date", Day: intDay, Month: intMonth, Year: intYear, I: i, J: j})
+ }
+
+ }
+ }
+
+ return matches
+}
+
+func buildDateMatchCandidate(dayMonth, year string, i, j int) dateMatchCandidate {
+ return dateMatchCandidate{DayMonth: dayMonth, Year: year, I: i, J: j}
+}
+
+func buildDateMatchCandidateTwo(day, month string, year string, i, j int) dateMatchCandidateTwo {
+
+ return dateMatchCandidateTwo{Day: day, Month: month, Year: year, I: i, J: j}
+}
diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go
new file mode 100644
index 000000000..4ddb2c3b0
--- /dev/null
+++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go
@@ -0,0 +1,57 @@
+package matching
+
+import (
+ "strings"
+
+ "github.com/nbutton23/zxcvbn-go/entropy"
+ "github.com/nbutton23/zxcvbn-go/match"
+)
+
+func buildDictMatcher(dictName string, rankedDict map[string]int) func(password string) []match.Match {
+ return func(password string) []match.Match {
+ matches := dictionaryMatch(password, dictName, rankedDict)
+ for _, v := range matches {
+ v.DictionaryName = dictName
+ }
+ return matches
+ }
+
+}
+
+func dictionaryMatch(password string, dictionaryName string, rankedDict map[string]int) []match.Match {
+ var results []match.Match
+ pwLower := strings.ToLower(password)
+
+ pwLowerRunes := []rune(pwLower)
+ length := len(pwLowerRunes)
+
+ for i := 0; i < length; i++ {
+ for j := i; j < length; j++ {
+ word := pwLowerRunes[i : j+1]
+ if val, ok := rankedDict[string(word)]; ok {
+ matchDic := match.Match{Pattern: "dictionary",
+ DictionaryName: dictionaryName,
+ I: i,
+ J: j,
+ Token: string([]rune(password)[i : j+1]),
+ }
+ matchDic.Entropy = entropy.DictionaryEntropy(matchDic, float64(val))
+
+ results = append(results, matchDic)
+ }
+ }
+ }
+
+ return results
+}
+
+func buildRankedDict(unrankedList []string) map[string]int {
+
+ result := make(map[string]int)
+
+ for i, v := range unrankedList {
+ result[strings.ToLower(v)] = i + 1
+ }
+
+ return result
+}
diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go
new file mode 100644
index 000000000..610f1973f
--- /dev/null
+++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go
@@ -0,0 +1,234 @@
+package matching
+
+import (
+ "strings"
+
+ "github.com/nbutton23/zxcvbn-go/entropy"
+ "github.com/nbutton23/zxcvbn-go/match"
+)
+
+// L33TMatcherName id
+const L33TMatcherName = "l33t"
+
+//FilterL33tMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
+func FilterL33tMatcher(m match.Matcher) bool {
+ return m.ID == L33TMatcherName
+}
+
+func l33tMatch(password string) []match.Match {
+ permutations := getPermutations(password)
+
+ var matches []match.Match
+
+ for _, permutation := range permutations {
+ for _, mather := range dictionaryMatchers {
+ matches = append(matches, mather.MatchingFunc(permutation)...)
+ }
+ }
+
+ for _, match := range matches {
+ match.Entropy += entropy.ExtraLeetEntropy(match, password)
+ match.DictionaryName = match.DictionaryName + "_3117"
+ }
+
+ return matches
+}
+
+// This function creates a list of permutations based on a fixed table stored on data. The table
+// will be reduced in order to proceed in the function using only relevant values (see
+// relevantL33tSubtable).
+func getPermutations(password string) []string {
+ substitutions := relevantL33tSubtable(password)
+ permutations := getAllPermutationsOfLeetSubstitutions(password, substitutions)
+ return permutations
+}
+
+// This function loads the table from data but only keep in memory the values that are present
+// inside the provided password.
+func relevantL33tSubtable(password string) map[string][]string {
+ relevantSubs := make(map[string][]string)
+ for key, values := range l33tTable.Graph {
+ for _, value := range values {
+ if strings.Contains(password, value) {
+ relevantSubs[key] = append(relevantSubs[key], value)
+ }
+ }
+ }
+
+ return relevantSubs
+}
+
+// This function creates the list of permutations of a given password using the provided table as
+// reference for its operation.
+func getAllPermutationsOfLeetSubstitutions(password string, table map[string][]string) []string {
+ result := []string{}
+
+ // create a list of tables without conflicting keys/values (this happens for "|", "7" and "1")
+ noConflictsTables := createListOfMapsWithoutConflicts(table)
+ for _, noConflictsTable := range noConflictsTables {
+ substitutionsMaps := createSubstitutionsMapsFromTable(noConflictsTable)
+ for _, substitutionsMap := range substitutionsMaps {
+ newValue := createWordForSubstitutionMap(password, substitutionsMap)
+ if !stringSliceContainsValue(result, newValue) {
+ result = append(result, newValue)
+ }
+ }
+ }
+
+ return result
+}
+
+// Create the possible list of maps removing the conflicts from it. As an example, the value "|"
+// may represent "i" and "l". For each representation of the conflicting value, a new map is
+// created. This may grow exponencialy according to the number of conflicts. The number of maps
+// returned by this function may be reduced if the relevantL33tSubtable function was called to
+// identify only relevant items.
+func createListOfMapsWithoutConflicts(table map[string][]string) []map[string][]string {
+ // the resulting list starts with the provided table
+ result := []map[string][]string{}
+ result = append(result, table)
+
+ // iterate over the list of conflicts in order to expand the maps for each one
+ conflicts := retrieveConflictsListFromTable(table)
+ for _, value := range conflicts {
+ newMapList := []map[string][]string{}
+
+ // for each conflict a new list of maps will be created for every already known map
+ for _, currentMap := range result {
+ newMaps := createDifferentMapsForLeetChar(currentMap, value)
+ newMapList = append(newMapList, newMaps...)
+ }
+
+ result = newMapList
+ }
+
+ return result
+}
+
+// This function retrieves the list of values that appear for one or more keys. This is usefull to
+// know which l33t chars can represent more than one letter.
+func retrieveConflictsListFromTable(table map[string][]string) []string {
+ result := []string{}
+ foundValues := []string{}
+
+ for _, values := range table {
+ for _, value := range values {
+ if stringSliceContainsValue(foundValues, value) {
+ // only add on results if it was not identified as conflict before
+ if !stringSliceContainsValue(result, value) {
+ result = append(result, value)
+ }
+ } else {
+ foundValues = append(foundValues, value)
+ }
+ }
+ }
+
+ return result
+}
+
+// This function aims to create different maps for a given char if this char represents a conflict.
+// If the specified char is not a conflit one, the same map will be returned. In scenarios which
+// the provided char can not be found on map, an empty list will be returned. This function was
+// designed to be used on conflicts situations.
+func createDifferentMapsForLeetChar(table map[string][]string, leetChar string) []map[string][]string {
+ result := []map[string][]string{}
+
+ keysWithSameValue := retrieveListOfKeysWithSpecificValueFromTable(table, leetChar)
+ for _, key := range keysWithSameValue {
+ newMap := copyMapRemovingSameValueFromOtherKeys(table, key, leetChar)
+ result = append(result, newMap)
+ }
+
+ return result
+}
+
+// This function retrieves the list of keys that can be represented using the given value.
+func retrieveListOfKeysWithSpecificValueFromTable(table map[string][]string, valueToFind string) []string {
+ result := []string{}
+
+ for key, values := range table {
+ for _, value := range values {
+ if value == valueToFind && !stringSliceContainsValue(result, key) {
+ result = append(result, key)
+ }
+ }
+ }
+
+ return result
+}
+
+// This function returns a lsit of substitution map from a given table. Each map in the result will
+// provide only one representation for each value. As an example, if the provided map contains the
+// values "@" and "4" in the possibilities to represent "a", two maps will be created where one
+// will contain "a" mapping to "@" and the other one will provide "a" mapping to "4".
+func createSubstitutionsMapsFromTable(table map[string][]string) []map[string]string {
+ result := []map[string]string{{"": ""}}
+
+ for key, values := range table {
+ newResult := []map[string]string{}
+
+ for _, mapInCurrentResult := range result {
+ for _, value := range values {
+ newMapForValue := copyMap(mapInCurrentResult)
+ newMapForValue[key] = value
+ newResult = append(newResult, newMapForValue)
+ }
+ }
+
+ result = newResult
+ }
+
+ // verification to make sure that the slice was filled
+ if len(result) == 1 && len(result[0]) == 1 && result[0][""] == "" {
+ return []map[string]string{}
+ }
+
+ return result
+}
+
+// This function replaces the values provided on substitution map over the provided word.
+func createWordForSubstitutionMap(word string, substitutionMap map[string]string) string {
+ result := word
+ for key, value := range substitutionMap {
+ result = strings.Replace(result, value, key, -1)
+ }
+
+ return result
+}
+
+func stringSliceContainsValue(slice []string, value string) bool {
+ for _, valueInSlice := range slice {
+ if valueInSlice == value {
+ return true
+ }
+ }
+
+ return false
+}
+
+func copyMap(table map[string]string) map[string]string {
+ result := make(map[string]string)
+
+ for key, value := range table {
+ result[key] = value
+ }
+
+ return result
+}
+
+// This function creates a new map based on the one provided but excluding possible representations
+// of the same value on other keys.
+func copyMapRemovingSameValueFromOtherKeys(table map[string][]string, keyToFix string, valueToFix string) map[string][]string {
+ result := make(map[string][]string)
+
+ for key, values := range table {
+ for _, value := range values {
+ if !(value == valueToFix && key != keyToFix) {
+ result[key] = append(result[key], value)
+ }
+ }
+ }
+
+ return result
+}
diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go
new file mode 100644
index 000000000..4577db8a4
--- /dev/null
+++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go
@@ -0,0 +1,82 @@
+package matching
+
+import (
+ "sort"
+
+ "github.com/nbutton23/zxcvbn-go/adjacency"
+ "github.com/nbutton23/zxcvbn-go/frequency"
+ "github.com/nbutton23/zxcvbn-go/match"
+)
+
+var (
+ dictionaryMatchers []match.Matcher
+ matchers []match.Matcher
+ adjacencyGraphs []adjacency.Graph
+ l33tTable adjacency.Graph
+
+ sequences map[string]string
+)
+
+func init() {
+ loadFrequencyList()
+}
+
+// Omnimatch runs all matchers against the password
+func Omnimatch(password string, userInputs []string, filters ...func(match.Matcher) bool) (matches []match.Match) {
+
+ //Can I run into the issue where nil is not equal to nil?
+ if dictionaryMatchers == nil || adjacencyGraphs == nil {
+ loadFrequencyList()
+ }
+
+ if userInputs != nil {
+ userInputMatcher := buildDictMatcher("user_inputs", buildRankedDict(userInputs))
+ matches = userInputMatcher(password)
+ }
+
+ for _, matcher := range matchers {
+ shouldBeFiltered := false
+ for i := range filters {
+ if filters[i](matcher) {
+ shouldBeFiltered = true
+ break
+ }
+ }
+ if !shouldBeFiltered {
+ matches = append(matches, matcher.MatchingFunc(password)...)
+ }
+ }
+ sort.Sort(match.Matches(matches))
+ return matches
+}
+
+func loadFrequencyList() {
+
+ for n, list := range frequency.Lists {
+ dictionaryMatchers = append(dictionaryMatchers, match.Matcher{MatchingFunc: buildDictMatcher(n, buildRankedDict(list.List)), ID: n})
+ }
+
+ l33tTable = adjacency.GraphMap["l33t"]
+
+ adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["qwerty"])
+ adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["dvorak"])
+ adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["keypad"])
+ adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["macKeypad"])
+
+ //l33tFilePath, _ := filepath.Abs("adjacency/L33t.json")
+ //L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t")
+
+ sequences = make(map[string]string)
+ sequences["lower"] = "abcdefghijklmnopqrstuvwxyz"
+ sequences["upper"] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ sequences["digits"] = "0123456789"
+
+ matchers = append(matchers, dictionaryMatchers...)
+ matchers = append(matchers, match.Matcher{MatchingFunc: spatialMatch, ID: spatialMatcherName})
+ matchers = append(matchers, match.Matcher{MatchingFunc: repeatMatch, ID: repeatMatcherName})
+ matchers = append(matchers, match.Matcher{MatchingFunc: sequenceMatch, ID: sequenceMatcherName})
+ matchers = append(matchers, match.Matcher{MatchingFunc: l33tMatch, ID: L33TMatcherName})
+ matchers = append(matchers, match.Matcher{MatchingFunc: dateSepMatcher, ID: dateSepMatcherName})
+ matchers = append(matchers, match.Matcher{MatchingFunc: dateWithoutSepMatch, ID: dateWithOutSepMatcherName})
+
+}
diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go
new file mode 100644
index 000000000..a93e45935
--- /dev/null
+++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go
@@ -0,0 +1,67 @@
+package matching
+
+import (
+ "strings"
+
+ "github.com/nbutton23/zxcvbn-go/entropy"
+ "github.com/nbutton23/zxcvbn-go/match"
+)
+
+const repeatMatcherName = "REPEAT"
+
+//FilterRepeatMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
+func FilterRepeatMatcher(m match.Matcher) bool {
+ return m.ID == repeatMatcherName
+}
+
+func repeatMatch(password string) []match.Match {
+ var matches []match.Match
+
+ //Loop through password. if current == prev currentStreak++ else if currentStreak > 2 {buildMatch; currentStreak = 1} prev = current
+ var current, prev string
+ currentStreak := 1
+ var i int
+ var char rune
+ for i, char = range password {
+ current = string(char)
+ if i == 0 {
+ prev = current
+ continue
+ }
+
+ if strings.ToLower(current) == strings.ToLower(prev) {
+ currentStreak++
+
+ } else if currentStreak > 2 {
+ iPos := i - currentStreak
+ jPos := i - 1
+ matchRepeat := match.Match{
+ Pattern: "repeat",
+ I: iPos,
+ J: jPos,
+ Token: password[iPos : jPos+1],
+ DictionaryName: prev}
+ matchRepeat.Entropy = entropy.RepeatEntropy(matchRepeat)
+ matches = append(matches, matchRepeat)
+ currentStreak = 1
+ } else {
+ currentStreak = 1
+ }
+
+ prev = current
+ }
+
+ if currentStreak > 2 {
+ iPos := i - currentStreak + 1
+ jPos := i
+ matchRepeat := match.Match{
+ Pattern: "repeat",
+ I: iPos,
+ J: jPos,
+ Token: password[iPos : jPos+1],
+ DictionaryName: prev}
+ matchRepeat.Entropy = entropy.RepeatEntropy(matchRepeat)
+ matches = append(matches, matchRepeat)
+ }
+ return matches
+}
diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go
new file mode 100644
index 000000000..e0ed05229
--- /dev/null
+++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go
@@ -0,0 +1,76 @@
+package matching
+
+import (
+ "strings"
+
+ "github.com/nbutton23/zxcvbn-go/entropy"
+ "github.com/nbutton23/zxcvbn-go/match"
+)
+
+const sequenceMatcherName = "SEQ"
+
+//FilterSequenceMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
+func FilterSequenceMatcher(m match.Matcher) bool {
+ return m.ID == sequenceMatcherName
+}
+
+func sequenceMatch(password string) []match.Match {
+ var matches []match.Match
+ for i := 0; i < len(password); {
+ j := i + 1
+ var seq string
+ var seqName string
+ seqDirection := 0
+ for seqCandidateName, seqCandidate := range sequences {
+ iN := strings.Index(seqCandidate, string(password[i]))
+ var jN int
+ if j < len(password) {
+ jN = strings.Index(seqCandidate, string(password[j]))
+ } else {
+ jN = -1
+ }
+
+ if iN > -1 && jN > -1 {
+ direction := jN - iN
+ if direction == 1 || direction == -1 {
+ seq = seqCandidate
+ seqName = seqCandidateName
+ seqDirection = direction
+ break
+ }
+ }
+
+ }
+
+ if seq != "" {
+ for {
+ var prevN, curN int
+ if j < len(password) {
+ prevChar, curChar := password[j-1], password[j]
+ prevN, curN = strings.Index(seq, string(prevChar)), strings.Index(seq, string(curChar))
+ }
+
+ if j == len(password) || curN-prevN != seqDirection {
+ if j-i > 2 {
+ matchSequence := match.Match{
+ Pattern: "sequence",
+ I: i,
+ J: j - 1,
+ Token: password[i:j],
+ DictionaryName: seqName,
+ }
+
+ matchSequence.Entropy = entropy.SequenceEntropy(matchSequence, len(seq), (seqDirection == 1))
+ matches = append(matches, matchSequence)
+ }
+ break
+ } else {
+ j++
+ }
+
+ }
+ }
+ i = j
+ }
+ return matches
+}
diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go
new file mode 100644
index 000000000..fd858f5d1
--- /dev/null
+++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go
@@ -0,0 +1,88 @@
+package matching
+
+import (
+ "strings"
+
+ "github.com/nbutton23/zxcvbn-go/adjacency"
+ "github.com/nbutton23/zxcvbn-go/entropy"
+ "github.com/nbutton23/zxcvbn-go/match"
+)
+
+const spatialMatcherName = "SPATIAL"
+
+//FilterSpatialMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
+func FilterSpatialMatcher(m match.Matcher) bool {
+ return m.ID == spatialMatcherName
+}
+
+func spatialMatch(password string) (matches []match.Match) {
+ for _, graph := range adjacencyGraphs {
+ if graph.Graph != nil {
+ matches = append(matches, spatialMatchHelper(password, graph)...)
+ }
+ }
+ return matches
+}
+
+func spatialMatchHelper(password string, graph adjacency.Graph) (matches []match.Match) {
+
+ for i := 0; i < len(password)-1; {
+ j := i + 1
+ lastDirection := -99 //an int that it should never be!
+ turns := 0
+ shiftedCount := 0
+
+ for {
+ prevChar := password[j-1]
+ found := false
+ foundDirection := -1
+ curDirection := -1
+ //My graphs seem to be wrong. . . and where the hell is qwerty
+ adjacents := graph.Graph[string(prevChar)]
+ //Consider growing pattern by one character if j hasn't gone over the edge
+ if j < len(password) {
+ curChar := password[j]
+ for _, adj := range adjacents {
+ curDirection++
+
+ if strings.Index(adj, string(curChar)) != -1 {
+ found = true
+ foundDirection = curDirection
+
+ if strings.Index(adj, string(curChar)) == 1 {
+ //index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc.
+ //for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted.
+ shiftedCount++
+ }
+
+ if lastDirection != foundDirection {
+ //adding a turn is correct even in the initial case when last_direction is null:
+ //every spatial pattern starts with a turn.
+ turns++
+ lastDirection = foundDirection
+ }
+ break
+ }
+ }
+ }
+
+ //if the current pattern continued, extend j and try to grow again
+ if found {
+ j++
+ } else {
+ //otherwise push the pattern discovered so far, if any...
+ //don't consider length 1 or 2 chains.
+ if j-i > 2 {
+ matchSpc := match.Match{Pattern: "spatial", I: i, J: j - 1, Token: password[i:j], DictionaryName: graph.Name}
+ matchSpc.Entropy = entropy.SpatialEntropy(matchSpc, turns, shiftedCount)
+ matches = append(matches, matchSpc)
+ }
+ //. . . and then start a new search from the rest of the password
+ i = j
+ break
+ }
+ }
+
+ }
+ return matches
+}