Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [php] Walidacja URL - najlepszy sposób?
Forum PHP.pl > Forum > Przedszkole
tomilipin
Cześć,
poszukuję najlepszego sposobu na walidację adresów stron w skrypcie PHP. Robię klasę więc wersja obiektowa będzie mile widziana.

Próbowałem już funkcji parse_url oraz filter_var - niestety, obie przepuszczają niepoprawne adresy (pomijając fakt, że parse_url nie służy do walidacji). Chciałbym się więc skierować w stronę wyrażeń regularnych, ale w wielu przypadkach użytkownicy to odradzają. Dlaczego?

Znalazłem takie wyrażenie regularne:
  1. '%^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.[a-z\x{00a1}-\x{ffff}]{2,6}))(?::\d+)?(?:[^\s]*)?$%iu'

z jednej strony jest okropnie długie i jego wykonanie może być zasochłonne, ale z drugiej strony - nie przepuści żadnego błędnego URLa.

Jeśli mogę to samo uzyskać krótszym i prostszym kodem, to chętnie skorzystam, tylko błagam... nie odsyłajcie do szukajek - większość linków (nawet do Zenda w innym temacie na forum) jest już nieaktualnych, albo proponują tam rozwiązanie przepuszczające np. http://www.. (dwie kropki na końcu)

Na dłuższy czas porzuciłem tę kwestię, ale znowu muszę się nią zająć... Nadal poszukuję niezawodnego sposobu parsowania URLi.
Wyrażenie, które podałem w powyższym poście przepuszcza adresy typu:
Kod
http://www.kbrg.
http://www.kbrg
(z kropką i bez kropki na końcu)

Próbowałem też regexpy ze strony http://mathiasbynens.be/demo/url-regex ale nie potrafię dostosować ich do PHP - ciągle wywala mi jakiś błąd sad.gif

Jakieś pomysły?
timon27
Ja bym to zrobił curlem:

  1. function urlExists($url=NULL)
  2. {
  3. if($url == NULL) return false;
  4. $ch = curl_init($url);
  5. curl_setopt($ch, CURLOPT_TIMEOUT, 5);
  6. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
  7. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  8. $data = curl_exec($ch);
  9. $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  10. curl_close($ch);
  11. if($httpcode>=200 && $httpcode<300){
  12. return true;
  13. } else {
  14. return false;
  15. }
  16. }
Crozin
Co jest nie tak z filter_varem? Poprawnie odrzuca pierwszy podany przez Ciebie URL (z kropką na końcu) i przepuszcza drugi (prawidłowy URL).
tomilipin
timon, Twój kod akceptuje taki adres http://www.kbrg.

Ja już nie rozumiem... czy http://www.kbrg. jest poprawnym URLem?

Crozin, filter_var przepuszcza np. to
Kod
http://example.com/"><script>alert(document.cookie)</script>


Generalnie skłaniałem się do użycia tego regexpa
Kod
_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!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])(?:\.(?: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]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:/[^\s]*)?$_iuS

ale dostaję takie błędy:
Cytat
Notice: Undefined variable: _iuS in kaka.php on line 43
Warning: preg_match(): No ending delimiter '_' found in kaka.php on line 44


od razu zaznaczam, że nie znam się na regexp - bazuję na kopiuj-wklej ze strony http://mathiasbynens.be/demo/url-regex
timon27
Mój kod NIE przepuszcza adresu z kropką na końcu. Coś źle wkleiłeś.

  1. if(urlExists("http://www.kbrg.")) echo 'dobry'; else echo 'zly';


