mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-15 08:41:16 -05:00
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package matching
|
|
|
|
import (
|
|
"github.com/dlclark/regexp2"
|
|
"github.com/trustelem/zxcvbn/match"
|
|
"github.com/trustelem/zxcvbn/scoring"
|
|
)
|
|
|
|
type repeatMatch struct{}
|
|
|
|
var greedy = regexp2.MustCompile(`(.+)\1+`, 0)
|
|
var lazy = regexp2.MustCompile(`(.+?)\1+`, 0)
|
|
var lazyAnchored = regexp2.MustCompile(`^(.+?)\1+$`, 0)
|
|
|
|
func runeToStringIndex(index int, password string) int {
|
|
runes := 0
|
|
for i := range password {
|
|
if runes == index {
|
|
return i
|
|
}
|
|
runes++
|
|
}
|
|
//shouldn't really get here
|
|
return len(password)
|
|
}
|
|
|
|
func (repeatMatch) Matches(password string) []*match.Match {
|
|
var matches []*match.Match
|
|
|
|
lastIndex := 0
|
|
for lastIndex < len(password) {
|
|
greedyMatch, err := greedy.FindStringMatchStartingAt(password, lastIndex)
|
|
if err != nil || greedyMatch == nil {
|
|
break
|
|
}
|
|
lazyMatch, _ := lazy.FindStringMatchStartingAt(password, lastIndex)
|
|
|
|
var rmatch *regexp2.Match
|
|
var baseToken string
|
|
if greedyMatch.Captures[0].Length > lazyMatch.Captures[0].Length {
|
|
// greedy beats lazy for 'aabaab'
|
|
// greedy: [aabaab, aab]
|
|
// lazy: [aa, a]
|
|
rmatch = greedyMatch
|
|
// greedy's repeated string might itself be repeated, eg.
|
|
// aabaab in aabaabaabaab.
|
|
// run an anchored lazy match on greedy's repeated string
|
|
// to find the shortest repeated string
|
|
if m, err := lazyAnchored.FindStringMatch(rmatch.Captures[0].String()); err == nil {
|
|
baseToken = m.GroupByNumber(1).String()
|
|
}
|
|
} else {
|
|
// lazy beats greedy for 'aaaaa'
|
|
// greedy: [aaaa, aa]
|
|
// lazy: [aaaaa, a]
|
|
rmatch = lazyMatch
|
|
baseToken = rmatch.GroupByNumber(1).String()
|
|
}
|
|
// FindStringMatchStartingAt takes an index into the string (basically an offset
|
|
// into a byte array). rmatch indices will be rune offsets and so need to be converted
|
|
// to string offsets
|
|
i := runeToStringIndex(rmatch.Index, password)
|
|
j := runeToStringIndex(rmatch.Index+rmatch.Captures[0].Length-1, password)
|
|
|
|
// recursively match and score the base string
|
|
baseAnalysis := scoring.MostGuessableMatchSequence(
|
|
baseToken,
|
|
Omnimatch(baseToken, nil),
|
|
false,
|
|
)
|
|
matches = append(matches, &match.Match{
|
|
Pattern: "repeat",
|
|
I: i,
|
|
J: j,
|
|
Token: rmatch.Captures[0].String(),
|
|
BaseToken: baseToken,
|
|
BaseGuesses: baseAnalysis.Guesses,
|
|
BaseMatches: baseAnalysis.Sequence,
|
|
RepeatCount: rmatch.Captures[0].Length / len(baseToken),
|
|
})
|
|
lastIndex = j + 1
|
|
|
|
}
|
|
return matches
|
|
}
|