使用JavaScript验证URL

86

我想验证一个URL并显示消息。以下是我的代码:

$("#pageUrl").keydown(function(){
        $(".status").show();
        var url = $("#pageUrl").val();

        if(isValidURL(url)){

        $.ajax({
            type: "POST",
            url: "demo.php",
            data: "pageUrl="+ url,
            success: function(msg){
                if(msg == 1 ){
                    $(".status").html('<img src="images/success.gif"/><span><strong>SiteID:</strong>12345678901234456</span>');
                }else{
                    $(".status").html('<img src="images/failure.gif"/>');
                }
            }
            });

            }else{

                    $(".status").html('<img src="images/failure.gif"/>');
            }

    });


function isValidURL(url){
    var RegExp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;

    if(RegExp.test(url)){
        return true;
    }else{
        return false;
    }
} 
我的问题是,即使输入正确的URL,它也会在匹配正则表达式之前显示错误消息,而且即使URL像"http://wwww"这样的东西,它也会返回true。
感谢您的建议。

3
别忘了加上服务器端检查,因为 JavaScript 检查很容易被黑客攻击。 - Randell
5
注意:网址“wwww”是有效的网址。它可能是一个名为wwww的主机。 - kanngard
Jquery插件Validation也可以验证URL。 http://docs.jquery.com/Plugins/Validation/Methods/url - Codler
2
Google的Closure库在goog.string.linkify命名空间中有一个URL验证器,您可以从此处的源代码中提取它:https://code.google.com/p/closure-library/source/browse/closure/goog/string/linkify.js#100 查看findFirstUrl()函数。 - Technetium
请查看此链接,其中包含一些简单的正则表达式验证示例,包括用于URL的一个,而不强制使用字符串“www”,并防止在“http”之前出现任何其他字符:http://lionfishtechnologies.com/developers/tips/validating-common-formats-using-Regular-expressions-with-Javascript.html 谢谢,Chris - Chris Rosete
显示剩余2条评论
19个回答

69

有人提到了Jquery Validation插件,如果你只想验证URL,使用它可能过于复杂,这是该插件中验证URL的正则表达式:

return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);

这是他们获取信息的来源:http://projects.scottsplayground.com/iri/

