Skip to content
This repository has been archived by the owner on Feb 10, 2021. It is now read-only.

Commit

Permalink
Use a sorting algorithm that's as close to upstream as possible
Browse files Browse the repository at this point in the history
It's impossible for us to match all browsers exactly (since the way that
elements that compare as equal as sorted is implementation-defined in
JavaScript), but this gets us pretty close.
  • Loading branch information
clamburger committed Jul 18, 2018
1 parent 5ab1ab6 commit f2f540b
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 9 deletions.
42 changes: 35 additions & 7 deletions src/Matcher.php
Expand Up @@ -31,20 +31,48 @@ public function getMatches($password, array $userInputs = [])
$matches = array_merge($matches, $matched);
}
}

self::usortStable($matches, [$this, 'compareMatches']);
return $matches;
}

/**
* @param Match $a
* @param Match $b
* A stable implementation of usort().
*
* Whether or not the sort() function in JavaScript is stable or not is implementation-defined.
* This means it's impossible for us to match all browsers exactly, but since most browsers implement sort() using
* a stable sorting algorithm, we'll get the highest rate of accuracy by using a stable sort in our code as well.
*
* This function taken from https://github.com/vanderlee/PHP-stable-sort-functions
* Copyright © 2015-2018 Martijn van der Lee (http://martijn.vanderlee.com). MIT License applies.
*
* @param array $array
* @param callable $value_compare_func
* @return bool
*/
public static function sortMatches($a, $b)
public static function usortStable(array &$array, $value_compare_func)
{
$index = 0;
foreach ($array as &$item) {
$item = array($index++, $item);
}
$result = usort($array, function ($a, $b) use ($value_compare_func) {
$result = call_user_func($value_compare_func, $a[1], $b[1]);
return $result == 0 ? $a[0] - $b[0] : $result;
});
foreach ($array as &$item) {
$item = $item[1];
}
return $result;
}

public static function compareMatches(Match $a, Match $b)
{
if ($a->begin != $b->begin) {
return $a->begin - $b->begin;
} else {
return $a->end - $b->end;
$beginDiff = $a->begin - $b->begin;
if ($beginDiff) {
return $beginDiff;
}
return $a->end - $b->end;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/Matchers/DateMatch.php
Expand Up @@ -2,6 +2,8 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Matcher;

class DateMatch extends Match
{

Expand Down Expand Up @@ -100,6 +102,7 @@ public static function match($password, array $userInputs = [])
foreach ($dates as $date) {
$matches[] = new static($password, $date['begin'], $date['end'], $date['token'], $date);
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}

Expand Down
3 changes: 3 additions & 0 deletions src/Matchers/DictionaryMatch.php
Expand Up @@ -2,6 +2,8 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Matcher;

class DictionaryMatch extends Match
{

Expand Down Expand Up @@ -58,6 +60,7 @@ public static function match($password, array $userInputs = [], $rankedDictionar
$matches[] = new static($password, $result['begin'], $result['end'], $result['token'], $result);
}
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}

Expand Down
3 changes: 3 additions & 0 deletions src/Matchers/L33tMatch.php
Expand Up @@ -2,6 +2,8 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Matcher;

/**
* Class L33tMatch extends DictionaryMatch to translate l33t into dictionary words for matching.
* @package ZxcvbnPhp\Matchers
Expand Down Expand Up @@ -73,6 +75,7 @@ public static function match($password, array $userInputs = [], $rankedDictionar
}
}

Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}

Expand Down
5 changes: 3 additions & 2 deletions src/Matchers/ReverseDictionaryMatch.php
Expand Up @@ -2,6 +2,8 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Matcher;

class ReverseDictionaryMatch extends DictionaryMatch
{
/** @var bool Whether or not the matched word was reversed in the token. */
Expand All @@ -28,8 +30,7 @@ public static function match($password, array $userInputs = [], $rankedDictionar
$match->begin = mb_strlen($password) - 1 - $match->end;
$match->end = mb_strlen($password) - 1 - $tempBegin;
}

usort($matches, ['ZxcvbnPhp\Matcher', 'sortMatches']);
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}

Expand Down
3 changes: 3 additions & 0 deletions src/Matchers/SpatialMatch.php
Expand Up @@ -2,6 +2,8 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Matcher;

class SpatialMatch extends Match
{
const SHIFTED_CHARACTERS = '~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?';
Expand Down Expand Up @@ -45,6 +47,7 @@ public static function match($password, array $userInputs = [], array $graphs =
$matches[] = new static($password, $result['begin'], $result['end'], $result['token'], $result);
}
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}

Expand Down
3 changes: 3 additions & 0 deletions src/Matchers/YearMatch.php
Expand Up @@ -2,6 +2,8 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Matcher;

class YearMatch extends Match
{

Expand All @@ -24,6 +26,7 @@ public static function match($password, array $userInputs = [])
foreach ($groups as $captures) {
$matches[] = new static($password, $captures[1]['begin'], $captures[1]['end'], $captures[1]['token']);
}
Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
return $matches;
}

Expand Down

0 comments on commit f2f540b

Please sign in to comment.