views:

392

answers:

7

I'm using a RegExp to validate some user input on an ASP.NET web page. It's meant to enforce the construction of a password (i.e. between 8 and 20 long, at least one upper case character, at least one lower case character, at least one number, at least one of the characters #@!$% and no use of letters L or O (upper or lower) or numbers 0 and 1. This RegExp works fine in my tester (Expresso) and in my C# code.

This is how it looks:

(?-i)^(?=.{8,20})(?=.*[2-9])(?=.*[a-hj-km-np-z])(?=.*[A-HJ-KM-NP-Z])
(?=.*[#@!$%])[2-9a-hj-km-np-zA-HJ-KM-NP-Z#@!$%]*$

(Line break added for formatting)

However, when I run the code it lives in in IE6 or IE7 (haven't tried other browsers as this is an internal app and we're a Microsoft shop), I get a runtime error saying 'Syntax error in regular expression'. That's it - no further information in the error message aside from the line number.

What is it about this that JavaScript doesn't like?

A: 

Are you enclosing the regexp in / / characters?

var regexp = /[]/;
return regexp.test();
jthompson
Hi,I'm not writing the Javascript myself, I use Visual Studio 2008 and it generates the Javascript from the validation controls I'm using.Are these characters telling the Javascript to ignore potential escape characters in the string?
HowardND
The / characters indicate the start and end of a regular expression. The [] inside was to indicate that's where your regex would go.
jthompson
+2  A: 

Well, there are two ways of defining a Regex in Javascript:

a. Through a Regexp object constructor:

var re = new RegExp("pattern","flags");
re.test(myTestString);

b. Using a string literal:

var re = /pattern/flags;

You should also note that JS does not support some of the tenets of Regular Expressions. For a non-comprehensive list of features unsupported in JS, check out the regular-expressions.info site.

Specifically speaking, you appear to be setting some flags on the expression (for example, the case insensitive flag). I would suggest that you use the /i flag (as indicated by the syntax above) instead of using (?-i)

That would make your Regex as follows (Positive Lookahead appears to be supported):

/^(?=.{8,20})(?=.*[2-9])(?=.*[a-hj-km-np-z])(?=.*[A-HJ-KM-NP-Z])(?=.*[#@!$%])[2-9a-hj-km-np-zA-HJ-KM-NP-Z#@!$%]*$/i;

For a very good article on the subject, check out Regular Expressions in JavaScript.

Edit (after Howard's comment)


If you are simply assigning this Regex pattern to a RegularExpressionValidator control, then you will not have the ability to set Regex options (such as ignore case). Also, you will not be able to use the Regex literal syntax supported by Javascript. Therefore, the only option that remains is to make your pattern intrinsically case insensitive. For example, [a-h] would have to be written as [A-Ha-h]. This would make your Regex quite long-winded, I'm sorry to say.

Here is a solution to this problem, though I cannot vouch for it's legitimacy. Some other options that come to mind may be to turn of Client side validation altogether and validate exclusively on the Server. This will give you access to the full Regex flavour implemented by the System.Text.RegularExpressions.Regex object. Alternatively, use a CustomValidator and create your own JS function which applies the Regex match using the patterns that I (and others) have suggested.

Cerebrus
Thanks for this :) I'm not writing the Javascript myself, I use VS2008 which generates Javascript from the validation controls I use. All I do is tell it the RegExp to use. I will try the /i suggestion. Do the // characters tell the Javascript to ignore potential escape characters in the string?
HowardND
Edited my post to include info about RegexValidator. Hope this helps.
Cerebrus
Thanks for the help - very constructive and very useful :)
HowardND
My pleasure! Thanks for the acceptance. :-)
Cerebrus
+1  A: 

I'm not familiar with C#'s regular expression syntax, but is this (at the start)

(?-i)

meant to turn the case insensitivity pattern modifier on? If so, that's your problem. Javascript doesn't support specifying the pattern modifiers in the expression. There's two ways to do this in javascript

var re = /pattern/i
var re = new RegExp('pattern','i');

Give one of those a try, and your expression should be happy.

Alan Storm
+1  A: 

As Cerberus mentions, (?-i) is not supported in JavaScript regexps. So, you need to get rid of that and use /i. Something to keep in mind is that there is no standard for regular expression syntax; it is different in each language, so testing in something that uses the .NET regular expression engine is not a valid test of how it will work in JavaScript. Instead, try and look for a reference on JavaScript regular expressions, such as this one.

Your match that looks for 8-20 characters is also invalid. This will ensure that there are at least 8 characters, but it does not limit the string to 20, since the character class with the kleene-closure (* operator) at the end can match as many characters as provided. What you want instead is to replace the * at the end with the {8,20}, and eliminate it from the beginning.

var re = /^(?=.*[2-9])(?=.*[a-hj-km-np-z])(?=.*[A-HJ-KM-NP-Z])(?=.*[#@!$%])[2-9a-hj-km-np-zA-HJ-KM-NP-Z#@!$%]{8,20}$/i;

On the other hand, I'm not really sure why you would want to restrict the length of passwords, unless there's a hard database limit (which there shouldn't be, since you shouldn't be storing passwords in plain text in the database, but instead hashing them down to something fixed size using a secure hash algorithm with a salt). And as mentioned, I don't see a reason to be so restrictive on the set of characters you allow. I'd recommend something more like this:

var re = /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[#@!$%])[a-zA-Z0-9#@!$%]{8,}$/i;

Also, why would you forbid 1, 0, L and O from your passwords (and it looks like you're trying to forbid I as well, which you forgot to mention)? This will make it very hard for people to construct good passwords, and since you never see a password as you type it, there's no reason to worry about letters which look confusingly similar. If you want to have a more permissive regexp:

var re = /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[#@!$%]).{8,}$/i;
Brian Campbell
Paragraph 4 was useful - thanks. The rest wasn't relevant to my question - keep to the point and waste a little less electricity...
HowardND
Sorry, when I was testing the regexp that I was trying to give as an answer, I kept on accidentally using the letters I, L, and O, which meant that it didn't pass for reasons other than what I was looking for at the moment.
Brian Campbell
A: 

(?-i)

Doesn't exist in JS Regexp. Flags can be specified as “new RegExp('pattern', 'i')”, or literal syntax “/pattern/i”.

(?=

Exists in modern implementations of JS Regexp, but is dangerously buggy in IE. Lookahead assertions should be avoided in JS for this reason.

between 8 and 20 long, at least one upper case character, at least one lower case character, at least one number, at least one of the characters #@!$% and no use of letters L or O (upper or lower) or numbers 0 and 1.

Do you have to do this in RegExp, and do you have to put all the conditions in one RegExp? Because those are easy conditions to match using multiple RegExps, or even simple string matching:

if (
    s.length<8 || s.length>20 ||
    s==s.toLowerCase() || s==s.toUpperCase() ||
    s.indexOf('0')!=-1 || s.indexOf('1')!=-1 ||
    s.toLowerCase().indexOf('l')!=-1 || s.toLowerCase().indexOf('o')!=-1 ||
    (s.indexOf('#')==-1 && s.indexOf('@')==-1 && s.indexOf('!')==-1 && s.indexOf('%')==-1 && s.indexOf('%')==-1)
)
    alert('Bad password!');

(These are really cruel and unhelpful password rules if meant for end-users BTW!)

bobince
A: 

I would use this regular expression:

/(?=[^2-9]*[2-9])(?=[^a-hj-km-np-z]*[a-hj-km-np-z])(?=[^A-HJ-KM-NP-Z]*[A-HJ-KM-NP-Z])(?=[^#@!$%]*[#@!$%])^[2-9a-hj-km-np-zA-HJ-KM-NP-Z#@!$%]{8,}$/

The [^a-z]*[a-z] will make sure that the match is made as early as possible instead of expanding the .* and doing backtracking.

Gumbo
A: 

(?-i) is supposed to turn case-insensitivity off. Everybody seems to be assuming you're trying to turn it on, but that would be (?i). Anyway, you don't want it to be case-insensitive, since you need to ensure that there are both uppercase and lowercase letters. Since case-sensitive matching is the default, prefacing a regex with (?-i) is pointless even in those flavors (like .NET) that support inline modifiers.

Alan Moore