wklej to za definicją funkcji.
Bez kropki zwraca prawdę, bo to dobry adres
Crozin
@timon27: Twój sposób jest wyjątkowo słaby. 1) Wymaga wykonania żądania HTTP, które samo w sobie może się wykonywać dosłownie godzinami 2) Ogranicza się do kodów odpowiedzi z zakresu 200-300, czyli jak zew. strona padnie to nie będzie się dało zapisać jej URL-a?
@tomilipin: Ale URL, który podałeś jest przecież poprawnym URL-em, który jak najbardziej może istnieć. A ochronę przed XSS (bo jak rozumiem to chciałeś tutaj przedstawić) uzyskuje się przy pomocy htmspecialchars, które musi zostać użyte w momencie wstawiania danych do dokumentu HTML.
tomilipin
Hmm... tak myślę nad tym, co napisał Crozin i dochodzę do wniosku, że faktycznie to może być problem. No bo jeśli strona padnie, ale jej adres jest poprawny, to niestety funkcja timona zwróci FALSE.

Co do filter_var - zastosowałem to tak:
Kod
var_dump(filter_var($url, FILTER_VALIDATE_URL));


i oto co otrzymałem...
Kod
string(13) "htp://www.k.."
string(17) "http://3628126748"
string(16) "http://224.1.1.1"
string(17) "http://a.b--c.de/"
Wszędzie powinno być FALSE


Natomiast filter_var odrzuca adresy, w których znajdują się znaki Unicode, np. http://www.broń.pl nie mówiąc już o czymś tak egzotycznym jak http://✪df.ws/123 pod którym kryje się działająca strona, ale nawet forumowy parser nie podołał wykryć w tym przypadku linka.


Ale nie poddaję się - szukam dalej rozwiązania smile.gif

-- edit --

Zrobiłem smile.gif Oto regexp, który perfekcyjnie filtruje URL:
Kod
%^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.[a-z\x{00a1}-\x{ffff}]{2,6}))(?::\d+)?(?:[^\s]*)?$%iu

Do użytku z preg_match

Pozdrawiam i dziękuję za pomoc smile.gif
sowiq
Cytat(tomilipin @ 17.03.2014, 17:41:23 ) *
Oto regexp, który perfekcyjnie filtruje URL


Pierwszy z brzegu kontrprzykład: http://test.aaaaaaaaaaaaaaa

Ja kiedyś zrobiłem wyrażenie regularne z listą wszystkich dostępnych domen końcówek domen smile.gif Wydajność pewnie słaba, ale adresy jak powyżej nie przechodziły wink.gif

[edit]
Zresztą można to przyspieszyć rozbijając na kilka akcji, np. najpierw jakiś strpos z listą domen, a później zawężanie wyniku przez regexp smile.gif
emillo91
Ja natomiast znalazłem coś takiego. Może sie przyda :
  1. preg_match("/^[a-zA-Z\-_]+\.[a-zA-Z0-9\.\-_]+\.[a-z]{2,4}$/D",$adres)
sowiq
@emillo91, nowe domeny jak .travel, .museum itp. nie załapią się do Twojego regexpa.
tomilipin
Cytat(sowiq @ 17.03.2014, 21:58:44 ) *
Pierwszy z brzegu kontrprzykład: http://test.aaaaaaaaaaaaaaa
Fuck... rolleyes.gif Może uda się jakoś rozbudować tego regexpa żeby nie przepuszczał takich adresów?
sowiq
Ale przecież niekoniecznie musisz to robić za pomocą jednego regexpa.

Moim zdaniem bardzo dobrym sposobem jest to, co napisałem powyżej. Wchodzisz na Wikipedię, kopiujesz listę domen i robisz z tego tablicę w PHP. Pierwszy krok to sprawdzenie, czy domena jest poprawna. A dalej lecisz z kolejnymi warunkami, chociażby przy pomocy regexpa / regexpów.
karakara
Kilka wyrażeń i ich testy.

http://mathiasbynens.be/demo/url-regex

Tylko jeden przeszedł wszystkie testy - @diegoperini

