0

I have a signup page for my website.

When the user fills in the form, they get real-time validation through a JavaScript Regex, which works fine. This is done through:

var password = document.getElementsByName("password")[0].value;
var pattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)([a-zA-Z0-9!\"\#$%&\'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]{6,})$/;
if(pattern.test(password)){
    document.getElementById("check_password").innerHTML = "Password is valid.";
} else {
    document.getElementById("check_password").innerHTML = "Password is invalid. It should have at least 6 characters, and 1 lowercase letter, uppercase letter, and number.";
}

The PHP Regex is used when the user submits the form through this:

if(!preg_match("/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)([a-zA-Z0-9!\"\#$%&\'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]{6,})$/", $_POST["password"])){
        $error = "Password is invalid. It should have at least 6 characters, and 1 lowercase letter, uppercase letter, and number.";
}

However, the PHP keeps throwing up the error even when the JavaScript doesn't. Both regexes are the same and have been tested here in the PHP setting and JavaScript setting. Yet it works in JavaScript but not PHP!

Why does it not work in PHP, and how can I solve the problem?

14
  • 4
    Share problematic input. Commented Sep 15, 2017 at 17:15
  • 1
    An example would be heHE12. It would match the regex as seen on regex101.com, yet it still does not match in PHP's preg_match() Commented Sep 15, 2017 at 17:19
  • 1
    @revo isn't that password supposed to be a good password Commented Sep 15, 2017 at 17:25
  • 1
    @revo Isn't that the reproduction of it? The code says it fails when it should pass! And removing the ! doesn't really make sense. Commented Sep 15, 2017 at 17:26
  • 1
    The problem is the backslashes, people. Commented Sep 15, 2017 at 17:29

2 Answers 2

5

The problem comes from the backslash. To figure a literal backslash in a php regex string, you need to use 4 backslashes (you need to escape once for the string and once for the regex since it's also a regex special character):

if(!preg_match("/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)([a-zA-Z0-9!\"\#$%&\'()*+,\-.\/:;<=>?@\[\\\\\]^_`{|}~]{6,})$/", $_POST["password"])){
    $error = "Password is invalid. It should have at least 6 characters, and 1 lowercase letter, uppercase letter, and number.";
}

Note that it is the same if you decide to define your pattern with the RegExp constructor in a Javascript string:

var re = new RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)([-a-zA-Z0-9!\"#$%&'()*+,./:;<=>?@\\[\\]\\\\^_`{|}~]{6,})$");

As an aside, and to avoid the problem, the character class [a-zA-Z0-9!\"\#$%&\'()*+,\-.\/:;<=>?@\[\\\\\]^_``{|}~] can be shorten to [!-~]. Also, you don't need a capture group:

if(!preg_match("/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[!-~]{6,}$/", $_POST["password"])){
    $error = "Password is invalid. It should have at least 6 characters, and 1 lowercase letter, uppercase letter, and number.";
}

To finish, there's a small difference with the anchor $ between Javascript and PHP. In the two languages it matches the end of the string (by default without the multiline mode), but in PHP it matches also the end of the line when this one is followed by the newline character \n just before the end of the string. In other words, the string "Password0\n" matches also your pattern because the $ anchor matches between 0 and \n (at the position of \n).

To avoid the problem, two possibilities:

  • you can use the D modifier (PCRE_DOLLAR_ENDONLY):
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[!-~]{6,}$/D
  • or change $ to \z (that matches always and only the end of the string whatever the mode):
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[!-~]{6,}\z/
Sign up to request clarification or add additional context in comments.

3 Comments

Yes! This is correct. Actually it should be 2 backslashes, as 4 would be pointless as I am trying to include the whole set and 4 would be a waste. Thanks a bunch!
Can you explain why it can be shortened to [!-~]
@ngmh: it's a simple character range that includes all of your characters, see the ascii table.
-1

You should check if preg_match returns:

Returns INT(0) if not found. Returns INT(1) if found. Returns BOOL(false) if an error occurred.

5 Comments

And I have to use preg_mach() == 0?
Exactly, you're checking if it returns false, which means if it got an error occurred.
This answer implicitly promotes a correct solution. Up-vote.
This isn't actually the problem.
This is just part of the problem, then.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.