javascript - URL-valideringsregex för verkliga URL:er


Jag vill validera att givna strängar är URL:er. Att matcha URL:er i text skulle också vara trevligt, men det är inte ett krav. Jag har sökt och experimenterat men hittills har jag inte hittat något som uppfyller dessa krav:

  1. Får inte acceptera strängar som, när de behandlas som länkar, innebär en säkerhetsrisk. Till exempel, <a href="javascript:alert(document.cookie)">clickme</a> är ett giltigt HTML-element och fungerar faktiskt (utlöser en alert och så vidare) i åtminstone vissa webbläsare. Jag är orolig för att om jag tillåter godtyckliga scheman (se nedan) kan det äventyra säkerheten (som noterats, till exempel här: Vad är det bästa reguljära uttrycket för att kontrollera om en sträng är en giltig URL?).

  2. Måste fungera korrekt i JavaScript.

  3. Det skulle vara trevligt om det fungerade likadant i Java -- jag utvecklar i GWT, så det skulle vara trevligt men inte absolut nödvändigt.

  4. Måste acceptera URL:er som används i praktiken, och inte bara standardkompatibla URL:er. Specifika exempel:

    a. Jag vill acceptera http://fr.wikipedia.org/wiki/Français, vilket är icke-standard på grund av det icke-engelska tecknet, men accepterat av mina referenswebbläsare IE(7+) och Chrome.

    b. Jag vill acceptera http://fr.wikipedia.org/wiki/Fran%c3%a7ais, vilket är icke-standard eftersom procentkodningshex ska vara versaler, men som ändå accepteras av IE och Chrome. Jag antar att jag bara kan göra en skiftlägesokänslig matchning - finns det några nackdelar du kan komma på?

    c. Jag vill acceptera http://localhost/localpath/servlet#action?param=value, vilket är icke-standard eftersom fragmentdelen (från '#' till slutet) inte borde inkludera '?' och andra tecken, men det finns appar som genererar sådana URL:er och webbläsare accepterar dem.

    d. Jag vill acceptera URL:er med vilken som helst schema/protokoll (inte bara http, https och ftp), eftersom alla slags appar jag integrerar med och deras användare kan behöva använda sådana URL:er. Jag kan förbjuda 'javascript:' och tillåta allt annat; om du tror att detta skulle kompromettera säkerheten, vänligen säg det.

Det finns massor av frågor om detta ämne på SO och andra ställen, men jag hittade ingen regex som uppfyller alla mina krav. Exempel:

  • Regex i GWT för att matcha URL:er -- Ganska bra och enkel regex, men accepterar inte icke-standardiserade URL:er. Jag kan hantera schema-delen och fallkänslighet för procentkodning, men inte de andra problemen.

  • https://stackoverflow.com/a/190405/96929 -- Gigantisk regex (jag frågar mig själv om alla webbläsare och ramverk jag använder kan hantera denna storlek) som verkar vara mycket omfattande, men säger att den följer standarden och jag kan inte göra varken hackor eller hällar av den.

Tack! :-)

Svar #1

Måste acceptera URL:er som används i praktiken, och inte bara standardkompatibla URL:er

Faktiskt är URI-specifikationen ganska liberal och tillåter konstruktioner som man generellt sett vill utesluta av kompatibilitetsskäl...

Jag vill acceptera http://fr.wikipedia.org/wiki/Français, vilket är icke-standard

Det är inte en URI, men det är en ganska standard IRI.

  • icke-standard eftersom procentkodningens hex ska vara versaler
  • icke-standard eftersom fragmentdelen (från '#' till slutet) inte bör inkludera '?'

Båda dessa är helt acceptabla enligt URI-standarden. RFC 3986 rekommenderar men kräver inte att stora bokstäver används vid skapande av procentkodningar.

Jag kan förbjuda 'javascript:' och tillåta allt annat; om du tror att detta skulle kompromettera säkerheten, vänligen säg det.

