javascript - URL-tarkistuksen säännöllinen lauseke todellisille URL-osoitteille


Haluan varmistaa, että annetut merkkijonot ovat URL-osoitteita. URL-osoitteiden etsiminen tekstistä olisi myös mukavaa, mutta se ei ole pakollista. Olen etsinyt ja kokeillut, mutta toistaiseksi en ole löytänyt mitään, joka vastaisi näitä vaatimuksia:

  1. Ei saa hyväksyä merkkijonoja, jotka linkkeinä käsiteltynä aiheuttavat turvallisuusriskin. Esimerkiksi <a href="javascript:alert(document.cookie)">clickme</a> on kelvollinen HTML-elementti ja tosiaan toimii (antaa hälytyksen jne.) ainakin joissakin selaimissa. Olen huolissani siitä, että jos sallin mielivaltaiset muodot (katso alla), se voi vaarantaa turvallisuuden (kuten on todettu esimerkiksi täällä: Mikä on paras säännöllinen lauseke tarkistamaan, onko merkkijono kelvollinen URL?).

  2. Täytyy toimia oikein JavaScriptissä.

  3. Olisi mukavaa, jos se toimisi samalla tavalla Javassa -- kehitän GWT:llä, joten tämä olisi mukavaa mutta ei täysin välttämätöntä.

  4. On hyväksyttävä käytännössä käytetyt URL-osoitteet, eikä vain standardin mukaisia URL-osoitteita. Tarkempia esimerkkejä:

    a. Haluan hyväksyä http://fr.wikipedia.org/wiki/Françaisin, joka ei ole vakiona ei-englanninkielisen merkin vuoksi, mutta sen hyväksyvät vertailuselaimeni IE(7+) ja Chrome.

    b. Haluan hyväksyä http://fr.wikipedia.org/wiki/Fran%c3%a7ais, joka ei ole standardi, koska prosenttien koodauksen heksadesimaalit pitäisi olla isoja, mutta sen hyväksyvät taas IE ja Chrome. Arvelen, että voisin vain tehdä kirjainkoon huomaamattoman vertailun - näetkö mitään haittaa siinä?

    c. Haluan hyväksyä http://localhost/localpath/servlet#action?param=value, mikä on epästandardi, koska fragmenttiosa (kohtaa '#' myöten loppuun) ei pitäisi sisältää '?' eikä muita merkkejä, mutta on sovelluksia, jotka generoivat tällaisia URL-osoitteita ja selaimet hyväksyvät ne.

    d. Haluan hyväksyä URL-osoitteita kaikilla kaavoilla/protokollilla (ei vain http, https ja ftp), koska kaikenlaiset sovellukset, joiden kanssa integroin, ja niiden käyttäjät saattavat tarvita tällaisia URL-osoitteita. Voin kieltää 'javascript:' ja sallia kaiken muun; jos uskot, että tämä vaarantaisi turvallisuuden, kerro siitä.

Tässä aiheessa on tonni kysymyksiä SO:ssa ja muissa paikoissa, mutta en löytänyt säännöllistä lauseketta, joka vastaisi kaikkiin vaatimuksiini. Esimerkkejä:

  • Regex GWT:ssä URL-osoitteiden tunnistamiseen -- Melko hyvä ja yksinkertainen regex, mutta ei hyväksy ei-standardeja URL-osoitteita. Voin käsitellä skeemaosan ja prosenttien koodauksen kirjainkoon tarkkuuden, mutta en muita ongelmia.

  • https://stackoverflow.com/a/190405/96929 -- Jättimäinen säännöllinen lauseke (kysyn itseltäni, voivatko kaikki käyttämäni selaimet ja kehykset käsitellä tätä kokoa), joka näyttää olevan hyvin kattava, mutta sanoo noudattavansa standardia, enkä saa siitä mitään selvää.

Kiitos! :-)

Vastaus #1

On hyväksyttävä käytännössä käytetyt URL-osoitteet, ei vain standardien mukaiset URL-osoitteet

Itse asiassa URI-spesifikaatio on melko liberaali ja sallii rakenteita, jotka yleensä haluat sulkea pois yhteensopivuussyistä...

Haluan hyväksyä http://fr.wikipedia.org/wiki/Françaisin, joka on epästandardi

Se ei ole URI, mutta se on varsin tavallinen IRI.

  • ei-standardi, koska prosenttikoodeissa hexin pitäisi olla isoilla kirjaimilla
  • ei-standardi, koska fragmenttiosa (merkistä '#' loppuun) ei saisi sisältää '?'

Molemmat ovat täysin hyväksyttäviä URI-standardin mukaan. RFC 3986 suosittelee, mutta ei vaadi, että isoja kirjaimia käytetään prosenttikoodauksia luotaessa.

Voin estää 'javascript:' ja sallia kaiken muun; jos luulet tämän vaarantavan turvallisuuden, kerro siitä.

Se olisi. Valitettavasti URI-skeeman nimiavaruuteen on tullut useita mahdollisesti vaarallisia lisäyksiä, ja niitä tulee epäilemättä jatkossakin olemaan. Lisäksi on olemassa mahdollisia tapoja, joilla mustan listan ominaisuuksia saatetaan kiertää käyttämällä koodattuja merkkejä ja ohjausmerkkejä.

Lisäksi mielivaltaisen kaavan sovittaminen tarkoittaa, että toissijainen tavoitteenne tunnistaa osoitteet tekstistä tuottaa väärän positiivisen tuloksen useimmiten, kun kaksoispistettä käytetään.