@nhahtdh指出 这已经更新为:

        // Copyright (c) 2010-2013 Diego Perini, MIT licensed
        // https://gist.github.com/dperini/729294
        // see also https://mathiasbynens.be/demo/url-regex
        // modified to allow protocol-relative URLs
        return this.optional( element ) || /^(?:(?:(?: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\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value );

来源:https://github.com/jzaefferer/jquery-validation/blob/c1db10a34c0847c28a5bd30e3ee1117e137ca834/src/core.js#L1349


jQuery Validation的方法允许在域名中使用下划线(“_”)。据我所知,这在域名中是不允许的。只有连字符(“-”)是被允许的。例如,PHP的filter_var($url, FILTER_VALIDATE_URL)似乎不允许在域名中使用下划线。 - Vladius
“\u00A0-\uD7FF”范围过于宽泛。像” (\u201D)这样的引号字符就在这个范围内。例如,如果首先按空格分割,引用句子的结尾可能是“this.”。如果这不是技术上的误报,但如果使用此正则表达式来识别URL,则通常会在实践中出现问题。 - MetaThis
2
上述正则表达式已被Diego Perini的正则表达式替换,出现在jQuery Validation的源代码中:https://github.com/jzaefferer/jquery-validation/blob/master/src/core.js#L1191 - nhahtdh
1
正则表达式已经移动到第 1306 行 - https://github.com/jzaefferer/jquery-validation/blob/master/src/core.js#L1306 - The Only One Around
2
@Kousha:www.google.com 不是 一个有效的URL,因此它应该无法通过测试。 - Mr. Lance E Sloan
显示剩余2条评论

49

使用正则表达式解析URL并不实际。根据RFC1738规则的完整实现将导致一个非常长的正则表达式(假设它是可能的)。当然,您当前的表达式可能会失败于许多有效的URL,同时通过了一些无效的URL。

相反:

a. 使用真正遵循实际规则的URL解析器。(我不知道JavaScript是否有这样的解析器;但在服务器端可能会过度)。或者,

b. 只需去除任何前导或后续空格,然后检查它是否具有你所喜欢的计划之一(通常为“http://”或“https://”),就完成了。或者,

c. 尝试使用URL并查看最终结果,例如从服务器端发送HTTP HEAD请求。如果收到404或连接错误,则可能是错误的URL。

即使url类似于“http://wwww",它仍将返回true。

嗯,这确实是一个完全有效的URL。

如果您想检查像“wwww”这样的主机名是否存在,则只能在DNS中查找它。同样,这将是服务器端代码。


46
这是否务实?是的。它是否回答了问题或提供了足够的信息来实现实施?没有。 - coreyward
1
要匹配IRI([RFC 3987](http://www.ietf.org/rfc/rfc3987.txt))的正则表达式非常接近,请参见“[W3C兼容URL的正则表达式?](https://dev59.com/SlfUa4cB1Zd3GeqPFCMn)”。请注意,这将匹配任何[IRI](http://www.w3.org/2002/Talks/0715-duerst-iri/slide2-0.html),而不仅仅是URL([RFC 1738](http://www.ietf.org/rfc/rfc1738.txt))或URI([RFC 3986](http://www.ietf.org/rfc/rfc3986.txt))。 - Robin Winslow

34
function validateURL(textval) {
    var urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
    return urlregex.test(textval);
}

这可以返回类似以下的URL的真值:

https://dev59.com/KXM_5IYBdhLWcg3wmkiu
或:
http://regexlib.com/DisplayPatterns.aspx?cattabindex=1&categoryId=2

2
为什么要使用 RegExp 构造函数?在这里使用直接量也可以。为什么要将对象赋值给变量?使用 function validateURL(textval) { return /^(http...$/.test(textval) } 会稍微更短更清晰一些。 - davidchambers
7
[...&...]不能达到你的预期。字符类不允许包含字符串。 - genio
这个URL虽然是合法的,但无法通过验证: http://uk.reuters.com/article/2013/02/25/rosneft-tender-idUKL6N0BPJZC20130225(将结尾的20130225删除后就是有效的) - ytoledano
为什么asdf.asdf返回true? - Raza Ahmed
这完全取决于你对“有效URL”的理解。当粘贴到浏览器地址栏时,你提供的所有示例都可以正常工作(被视为有效),但在作为<a href="">的值时,它们很可能会失败(被视为无效)。 - trejder
如何修改@MoheshMohan以更宽容的方式允许“www.example.com”?(这样它也可以像“http://www.example.com”一样工作) - Basj

16

我还编写了一个基于rfc1738和rfc3986的URL验证函数,用于检查http和https URL。我尝试使其模块化,以便更好地维护并适应自己的要求。

正则表达式在本文末尾显示。

该正则表达式接受具有一些国际域名或IPv4数字的HTTP和HTTPS URL。目前不支持IPv6。

window.isValidURL = (function() {// wrapped in self calling function to prevent global pollution

     //URL pattern based on rfc1738 and rfc3986
    var rg_pctEncoded = "%[0-9a-fA-F]{2}";
    var rg_protocol = "(http|https):\\/\\/";

    var rg_userinfo = "([a-zA-Z0-9$\\-_.+!*'(),;:&=]|" + rg_pctEncoded + ")+" + "@";

    var rg_decOctet = "(25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])"; // 0-255
    var rg_ipv4address = "(" + rg_decOctet + "(\\." + rg_decOctet + "){3}" + ")";
    var rg_hostname = "([a-zA-Z0-9\\-\\u00C0-\\u017F]+\\.)+([a-zA-Z]{2,})";
    var rg_port = "[0-9]+";

    var rg_hostport = "(" + rg_ipv4address + "|localhost|" + rg_hostname + ")(:" + rg_port + ")?";

    // chars sets
    // safe           = "$" | "-" | "_" | "." | "+"
    // extra          = "!" | "*" | "'" | "(" | ")" | ","
    // hsegment       = *[ alpha | digit | safe | extra | ";" | ":" | "@" | "&" | "=" | escape ]
    var rg_pchar = "a-zA-Z0-9$\\-_.+!*'(),;:@&=";
    var rg_segment = "([" + rg_pchar + "]|" + rg_pctEncoded + ")*";

    var rg_path = rg_segment + "(\\/" + rg_segment + ")*";
    var rg_query = "\\?" + "([" + rg_pchar + "/?]|" + rg_pctEncoded + ")*";
    var rg_fragment = "\\#" + "([" + rg_pchar + "/?]|" + rg_pctEncoded + ")*";

    var rgHttpUrl = new RegExp( 
        "^"
        + rg_protocol
        + "(" + rg_userinfo + ")?"
        + rg_hostport
        + "(\\/"
        + "(" + rg_path + ")?"
        + "(" + rg_query + ")?"
        + "(" + rg_fragment + ")?"
        + ")?"
        + "$"
    );

    // export public function
    return function (url) {
        if (rgHttpUrl.test(url)) {
            return true;
        } else {
            return false;
        }
    };
})();

一行代码实现正则表达式:

var rg = /^(http|https):\/\/(([a-zA-Z0-9$\-_.+!*'(),;:&=]|%[0-9a-fA-F]{2})+@)?(((25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])(\.(25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])){3})|localhost|([a-zA-Z0-9\-\u00C0-\u017F]+\.)+([a-zA-Z]{2,}))(:[0-9]+)?(\/(([a-zA-Z0-9$\-_.+!*'(),;:@&=]|%[0-9a-fA-F]{2})*(\/([a-zA-Z0-9$\-_.+!*'(),;:@&=]|%[0-9a-fA-F]{2})*)*)?(\?([a-zA-Z0-9$\-_.+!*'(),;:@&=\/?]|%[0-9a-fA-F]{2})*)?(\#([a-zA-Z0-9$\-_.+!*'(),;:@&=\/?]|%[0-9a-fA-F]{2})*)?)?$/;

3
太好了!非常有帮助,你把它分成不同的部分,这样我就不会盲目相信一个庞大的正则表达式了。 - Jason

13
在类似情况下,我用了这个方法成功脱身:
someUtils.validateURL = function(url) {
    var parser = document.createElement('a');
    try {
        parser.href = url;
        return !!parser.hostname;
    } catch (e) {
        return false;
    }
};

也就是说,如果浏览器可以为您完成工作,为什么还要发明轮子呢?但是,当然,这只在浏览器中有效。

解析后的 URL 包含各种部分,正是浏览器会如何解释它的方式:

parser.protocol; // => "http:"
parser.hostname; // => "example.com"
parser.port;     // => "8080"
parser.pathname; // => "/path/"
parser.search;   // => "?search=test"
parser.hash;     // => "#hash"
parser.host;     // => "example.com:3000"

使用这些方法可以根据要求改进您的验证函数。唯一的缺点是它将接受相对URL,并使用当前页面服务器的主机和端口。但是,您可以利用它,通过从部分重新组装URL并始终将其完整地传递给AJAX服务。

无法接受的是无效的URL,例如http:\:8883将返回false,但:1234是有效的,并解释为http://pagehost.example.com/:1234,即相对路径。

更新

这种方法在Chrome和其他WebKit浏览器中不再可行。即使URL无效,主机名也会被填充上一些值,例如从base中获取。它仍然有助于解析URL的部分,但不允许验证URL。

可能更好的非自己编写解析器的方法是使用var parsedURL = new URL(url)并捕获异常。请参见例如URL API。所有主流浏览器和NodeJS都支持它,但仍标记为实验性的。


3
对于<script>alert();</script>,这将返回TRUE。 - Subin
2
@Subin 在现代浏览器中,这个答案中的方法已经不再适用了。感谢您引起了注意。实际上,无论您传递什么字符串,主机名都会被填充。 - Alex Pakka

8

我从http://angularjs.org/找到了最好的正则表达式。

var urlregex = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;


如果我测试这个:---- /^(ftp|http|https)://(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(/|/([\w#!:.?+=&%@!-/]))?$/.test('http://www.abc.com.com.com') 它会返回 true 值。我认为它应该只允许 URL 中有 2 个 .com。 - Abhishek
@Abhishek,该URL是有效的,点击它将带您到一些占位符网站。 - Jacob

5
这是我成功的方法:
function validateURL(value) {
    return /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
    }

从那里开始,只需要调用该函数即可获得true或false的返回结果:

validateURL(urltovalidate);

注意,这将无法处理IP地址。 - A F

5
我知道这是一个很老的问题,但由于它没有任何被接受的答案,我建议您使用URI.js框架:https://github.com/medialize/URI.js 您可以使用它来检查不良格式的URI,使用try/catch块:
function isValidURL(url)
{
    try {
        (new URI(url));
        return true;
    }
    catch (e) {
        // Malformed URI
        return false;
    }
}

当然,它会将类似于“%@”这样的内容视为格式良好的相对URI...因此我建议您阅读URI.js API以执行更多检查,例如如果您想确保用户输入了格式良好的绝对URL,可以这样做:
function isValidURL(url)
{
    try {
        var uri = new URI(url);
        // URI has a scheme and a host
        return (!!uri.scheme() && !!uri.host());
    }
    catch (e) {
        // Malformed URI
        return false;
    }
}

它在 htt://localhost:86/asdfghjkl 上失败了。 - Shivam Chawla
你的URL是有效的,所以测试返回true。我猜你想做的是检查协议是否为http(s),然后只需在测试中添加!!uri.scheme().match(/^http(s?)$/)即可。 - Romain

3

URL API 可用于验证 URL 字符串的结构。

当尝试将无效的 URL 字符串序列化为 URL 对象时,会抛出错误。这可以抽象成一个辅助函数(下面是 TypeScript 代码片段):

function isValidURL(URL: string) : boolean {
    try {
        new URL(string);
        return true;
    } catch (err) { return false; }
}

isValidURL('https://www.google.com'); // returns true
isValidURL('localhost:3000'); // returns true
isValidURL('not-a-valid-url'); // returns false
isValidURL('google.com'); // returns false (see footnote)

如果您希望HTTP/网页链接严格有效,我们可以简单地在返回语句中添加一个条件:
...
    const url = new URL(string);
    return url.protocol === 'https:' || url.protocol === 'http:';
...

不可否认,这种方法存在一些注意事项:

  • Internet Explorer中不支持URL API(可以通过polyfill进行修复)。
  • 如果没有额外的检查,缺少协议或端口的URL被视为无效(例如google.com是无效的,但google.com:3000是可以的)。这可能对某些用例产生了意想不到的影响。

2
你可以使用最近标准化的URL API。浏览器支持情况不太确定,请参见链接。new URL(str)保证对于无效的URL会抛出TypeError异常。
如上所述,http://wwww是一个有效的URL。

1
到了2023年,URL API已经在每个浏览器中可用,并且已经存在多年。 - Jacob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接