Det skulle det. Tyvärr har det funnits flera potentiellt farliga tillägg till URI-schemanamnområdet och det kommer tveklöst att fortsätta att finnas i framtiden. Dessutom finns det potentiellt sätt som svartlistningsfunktioner kan undvikas genom att använda kodade tecken och kontrolltecken.

Dessutom innebär godtycklig schema-matchning att ditt sekundära mål att upptäcka adresser i text kommer att skapa ett falskt positivt resultat de flesta gånger ett kolon används.

Vittlista är det enda troliga sättet framåt, så du kommer helt enkelt att behöva tillåta varje nytt schema manuellt från fall till fall. Detta kräver viss försiktighet; till exempel verkar data:-schemat harmlöst och användbart, men kan potentiellt ha samma XSS-problem som javascript:.

Du kommer också behöva känna till viss information om varje schema. Scheman som http och ftp har en 'serverbaserad namngivningsmyndighet': de kan inkludera ett separat värdnamn och resursväg inom den värden; dessutom kräver du förmodligen att de är absoluta URI:er. Om du vill tillåta fil-URI:er måste du kontrollera att de är värdlösa (file:///). För andra scheman kan det finnas ingen konkret syntax som krävs av själva URI-standarden, men det kan finnas andra begränsningar, till exempel mailto: måste ta en giltig e-postadress.

Jätte-regular expression (jag frågar mig själv om alla webbläsare och ramverk jag använder kan hantera den här storleken) som verkar vara mycket omfattande

Detta fungerar inte i JavaScript eftersom det har den ej stödda \x{kodpunkt}-syntaxen. Även språk som JavaScript, vars regex-motorer arbetar i termer av UTF-16-kodenheter istället för fullständiga Unicode-kodpunkter, kommer inte kunna hantera teckenvängder utanför BMP.

Du skulle behöva ersätta de långa \x{A0}...\x{1FFFD}-grupperna med något enklare som \u00A0-\uFFFD, och sedan kontrollera ogiltiga surrogatpar separat, samt 0xnnFFFE-F icke-tecken, om du bryr dig om dem (antagligen inte).

Man kan hävda att du troligen redan skulle ha tagit bort alla dåliga ersättare och icke-tecken på en allmän inmatningsgranskningsnivå innan du ens kommer så långt som IRI-validering; det finns ingen anledning att tillåta dem i någon textinmatning. Att göra det i ett separat steg är mer logiskt än att försöka pressa in allt i ett enda regex.

Med det ersatt är den längsta delen av det citerade regexet den vansinnigt långa strängen som kontrollerar siffror för att försöka validera numeriska IP-adresser. Det här är typen av saker som regex inte alls är bra på. Jag skulle starkt överväga att inte bry sig om IPv6 och framtida numeriska IP-adresser: även om man antar en utbredd IPv6-adoption snart, kommer ingen att använda dem inom överskådlig framtid. (Vill du ens tillåta länkar till numeriska adresser? Det beror på vad din app gör, men ofta inte.)

Du kan också överväga att inte tillåta användarinformation@ värdnamnsprefix (eftersom de traditionellt sett inte har haft något syfte förutom för förfalskningsattacker), och procentkodade värdnamn (eftersom de inte fyller någon funktion med tanke på att Punycode finns, och fungerar inte i vissa webbläsare).

Så det finns inte ett enda svar på IRI-validering, men här är den typ av ställe där du kan börja:

(
    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
)

(Skiftlägesokänslighet antas. Detta tillämpar DNS-begränsningar som inte är en del av URI-specifikationen själv, även om det är ofullständigt eftersom det inte kontrollerar för ledande/slutande - i DNS-etiketter, eller sifferspannet i IPv4-okterar. Validering av e-postadresser lämnas som en övning för läsaren, eftersom det i sig är en mödosam uppgift som inte passar regex om man vill göra det noggrant.)

Svar #2

Eftersom du använder Java på serversidan, skulle jag föreslå att du använder URI. Den kommer att acceptera all den "bisarra" saker du vill och det är bara en fråga om .getScheme() för att kontrollera att det faktiskt är HTTP eller HTTPS.

Och till skillnad från URL kommer URI inte att försöka göra namnuppslagning!