Edit:
teraz widzę, że tomilipin to wrzucał
Crozin
@tomilipin: filter_var sprawdza czy dany URL jest technicznie poprawny, i faktycznie działa on jedynie w zakresie znaków US-ASCII. To dopiero pierwszy etap sprawdzania poprawności. Później powinieneś skorzystać z parse_url i sprawdzić czy poszczególnego części URL-a są przez Ciebie akceptowalne. Na przykład czy protokół to HTTP(S), albo czy host jest sensowną domeną/IP-kiem.
YourFrog
Pozwole się wtrącić w waszą dyskusję.

Dlaczego próbujecie napisać metodę perfekcyjną do sprawdzenia poprawności adresu url ? Według mnie każdy ma inne potrzeby i robi portal dla innych ludzi. Jednemu będą pasowały linki z konkretnej domeny, innemu z całego świata (wystarczy że serwer odpowie) itd. Według mnie trzeba iść tropem dania narzędzia które w drobnym stopniu pomoże zwalidować adres jednak nie ograniczy programisty (nie wiem jak wy ale ja tych regexpów na pierwszy rzut oka nawet nie rozumiem).

Osobiście skłaniałbym się w kierunku prostej klasy która tak jak u @sowiq'a sprawdzi poprawność domen i protokołów, a resztę pozostawi do decyzji użytkownika. Według mnie sposób @timon27 jest dobry do określonych sytuacji (osobiście bym chciał aby linki na moim serwisie nie prowadziły do 404..). I nie powiniście go wykluczać całkowicie.

Poniżej zamieszczam link do repo w którym daje swoje rozwiazanie. Na 100% nie jest idealne jednak wydaje mi się że jak komuś czegoś będzie w nim brakować to sobie zdziedziczy po mojej klasie / dopisze helper i po sprawie.
https://svn.code.sf.net/p/urlvalidate/code/...k/YourFrog/Url/

Zamieściłem przykładowe testy z informacja czy przechodzą czy nie więc łatwiej można sprawdzić poprawnośc.
tomilipin
Cześć,
znowu zacząłem grzebać w tym kodzie smile.gif

Kilka osób sugerowało sprawdzanie poprawności samej domeny (TLD) - taki sposób odpada, gdyż nie przepuści adresów typu http://142.42.1.1 czy http://例子.测试
Wyrażenie regularne autorstwa @diegoperini, wspomniane wcześniej, nie jest idealne, gdyż nie sprawdza poprawności podanych domen i przepuszcza adresy typu http://asd.aaaaaaaaaaaaa
Innym pomysłem było użycie filter_var i parse_url - patrz pierwszy post i zapoznaj się z opisem tych funkcji w manualu.
Żeby sprawdzić czy "host jest sensowną domeną/IP-kiem" i tak trzeba użyć wyrażeń regularnych, nie widzę więc sensu dodatkowego użycia innych funkcji, skoro można to załatwić jedną linijką kodu.

YourFrog, myślę, że masz dużo racji. Idąc Twoim tokiem myślenia, poczyniłem już pewne kroki. Nie są mi potrzebne chińskie krzaki w nazwach TLD, więc pozbyłem się tej funkjonalności. Adresy, które będę walidował to strony producentów żywności, a nie sądzę, żeby którykolwiek reklamował się pod adresem IP, zatem nie jest mi potrzebna walidacja IPków (jednak zostwiłem ją na wszelki wypadek). Chcę mieć istniejącą domenę na końcu, więc dodałem listę akceptowanych przeze mnie nazw TLD. Nie pinguję serwera, bo w przypadku chwilowego braku odpowiedzi mój kod odrzuciłby poprawny adres. Oczywiście nie chcę, żeby linki prowadziły do 404, więc dopiero po walidacji całego adresu można sobie to sprawdzić.

Doszedłem więc do takiego regexpa:
Kod
_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|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])(?:\.(?: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]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z]{2}|com|org|net|inne_domeny_tutaj_wpisz)))(?::\d{2,5})?(?:/\S*)?$_iuS


ten rzeczywiście nie jest idealny, bo nie akceptuje poprawnych domen zapisanych chińskimi czy arabskimi krzakami, ale odpowiada moim potrzebom.

Co sądzicie?
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.