Invoer validatie
Invoer Valideren
Je kunt in een formulier nog zo goed aangeven wat de gebruiker in moet voeren: je weet nooit zeker of het dat al dan niet bewust fout of niet gedaan wordt.
Daarom dien je altijd te controleren of de gebruikerinvoer er wel zo uit ziet als dat je dat in je script bedoeld had. Dit artikel beoogt om daartoe wat handgrepen aan te reiken:
a) controle of het soort invoer wel is wat je bedoelt: getal / string etc.
b) een aantal (regular expressions) om te controleren of het formaat deugt.
Hoe verder?
Ik spring nu even over de functies als ctype_digit() heen om direct wat regular expressions te dumpen.
Voel je vrij om de uitleg daarvan aan te vullen. Idem voor het toevoegen of aanpassen van de regular expressions.
Type controle
Bijvoorbeeld met ctype_digit en aanverwante functies
to do....
Regular expressions
Sterk wachtwoord
Als je de lengte wilt verhogen verander dan bij "\S{6,}" de 6 met wat jij wilt. Aangeraden wordt minimaal 6 of 8 te gebruiken.
De code eist
- minimaal een hoofdletter
- minimaal een kleine letter
- minimaal een cijfer
- minimaal een ander teken dan een letter of cijfer.
Er zijn geen beperkingen ten aanzien van welk ander teken dat dan zou zijn. Een spatie of single quote kan dus ook. Dat zou afhankelijk van je toepassing tot problemen kunnen leiden. Dat zou niet het geval zijn als een md5-hash Md5 opgeslagen zou worden in de database.
/** * Look if the password is strong. * Must be at least six characters and contains an combination of alpha digits and nummbers and at least 1 other character. * * @author Sebastiaan Stok <s.stok@rollerscapes.net> * @author Richard JeXuS.net (Regex optimization) * * @param string $psPassword * @param array $paCommonWords An array of words to disallow, like username of birthday * @return bool */ function strongPassword($psPassword, array $paCommonWords = null) { if ($paCommonWords !== null) { $psCommonWords = implode('|', array_map('preg_quote', $paCommonWords)); } /* * Strong password with the following requirements. * - At least 6 characters long. * - At least 1 uppercase, AND at least 1 lowercase * - At least 1 digit OR at least 1 alphanumeric. * - No spaces */ if (!preg_match('~^(?=[^a-z]*[a-z])(?=[^A-Z]*[A-Z])(?=\D*\d)(?=[A-Za-z0-9]*[^A-Za-z0-9])\S{6,}$~', $psPassword)) { return false; } // Look for common words, like for example is same as the username elseif ($paCommonWords !== null && preg_match('{' . $psCommonWords . '}i', $psPassword)) { return false; } return true; }
Is het een Nederlandse Postcode
begint met een cijfer groter dan 0 gevolgd door 3 cijfers. optioneel volgen er 1 of meer spaties. Er wordt geëindigd met een tweetal letters (hoofd- of kleine)
$bAntwoord = preg_match('#^[1-9][0-9]{3}\h*[A-Z]{2}$#i', $sInvoer);
- voor oudere php-versies (< 5.2.4) kun je de \h ook vervangen door [ ]
- Wil je precies 1 spatie, dan vervalt het sterretje na de \h of [ ]
- zijn hoofdletters verplicht, laat dan de i weg na het #
- denk ook na, of je doelgroep 100% zeker altijd uit Nederland komt, om te voorkomen dat mensen met een Belgische of Duitse postcode hun adres niet in kunnen voeren
- Niet alle lettercobminaties zijn toegestaan, bijvoorbeeld 'SA', 'SD' en 'SS'. Deze check negeert deze eis. Tot 2005 waren ook de letters F, I, O, Q, U en Y niet ingebruik. Bij gebrek aan combinaties zijn die tegenwoordig wel in gebruik.
Is het een datum in het formaat dd-mm-yyyy?
Het eerste cijfer van de dag is 0,1,2 of 3 Het eerste cijfer van de maand is 0 of 1 Aan het jaartal kun je ook nog eisen stellen, zie onder
$bAntwoord = preg_match('^[0-3][0-9]-[0-1][0-9]-[0-9]{4}$', $sInvoer);
Merk op dat 39-19-2008 dus ook voldoet! Een controle slag met checkdate() kan dat opvangen. Zodra je weet dat het formaat inderdaad xx-xx-xxxx is, kun je splitsen op de streepjes of op de lengte en de losse onderdelen van je datum (?) aan checkdate() voeren om te horen of dat inderdaad een bestaande datum is en niet 31 feb 2001.
$bAntwoord = preg_match('^([0-3][0-9])-([0-1][0-9])-([0-9]{4})$', $sInvoer, $aM) && checkdate($aM[2], $aM[1], $aM[3]);
Varianten:
- voorloopnul is optioneel (1-8-2008)
$bAntwoord = preg_match('#^[0-3]?[0-9]-[0-1]?[0-9]-[0-9]{4}$#', $sInvoer);
- jaartal begint verplicht met 19 of 20
$bAntwoord = preg_match('#^[0-3]?[0-9]-[0-1]?[0-9]-(19|20)[0-9]{2}$#', $sInvoer);
Is de tijd geldig?
Een tijdcontrole is eigenlijk makkelijker dan het misschien lijkt. Een tijd bestaat uit een getal tussen 0 en 23 (al dan niet met voorloopnul), gevolgd door een dubbele punt, waarna de minuten worden weergegeven als een 2 cijferig getal tussen 00 en 59. Onderstaande controle accepteert alle 24-uurstijden.
$bAntwoord = preg_match('#^(?:[01]?\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$#', $sInvoer);
- ^ vanaf het begin van de string
- [01]? er mág een 0 of 1 staan
- \d na die 0 of 1 volgt een cijfer tussen 0 en 9
- | in plaats van de vorige twee regels mag ook:
- 2[0-3] het cijfer 2 gevolgd door een 0, 1, 2, of 3 (feitelijk dus de getallen 20 t/m 23
- : dan een dubbele punt
- [0-5] een cijfer tussen 0 en 5
- \d gevolgd door een cijfer tussen 0 en 9
- $ geeft het einde van de string aan.
Voor AM-/PM-tijden kun je het volgende gebruiken:
$bAntwoord = preg_match('#^(?:1[0-2]|0?[1-9]):[0-5]\d(?::[0-5]\d)?\h*[aApP]\.?[mM]\.?$#', $sInvoer);
- ^ vanaf het begin van de string
- 1 er moet een 1 staan
- na die 1 volgt een cijfer tussen 0 en 2
- | in plaats van de vorige twee regels mag ook:
- 0? optioneel een 0
- dan een cijfer tussen 1 en 9
- : dan een dubbele punt
- [0-5] een cijfer tussen 0 en 5
- \d gevolgd door een cijfer tussen 0 en 9
- \h daarna een spatie of tab
- [aApP] een 'a' of 'p'
- \.?m\.? een optionele '.', dan een 'm' of 'M' en nogmaals een optionele '.'
- $ geeft het einde van de string aan.
Gecombineerde controle.
Om de invoer zo toegankelijk mogelijk te houden is het verstandig beide formaten te accepteren en deze dan zelf om te zetten naar een geldig formaat. strtotime() kan hier goed voor worden gebruikt.
- Europees formaat of AM/PM
- Tijdzones zijn toegestaan: Militair (A tot Z, met uitzonder van J) of in +0100 formaat.
$bAntwoord = preg_match('#^((?:(?:[01]?\d|2[0-3]):[0-5]\d(?::[0-5]\d)?(?:[+-](?:0[0-9]|1[012])(?::?[03]\d)?|[A-IK-Z])?(?:\.\d+(?:-\d+)?)?)|(?:(?:1[0-2]|0?[1-9]):[0-5]\d(?::[0-5]\d)?\h*[aApP]\.?[mM]\.?))$#', $sInvoer);
Meer informatie over tijd en datum formaat.
- ISO_8601 datum
- Datetime W3c
- Timezone Wikipedia
- List of military time zones
- List of time zone abbreviations
Is het een geldig email adres?
Hoewel op een lokale server ook adressen als "admin" of "pietje" -wat kort is voor admin@localhost of pietje@localhost- gebruikt kunnen worden, voldoen publieke adressen aan een algemeen formaat. Zo begint het met een letter of cijfer, zit er een @ in, is er een domeinnaam, gevolgd door een punt en een top level-domain. Daar tussen kunnen ook nog verschillende levels zitten. (x@phpfreakz.co.uk of y@wiki.phpfreakz.nl). De maximale lengte van het stuk voor @ is 64 karakters.
$bAntwoord = preg_match('#^[a-z0-9][a-z0-9_.-]{0,63}@([a-z0-9]+\.)*[a-z0-9][a-z0-9\-]+\.([a-z]{2,6})$#i', $sInvoer);
beperkingen:
- domeinnamen met letters met accenten worden niet geaccepteerd: köln.de is een geldige domeinnaam, die niet door deze check heen komt.
- RFC specificeert ook de karakters ! # $ % & ' * + / = ? ^ ` { | } ~ als toegestaan. Deze tekens worden echter zelden gebruikt - mede omdat vele validators ze niet accepteren -. Daarnaast is het mogelijk om niet-toegestane tekens in het stuk voor de @ te plaatsen, indien dat stuk tussen "" staat. "php freakz"@phpfreakz.nl is dus valide, ondanks de spatie voor het @-teken.
- de controle van het stuk voor @ is niet compleet: zo mag dat stuk niet eindigen met een punt of koppelteken en mogen er ook niet meerdere punten achter elkaar staan. (fout..mail..adres.@domein.com)
Let er ook op dat momenteel een toplevel domain tussen 2 en 6 letters kan bevatten (.nl, .com, .name en .museum zijn voorbeelden.).
Oudere scripts controleren vaak slechts op {2,3} of als je geluk hebt {2,4} omdat de langere varianten op dat moment nog niet ingevoerd waren. Mocht er ooit nog een langer TLD mogelijk worden, dan dient deze check dus aangepast te worden.
Je kunt ook de redenatie volgen dat jan@department.office.com vergeet om .com toe te voegen. @department.office is een geldig adres, aangezien office uit 6 letters bestaat. Dat zou je kunnen oplossen door alle mogelijke topleveldomains van meer dan 2 tekens expliciet te noemen:
(?:[A-Z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)
maar aangezien die lijst af en toe bijgewerkt wordt, is hij nooit uitputtend. Zo bestaat tegenwoordig ook het TLD .pro en .tel, wat ten tijde van het kopieren van bovenstaan lijstje nog niet zo was. Bovendien verdwijnen er -zelden- ook TLD's. .NATO is bijvoorbeeld opgeheven, omdat de nato de voorkeur gaf aan .nato.int.
Vraag je dus terdege af, of je dwingend wilt zijn in je controle, of dat je het bij een waarschuwing wilt laten, dat het adres mogelijk niet juist is. Uiteindelijk weet de gebruiker zelf het best hoe zijn mailadres luidt.
Tekens die in elk geval zeker niet in het adres thuis horen, zijn puntkomma's en enters. Die zouden tot misbruik van een mailform kunnen leiden!
NB: Vanaf PHP 5.2.0 beschikt PHP over enkele filterfuncties. Hiermee zou je efficiënter bovenstaande controle kunnen doen:
$bAntwoord = filter_var($sInvoer, FILTER_VALIDATE_EMAIL);
ware het niet dat die functie zo geschreven is, dat wiki@localhost ook geaccepteerd wordt als adres. Dat is ook een geldig adres, maar doorgaans wens je een controle of het een publiek bereikbare domeinnaam is. Grofweg doet de filterfunctie dus hier niet veel meer dan controleren of er een @ in de string staat en er letters en cijfers in de string zitten. Zelfs de controle of het local part van het adres (het stuk voor @) niet langer is dan 64 tekens ontbreekt.
Met onderstaande code controleer je of er een geldig toplevel-domain in het e-mailadres voorkomt. Deze kan uitgebreider (een volledige lijst is hier te vinden) maar dit zal voor de meeste gevallen prima voldoen. Het is dan ook nog goed mogelijk om te kijken of het e-mailadres bestaat, dan heb je zo'n lijst niet nodig.
$bAntwoord = filter_var($sInvoer, FILTER_VALIDATE_EMAIL) && preg_match('#\.[a-z]{2,6}$#i', $sInvoer);
TODO: voorbeeld van "echte" e-mailcontrole
Is het een geldig sofinummer (burger service nummer)?
Dit wordt gecontroleerd middels de "11 proef": een bepaalde berekening van de losse cijfers uit dit nummer is altijd deelbaar door 11.
/** * Checks whether or not the sofinummer provided passes the test of 11. * @version 2.0 * @param $sofinummer The sofinummer to be checked. * @return boolean * @author Ivo Peters * @author Berry Langerak, al had Ivo hem als eerste goed :) **/ function isSofinummer( $sofinummer ) { $snummer = trim( $sofinummer ); // lijst met nummers die qua check kloppen, maar toch niet geldig zijn $aInvalid = array( '111111110', '999999990', '000000000' ); if( strlen( $snummer ) != 9 || !ctype_digit( $snummer ) || in_array( $snummer, $aInvalid ) ) { return false; } for( $i = 9, $som = -$snummer % 10; $i > 1; $i-- ) { $som += $i * $snummer{( 9 - $i )}; } return ( $som % 11 == 0 ); }
Bij een sofinummer (Burger Service Nummer) is het de bedoeling dat het laatste cijfer uit de reeks wordt vermenigvuldigd met -1, in plaats van 1. Daarmee wijkt deze elf-proef dus af van bijvoorbeeld de elf-proef voor bankrekeningnummers.
Merk op dat een sofinummer met een 0 kan beginnen. Geef de parameter van isSofinummer() dus door als string, aangezien anders de 0 weg kan vallen en je met 8 in plaats van 9 tekens zit.
Is het een geldig bankrekeningnummer? (11 proef)
Voor andere cijferreeksen waar de 11 proef van toepassing is, bijvoorbeeld Nederlandse bankrekeningnummers, kun je de volgende gebruiken:
/** * Checks whether or not the accountnumber provided passes the test of 11. * @version 2.0 * @param $sofinummer The accountnumber to be checked. * @return boolean * @author Ivo Peters * @author Berry Langerak **/ function isValidAccount( $account ) { $anummer = trim( $account ); $invalid = array( '111111110', '999999990', '000000000', '123456789' ); if( strlen( $anummer ) != 9 || !ctype_digit( $anummer ) || in_array( $anummer, $invalid ) ) { return false; } for( $i = 9, $som = 0; $i > 0; $i-- ) { $som += $i * $snummer{( 9 - $i )}; } return ( $som % 11 == 0 ); }
Overigens lijkt het er op, dat rekeningnummers in principe uit 10 posities kunnen bestaan, maar het eerste cijfer vaak (altijd?) 0 is. In dat geval gaat de berekening nog steeds op dezelfde wijze, alleen moet dan elke 9 in bovenstaande functie vervangen worden door 10. (bron: Clieop03 documentatie van Interpay)
Geldig BTW/VAT nummer
Een BTW nummer bestaat uit een land-code gevolgd door een bepaalde reeks nummers.
Een BTW-nummer van een eenmanszaak is in Nederland gelijk aan het BSN (tip: 11 proef) van de eigenaar, voorafgegaan door NL en gevolgd door B01 (of een hoger getal). Het BTW-nummer van een andere bedrijfsvorm dan eenmanszaak (BV, vereniging etc) voldoet aan het zelfde formaat. NB: Onderstaande function controleert niet de 11-proef van een Nederlands btw-nummer
Een geldig btw nummer is: NL 1535.50.909.B02
/** * Look if the given input could be a legal VAT-number. * Accepts input with or without '.' between the numbers, must contain a County-code * * Note: This only checks the syntax, NOT IF THE VAT REALLY EXITS OR IS ACTIVE! * * @author Sebastiaan Stok <s.stok@rollerscapes.net> * * @param string $psVatInput * @return bool */ function isVAT($psVatInput) { $psVatInput = trim($psVatInput); $psVatInput = str_replace('.', '', $psVatInput); $aVatMatch = array(); // Common syntax if (!preg_match('/^([a-z]{2})[ ]*(.+)$/is', $psVatInput, $aVatMatch)) { return false; } $aVatMatch[1] = strtoupper($aVatMatch[1]); $aVatRegexes = array( 'AT' => 'U[0-9]{8}', 'BE' => '0[0-9]{9}', 'BG' => '[0-9]{9,10}', 'CY' => '[0-9]{8}[A-Za-z]', 'CZ' => '[0-9]{8,10}', 'DE' => '[0-9]{9}', 'DK' => '[0-9]{2} ?[0-9]{2} ?[0-9]{2} ?[0-9]{2}', 'EE' => '[0-9]{9}', 'EL' => '[0-9]{9}', 'ES' => '([A-Za-z0-9][0-9]{7}[A-Za-z0-9])', 'FI' => '[0-9]{8}', 'FR' => '[A-Za-z0-9]{2} ?[0-9]{9}', 'GB' => '([0-9]{3} ?[0-9]{4} ?[0-9]{2}|[0-9]{3} ?[0-9]{4} ?[0-9]{2} ?[0-9]{3}|GD[0-9]{3}|HA[0-9]{3})', 'HU' => '[0-8]{8}', 'IE' => '[0-9][A-Za-z0-9+*][0-9]{5}[A-Za-z]', 'IT' => '[0-9]{11}', 'LT' => '([0-9]{9}|[0-9]{12})', 'LU' => '[0-9]{8}', 'LV' => '[0-9]{11}', 'MT' => '[0-9]{8}', 'NL' => '[0-9]{9}B[0-9]{2}', 'PL' => '[0-9]{10}', 'PT' => '[0-9]{9}', 'RO' => '[0-9]{2,10}', 'SE' => '[0-9]{12}', 'SI' => '[0-9]{8}', 'SK' => '[0-9]{10}', ); if (!isset($aVatRegexes[$aVatMatch[1]]) || !preg_match('/^([a-z]{2})[ ]*'.$aVatRegexes[$aVatMatch[1]].'$/is', $psVatInput)) { return false; } return true; }
Geldig BTW/VAT nummer (specifieke controles)
Bovenstaande functie controleert slechts de opbouw van een BTWnummer, maar niet of het nummer nog aan aanvullende eisen voldoet.
Voor het Nederlandse BTWnummer geldt, dat het nummer tussen "NL" en "B" moet voldoen aan dezelfde 11-proef als een Burger Service Nummer (zie boven).
Voor Belgische nummers zou moeten gelden dat de eerst 7 cijfers gedeeld moeten worden door 97. Het "restgetal" afgetrokken van 97 moet gelijk zijn aan de laatste 2 cijfers.
/** * Check if the number might be a belgian VAT-number. * Accepts input with or without '.' and spaces between the numbers, * spaces at begin and end of string are ignored * it must contain a County-code (BE) at the start (case insensitive) * after the country code 9 or 10 digits follow. * if 10 digits are given, the first must be zero * * Note: This only checks the syntax, NOT IF THE VAT REALLY EXITS OR IS ACTIVE! * * @author Ivo Peters * * @param string $psVatInput * @return bool */ function isBelgianVat($psVatInput) { // remove spaces and dots $sVatnr = str_replace(array(' ', '.'),'',($psVatInput)); if(preg_match('#^BE0?[0-9]{9}$#i', $sVatnr)) { $iVatnr = str_ireplace('BE','', $sVatnr); $bResult = 97 - (floor($iVatnr / 100) % 97) == $iVatnr % 100; } else { $bResult = false; } return $bResult; }
Voor nummers van Groot-Britannië geldt een mengvorm van de Nederlandse en Belgische check:
de eerste 7 cijfers worden met 8, 7, ... en 2 vermenigvuldigd en het resultaat wordt opgeteld. 97 - modulus van die som en 97 is weer gelijk aan het getal gevormd door de laatste 2 cijfers:
/** * Check if the number might be a GB VAT-number. * Accepts input with or without '.' and spaces between the numbers, * spaces at begin and end of string are ignored * it must contain a County-code (GB) at the start (case insensitive) * after the country code 9 digits follow. * * Note: This only checks the syntax, NOT IF THE VAT REALLY EXITS OR IS ACTIVE! * * @author Ivo Peters * * @param string $psVatInput * @return bool */ function isGBVat($psVatInput) { // remove spaces and dots $sVatnr = str_replace(array(' ', '.'),'',($psVatInput)); if(preg_match('#^GB[0-9]{9}$#i', $sVatnr)) { $sVatnr = str_ireplace('GB','', $sVatnr); $iSum = 0; for($i=0;$i<7;$i++) { $iSum += substr($sVatnr,$i,1) * (8 - $i); echo substr($sVatnr,$i,1) .' * '. (8 - $i) .' = '. substr($sVatnr,$i,1) * (8 - $i); echo PHP_EOL; } $bResult = 97 - $iSum % 97 == $sVatnr % 100; } else { $bResult = false; } return $bResult; }
NB: mogelijk zijn er nog varianten mogelijk op de combinatie GB+9cijfers
Is het een IP-adres?
Een IP adres (IPv4) bestaat uit 4 delen gescheiden door een punt.
Elk deel bestaat uit een getal tussen 0 en 255, waarbij voorloop nullen toegestaan zijn.
IPv4
$bAntwoord = preg_match('#^(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$#', $ip);
IPv6
A Regular Expression for IPv6 Addresses
RFC 4291 - IP Version 6 Addressing Architecture
$sIPv6 = '/^\s*( (([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:)) |(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:)) |(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:)) |(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)) |(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)) |(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)) |(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)) |(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)) )(%.+)?\s*$/x'; $bAntwoord = preg_match($sIPv6);
Een andere regex voor IPv6 luidt als volgt (let op, dit is een beest!):
function validateIPv6($ip) { return 1 === preg_match('/\A(?:(?:(?:[A-F\d]{1,4}:){6}|(?=(?:[A-F\d]{0,4}:){0,6}(?:\d{1,3}\.){3}\d{1,3}\z)(?:(?:[A-F\d]{1,4}:){0,5}|:)(?:(?::[A-F\d]{1,4}){1,5}:|:))(?<!\A:)(?:(?:2(?:5[0-5]|[0-4]\d|)|1\d{0,2}|\d)\.){3}(?:2(?:5[0-5]|[0-4]\d|)|1\d{0,2}|\d)|(?:[A-F\d]{1,4}:){7}[A-F\d]{1,4}|(?=(?:[A-F\d]{0,4}:){0,7}(?:[A-F\d]{0,4}|:)\z)(?:(?:[A-F\d]{1,4}:){1,7}|:)(?:(?::[A-F\d]{1,4}){1,7}|:)|(?=:?(?::[A-F\d]{0,4}){0,7}(?::[A-F\d]{0,4})\z)(?:(?::[A-F\d]{1,4}){1,7}|:)(?:(?::[A-F\d]{1,4}){1,7}))\z/i', $ip); }
Met de filter-functies sinds PHP 5.2.0 wordt dit:
$bAntwoord = filter_var(trim($ip), FILTER_VALIDATE_IP);
Is het een geldig web URL?
De invoer is een geldig web-adres als het begint met http of https. Gevolgd door een hostname of IP-adres (IPv6 is ook ondersteund!) gevolgd door een optionele '/' en bestandslocatie en een optionele query-string (?iets=dit&dit=iets) en optionele anchor-point (#)
LET OP: De versie voor 17-05-2010 had een fout waardoor alles werd geaccepteerd!
$bAntwoord = preg_match('^http[s]?://([\w\d]+([:][\w\d]+)?[@])?((([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6})|(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)|(\[(?:(?:(?:[a-f0-9]{1,4}:){6}|::(?:[a-f0-9]{1,4}:){0,4}|(?:[a-f0-9]{1,4}:){1,1}:(?:[a-f0-9]{1,4}:){4}|(?:[a-f0-9]{1,4}:){1,2}:(?:[a-f0-9]{1,4}:){3}|(?:[a-f0-9]{1,4}:){1,3}:(?:[a-f0-9]{1,4}:){2}|(?:[a-f0-9]{1,4}:){1,4}:(?:[a-f0-9]{1,4}:){1}|(?:[a-f0-9]{1,4}:){1,5}:)(?:[a-f0-9]{1,4}:[a-f0-9]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|(?::|(?:[a-f0-9]{1,4}:){1,6}):(?:[a-f0-9]{1,4})?)\]))(:[\d]+)?(/([-+_~.\d\w:]|[%][a-f\d]{2})*)*([?](&?([-+_~.\d\w]|[%][a-f\d]{2})=?)*)?([#]([-+_~.\d\w:]|[%][a-f\d]{2})*)?$!is', $psAdres);
Met de filter_var functie die sinds PHP 5.2.0 beschikbaar is:
$bAntwoord = filter_var($psAdres_, FILTER_VALIDATE_URL);
nb: niet getest of er beperkingen aan deze functie zitten.
Enkele test voorbeelden:
- http://www.phpfreakz.nl
- https://www.phpfreakz.nl
- http://www.phpfreakz.nl/forum.php?forum=16&iid=1226508
- http://gebruikersnaam@phpfreakz.nl
- http://gebruikersnaam:wachtwoord@phpfreakz.nl (deze URL werkt niet in Internet Explorer)
- http://www.phpfreakz.nl/forum.php?forum=16&iid=1226508#5342
- http://www.phpfreakz.nl:80/forum.php?forum=16&iid=1226508#5342
- http://www.phpfreakz.co.uk
- http://wiki.pfz.nl/
- http://phpfreakz.museum (vaak gemist omdat een tld tot 4 karakters beperk wordt door veel tests)
- http://PFZ.Nl (test moet case insensitive zijn)
- HTTP://PFZ.Nl (de wiki software kan kennelijk niet omgaan met een protocol in caps)
IP-Adressen
- http://127.0.0.1
- https://127.0.0.1
- http://127.0.0.1/forum.php?forum=16&iid=1226508
- http://gebruikersnaam@phpfreakz.nl
- http://gebruikersnaam:wachtwoord@phpfreakz.nl (deze URL werkt niet in Internet Explorer)
- http://127.0.0.1/forum.php?forum=16&iid=1226508#5342
- http://127.0.0.1:80/forum.php?forum=16&iid=1226508#5342
IPv6-Adressen (werkt nog niet in (alle) webbrowsers)
- http://[::1]
- https://[::1]
- http://[::1]/forum.php?forum=16&iid=1226508
- http://gebruikersnaam@[::1]
- http://gebruikersnaam:wachtwoord@[::1] (deze URL werkt niet in Internet Explorer)
- http://[::1]/forum.php?forum=16&iid=1226508#5342
- http://[::1]:80/forum.php?forum=16&iid=1226508#5342
Voorbeelden met fouten
- www.phpfreakz.nl (geen protocol)
- http://www..phpfreakz.nl (2 punten achter elkaar)
Natuurlijk zijn meer test voorbeelden welkom!
Update
Per december 2009 accepteert het .eu domein ook tekens als ä en ó in een domeinnaam. Dat was al langer het geval voor enkele Europese landen tld's. In januari 2010 is daar bij gekomen dat ook aan enkele Arabische landen een top level domain in het Arabische schrift is toegekend.
Daarmee kunnen bovenstaande expressies (mogelijk) niet uit de voeten.
Niet Europees
In mei 2010 zijn de eerste TLD met Arabische en Cyrillisch tekens verschenen. Een geldig web adres is http://وزارة-الأتصالات.مصر/, wat door de browser mogelijk tot http://xn--4gbrim.xn----ymcbaaajlc6dj7bxne2c.xn--wgbh1c vertaald wordt. Regex-validaties gaan hierop volledig mis.
Is het een Nederlands telefoonnummer
Een telefoonnummer
- begint met een 0
- bestaat uit 10 cijfers
als je zo ver wilt opsplitsen:
- het deel "netnummer" bestaat uit 2 tot 4 cijfer
- het netnummer begint met een 0, het 2e cijfer is geen nul.
- het abonneenummer bestaat uit 8 tot 6 cijfers en begint niet met een 0.
vraag je ook af of je wilt accepteren dat iemand zijn nummer met spaties er in opgeeft. Die kun je namelijk eenvoudig vervangen op het moment dat je het nummer verwerkt.
Onderstaand voorbeeld accepteert een - tussen netnummer en abonneenummer.
$bAntwoord = if(preg_match('#^0[1-9][0-9]{0,2}-?[1-9][0-9]{5,7}$#', $sInvoer);
Tekortkoming: een 4-cijferig netnummer in combinatie met een 8-cijferig abonneenummer wordt doorgelaten.
Een str_replace of preg_replace om alle niet cijfers te vervangen en vervolgens met strlen() de lengte bepalen, kan die oplossen.
Maar dat zou ook in de regular expressie mogelijk moeten zijn. NB: er ciruleren ook oplossingen die gebruik maken van een overzicht van alle bestaande netnnummers. Bedenk dat zo'n lijst aan verandering onderhevig is, en dus zo maar achterhaald kan zijn.
Is het een Nederlands mobiel nummer
Een mobiel telefoonnummer begint in Nederland doorgaans met 06 en de andere regels zoals die voor een vast telefoonnummer gelden, zijn ook van toepassing: totaal 10 cijfers en het eerste cijfer na 06 is niet 0.
Bovendien gelden er nog extra's, zoals dat de reeks 06-7(6) niet bedoeld is voor gsm gebruik, maar voor inbelnummers voor internettoegang. Ook is de reeks 06-9 momenteel(!) niet ingebruik. Maar bedenk, voordat je daarop filtert, dat tot voor kort ook de reeks 06-8 nog niet uitgegeven was. Als jouw applicatie weigert om die nummers nú te accepteren, kan een bezoeker mogelijk volgend jaar zijn geldige nummer niet in jouw formulier kwijt.
Is het een Bedrag ?
Een bedrag is eigenlijk een float. Echter word 5,95 niet als geldige float gezien. 5.95 daarentegen weer wel. De oplossing is simpel, de validatie echter niet. De oplossing is het in een string te zetten. De validatie zal getallen als in het lijstje hieronder moeten toelaten.
- 5
- 5,95
- 5.95
- 5,95e+5
- 5.95E-6
- -5,95
- etc.
De onderstaande regex zal dit alles valideren:
$bAntwoord = preg_match('~^[+-]?(?:\d*[.,]\d+|\d+[.,]\d*|\d+)(?:[eE][+-]?\d+)?$~', $sInvoer);
Is het een geldige berekening?
Naar aanleiding van: http://www.phpfreakz.nl/forum.php?forum=1&iid=1284709
Een triviaal probleem met een absoluut niet triviale oplossing. Maakt gebruik van recursie wat mogelijk is met PCRE.
$regex = '{ \A # het absolute begin van de tekst \h* # optionele horizontale whitespace ( # begin van groep 1 (deze wordt recursief aangeroepen) (?: \( # letterlijk een ( \h* [-+]? # optioneel als prefix een + of - \h* # een getal, zoals in de wiki bij Invoervalidatie beschreven (?: \d* \. \d+ | \d+ \. \d* | \d+) (?: [eE] [+-]? \d+ )? (?: \h* [-+*/] # een willekeurige operator \h* (?1) # recursieve aanroep naar patroon 1 )? \h* \) # het sluithaakje | # of: alleen een getal \h* [-+]? \h* (?: \d* \. \d+ | \d+ \. \d* | \d+) (?: [eE] [+-]? \d+ )? ) # en natuurlijk de rest (?: \h* [-+*/] \h* (?1) )? ) \h* \z # het absolute einde van de string }x'; $calculations = array( '123', ' 123 + 123', ' 123 + 456 * ( 789 )', ' ( 123 + 456 ) * 789', '- 123', '123+456', '123+456*-789', '(123+456)*789' ); echo '<table>'; foreach($calculations as $calculation) { echo ' <tr> <td>', htmlspecialchars($calculation), '</td> <td>', (preg_match($regex, $calculation) ? 'Ja' : 'Nee'), '</td> </tr>'; } echo '</table>';





