Zend_Filter, fleshing out isEmail and isUri
So, I’m working on my new personal website when I try to run a couple filter checks against input values, two of the: email and website URL. For some ugly reason, these two just never seem to give me a “false” return. Time to find out why….
Digging through the code, I get to Zend_Filter::isEmail and low and behond it is empty! Well, that’ll explain that. What surprises me the most about this is that the fact Zend_Filter_Input::testEmail is actually used as an example. One would think example code would be completed. I guess not.
For those of you using the Zend Framework, here’s what I’ve done to complete the filter checks for Zend_Filter::isUri and Zend_Filter::isEmail.
Within Zend_Filter you’ll find a method isEmail. This is the guts of the filter checks. Email addresses are very complicated. I decided not to get that convoluted and rely on old regex checks. I also wanted to add a new method; insuring said email address domain has a valid MX record. Not to hard through PHP’s magic abilities.
First step was to split up the email address and then insure it split correctly:
$parts = explode(”@”, $value);
if ( count($parts) != 2 ) {
return FALSE;
}
Once that one done, I used the internal isHostname() method to insure the domain is a valid domain. Then I used a basic regex to check the username portion:
try {
if ( ! Zend_Filter::isHostname($parts[1], self::HOST_ALLOW_DNS) ) {
return FALSE;
}
} catch ( Zend_Filter_Exception $e ) {
throw $e;
}if ( ! preg_match(’/[a-zA-Z]([.]?([[:alnum:]_-]+)*)?/’, $parts[0])) {
return FALSE;
}
The try catch is to catch the isHostname exceptions as it does have exceptions. The throw back is to allow me to catch the “error message” returned.
Once that’s done checking, it is time to insure this domain has a valid MX record:
if ( ! checkdnsrr($parts[1], ‘MX’ )) {
throw new Zend_Filter_Exception(’No valid MX record for ‘ . $parts[1]);
return FALSE;
}
I use a throw here as well so I can explicitly display their domain has no valid mail record to the user.
Naturally, the throw would make a forced return, but good to be sure. After all that’s said and done, a single return TRUE is used to signal all tests were successful:
return TRUE;
This completes my flesh out for the isEmail check. Now I can successfully call it and catch different error messages for the user to see:
$filter = new Zend_Filter_Input($data);
try {
if ( $filter->getRaw(’email’) == “” ) {
$tmp['err']['email'] = “How am I to reply?”;
} elseif ( ! $tmp['email'] = $filter->testEmail(’email’) ) {
$tmp['err']['email'] = “Ha, nice try! That’s obviously not a real email address”;
}
} catch ( Zend_Filter_Exception $e ) {
$tmp['err']['email'] = $e->getMessage();
}
My isUri setup is quite similar, except I did a small hack that I don’t recommend unless you know what you’re doing. I used parse_url() PHP method to parse the given website address so that I can access the domain part itself. I don’t care about the path, I just want to insure the domain is valid. However, because users aren’t always smart, I had to tag on http:// in instances where it is not already present. The best way I could do this is modify the original value before setting up my Zend_Filter_Input() class.
// Tack on http:// if not specified.
if (!empty($_POST['website']) &&
! preg_match(’/^https?:\/\//’, $_POST['website'])) {
$_POST['website'] = ‘http://’.$_POST['website'];
}
I’m not too happy about this hack, but it works. Now for the full isUri method:
if ( ! $host = parse_url($value, PHP_URL_HOST) ) {
return FALSE;
}try {
if ( ! Zend_Filter::isHostname($host, self::HOST_ALLOW_DNS ) ) {
return FALSE;
}
} catch ( Zend_Filter_Exception $e ) {
throw $e;
}if ( ! checkdnsrr($host, ‘A’) && ! checkdnsrr($host, ‘CNAME’) ) {
throw new Zend_Filter_Exception(’Domain has no valid DNS record’);
}return TRUE;
As you can see, with the exception of splitting of the email/uri, everything else is pretty much the same. Given this is a URL, instead of checking for an MX record, we check for A or CNAME records. If it does not have either of them, this isn’t a valid domain.
The executing code is almost identical, but website is optional, making the empty check used just to insure if it’s alright to run through isUri:
try {
if ( $filter->getRaw(’website’) != “” &&
! $tmp['website'] = $filter->testUri(’website’) ) {
$tmp['err']['website'] = “At least put in a bloody VALID optional value”;
}
} catch ( Zend_Filter_Exception $e ) {
$tmp['err']['website'] = $e->getMessage();
}
It all works and hopefully these two methods will get fleshed out in the next release (or sometime soon). Who knows, maybe if I clean up a few things, I’ll submit what I’ve done here. What worries me the most is the email username check. I know that the username of an email can contain a vast number of characters and options, I hardly think that petty regex will work in all situations. Unicode comes to mind…