11.30
Mieliście kiedyś problem ze sprawdzeniem czy adres URL jest poprawny?
Możemy niby napisać jakąś prostą funkcję jeśli wiemy dokładnie co się tam znajdzie. Ale czy będziesz pamiętać o możliwości przekazania parametrów logowania? portu? dostania się poprzez adres IP a nie domenę?
No właśnie…
W PHP mamy niby funkcje parse_url ale w manualu PHP wyraźnie napisane jest:
Ta funkcja nie służy do sprawdzania poprawności podanego URL
Co więc począć?
Z lenistwa i niechęci do studiowania anglojęzycznej dokumentacji zacząłem szukać w internecie idealnego wyrażenia regularnego, które sprawdzi dla mnie poprawność adresu URL. Szperałem i szperałem aż trafiłem tutaj. Mathias włożył sporo trudu w odnalezienie i przetestowanie wielu wyrażeń regularnych. Postanowiłem więc zaufać jego testom (po co robić coś co ktoś już zrobił) i wybrałem najlepsze z wyrażeń regularnych wg. jego testów.
Nie ufałem mu jednak na tyle, żeby w ciemno kopiować co mi w przeglądarce wyskoczyło.
Postarajmy się więc zrozumieć co poniższe wyrażenie może oznaczać:
Długie jak cholera (502 znaki). Wyczytać stąd nic się nie da, poza tym, że obsługuje tylko protokoły http, https i ftp. Pomogę wam jednak w zrozumieniu tego co się tutaj dzieje.
Nie będę tutaj tłumaczył tego co można znaleźć na Wikipedii. W dodatku po polsku. Odsyłam tutaj.
Po lekturę wyrażeń regularnych w PHP odsyłam z kolei tutaj.
Po przestudiowaniu powyższych KAŻDY pojedynczy element powinien być jasny.
Postarajmy się zrozumieć całość
Rozbijemy to na kawałeczki.
1. Określamy protokoły. Jak pisałem to wyrażenie ogranicza się to http(s) i ftp.
2. Opcjonalnie login i hasło na serwerze.
Kilka kolejnych wierszy to zdefiniowanie adresu serwera lub domeny. A zaczyna się tak.
3. Odrzucamy IP sieci lokalnych.
(?!127(?:\.\d{1,3}){3})
(?!169\.254(?:\.\d{1,3}){2})
(?!192\.168(?:\.\d{1,3}){2})
(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})
4. Pozwalamy na użycie IP z zakresu sieci globalnej (internetu).
(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}
(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))
Lub.
5. Szukamy domeny.
Najpierw szukamy domeny drugiego rzędu (Second Level Domain, SLD).
6. Opcjonalnie po kropce pozwalamy na użycie subdomen. Najbardziej zagnieżdżona subdomena w rzeczywistości przechwytywana jest przez wyrażenie widoczne powyżej. Definicja ciągu znaków dla SLD i subdomen jest jednak identyczna. Poniższe wyrażenie definiuje w związku z tym użycie (lub nie :)) subdomen.
7. Ostatnim krokiem jest znalezienie domeny najwyższego rzędu (Top Level Domain, TLD).
Koniec szukania adresu/domeny.
8. Następnie port. Opcjonalnie.
9. Również opcjonalnie pozostały ciąg znaków (z wyłączeniem spacji). Ten element akurat da się poprawić i zrobię to w swoim czasie ;)
Na przykładzie
Adres tego wpisu będzie więc walidowany w następujący sposób.
http://www.paweliwanowski.pl/2011/11/walidacja-adresu-url/
1. http://
5. www
6. .paweliwanowski
7. .pl
9. /2011/11/walidacja-adresu-url/
Reasumując
(?:(?:https?|ftp)://) # protokół
(?:\S+(?::\S*)?@)? # login, hasło
(?:
(?!10(?:\.\d{1,3}){3}) # odrzucone IP
(?!127(?:\.\d{1,3}){3})
(?!169\.254(?:\.\d{1,3}){2})
(?!192\.168(?:\.\d{1,3}){2})
(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})
(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3]) # IP
(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}
(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))
| # lub
(?: (?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+) # SLD
(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)* # subdomeny
(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})) # TLD
)
(?::\d{2,5})? # port
(?:/[^\s]*)? # reszta
$_iuS
Dzięki za wskazówki!