Valkolistaus on ainoa uskottava tapa edetä, joten sinun täytyy vain sallia jokainen uusi kaava tapauskohtaisesti käsin. Tämä vaatii varovaisuutta; esimerkiksi data:-kaava vaikuttaa harmittomalta ja hyödylliseltä, mutta voi kärsiä samoista XSS-ongelmista kuin javascript:.

Sinun täytyy myös tietää joitakin tietoja jokaisesta kaaviosta. Kaaviot kuten http ja ftp käyttävät 'palvelinpohjaista nimeämisviranomaista': ne voivat sisältää erillisen isäntänimen ja resurssipolun kyseisessä isännässä; lisäksi todennäköisesti tarvitset niiden olevan absoluuttisia URI:ita. Jos haluat sallia tiedosto-URI:ita, sinun täytyy tarkistaa, että se oli isännättömä (file:///). Muille kaavioille URI-standardin itsessään ei välttämättä vaadi konkreettista syntaksia, mutta saattaa olla muita rajoituksia, esimerkiksi mailto: on otettava kelvollinen sähköpostiosoite.

Jättimäinen regex (kysyn itseltäni, pystyykö kaikki käyttämäni selaimet ja kehykset käsittelemään tämän kokoisen) joka näyttää olevan erittäin kattava

Tämä ei toimi JavaScriptissä, koska siinä on tuettu \x{code point} -syntaksi. Lisäksi kielet, kuten JavaScript, joiden regex-moottorit toimivat UTF-16-koodiyksiköiden eikä kokonaisten Unicode-koodipisteiden mukaan, eivät pysty käsittelemään merkkiavaruuksia BMP:n ulkopuolella.

Sinun täytyisi korvata pitkät \x{A0}...\x{1FFFD}-ryhmät jollakin yksinkertaisemmalla, kuten \u00A0-\uFFFD, ja tarkistaa sitten erikseen virheelliset välipariheksamerkit sekä 0xnnFFFE-F ei-merkit, jos välität niistä (todennäköisesti et).

Voidaan perustellusti sanoa, että olet todennäköisesti jo poistanut kaikki huonot varaajat ja ei-merkit yleisellä syötteen tarkistustasolla ennen kuin pääset IRI:n validointiin; ei ole syytä sallia niitä missään tekstisyötteessä. Sen tekeminen erillisessä vaiheessa on järkevämpää kuin yrittää tunkea kaikkea yhteen säännölliseen lausekkeeseen.

Kun se on korvattu, lainatun säännöllisen lausekkeen pisin osa on uskomattoman pitkä numerosarjan tarkistus, joka yrittää validoida numeerisia IP-osoitteita. Tämä on juuri se asia, jossa säännölliset lausekkeet eivät ole lainkaan hyviä. Harkitsisin vahvasti olevani vaivaamatta itseäni IPv6- ja tulevien IPv-numeeristen osoitteiden kanssa: vaikka oletettaisiin laaja IPv6:n käyttöönotto pian, kukaan ei tule käyttämään niitä lähitulevaisuudessa. (Haluatko edes sallia linkit numeerisiin osoitteisiin? Se riippuu siitä, mitä sovelluksesi tekee, mutta usein ei.)

Voisit myös harkita käyttäjän info@-isäntänimen etuliitteiden kieltämistä (koska niistä ei perinteisesti ole ollut hyötyä muuten kuin huijaushyökkäyksissä) ja prosenttikoodein koodattuja isäntänimiä (koska niistä ei ole hyötyä Punycode:n olemassaolon huomioiden, ja ne eivät toimi joissakin selaimissa).

Joten ei ole yhtä ainoaa vastausta IRI:n validointiin, mutta tässä on eräänlainen paikka, josta voit aloittaa:

(
    https?://
    (
        ([0-9]{1-3}(\.[0-9]{1-3}){3})|
        ([-0-9a-z\u00A0-\uFFFD]{1-63}(\.[-0-9a-z\u00A0-\uFFFD]{1-63})*)
    )
    (:[0-9]+)?/
    (
        %[0-9a-f][0-9a-f]|
        [-._!$&'()*+,:;=@~0-9a-z\u00A0-\uFFFD/?#]
    )*
)|(
    ftp://                                    // same again but with no ?query
    ...                                       // or port number
)|(
    mailto:                                   // specify requirements for
    ...                                       // other accepted schemes
)

(Kirjainkoon häiritsemättömyys oletetaan. Tämä soveltaa DNS-rajoituksia, jotka eivät ole osa URI-spesifikaatiota itsessään, vaikka epätäydellisesti, koska se ei tarkista DNS-tunnusten johtavia/lopullisia --merkkejä tai IPv4-oktetien numeroväliä. Sähköpostiosoitteiden validoiminen jätetään lukijan harjoitukseksi, koska se on itsessään vaativa tehtävä, jota ei ole tarkoituksenmukaista tehdä säännöllisten lausekkeiden avulla, jos haluat tehdä sen perusteellisesti.)

Vastaus #2

Koska käytät Javaa palvelinpuolella, ehdottaisin, että käytät URI:a. Se hyväksyy kaiken haluamasi "outouden" ja on vain kyse .getScheme():n käyttämisestä tarkistaaksesi, että se on todellakin HTTP tai HTTPS.

Ja toisin kuin URL, URI ei yritä tehdä nimen ratkaisemista!