web-dev-qa-db-ja.com

外部URLを検出する最速の方法

foo='http://john.doe'が外部であるかどうかを検出する最も速い方法は何ですか rlwindow.location.hrefと比較して)?

26
mate64

正規表現バージョンがすでに受け入れられていることは知っていますが、これは、その複雑な正規表現を実行するよりも「高速」であるに違いありません。 String.replaceは非常に高速です。

var isExternal = function(url) {
    var domain = function(url) {
        return url.replace('http://','').replace('https://','').split('/')[0];
    };

    return domain(location.href) !== domain(url);
}

更新

私はこれについてもう少し調査することに決め、正規表現を使用するより高速な方法を見つけました。

var isExternalRegexClosure = (function(){
    var domainRe = /https?:\/\/((?:[\w\d-]+\.)+[\w\d]{2,})/i;

    return function(url) {
        function domain(url) {
          return domainRe.exec(url)[1];  
        }

        return domain(location.href) !== domain(url);
    }
})();

IEこれはString.replaceメソッドよりもわずかに高速です。ただし、ChromeおよびFirefoxでは約2倍高速です。また、正規表現を定義するFirefoxでは、通常、関数内ではなくクロージャ内で1回だけ高速になります。

これはjsperfです 外部ホスト名を決定する4つの異なる方法を調べます。

私が試したすべてのメソッドは、古い電話でも実行するのに1ms未満かかることに注意することが重要です。したがって、大規模なバッチ処理を行わない限り、パフォーマンスを主な考慮事項にするべきではありません。

17
pseudosavant

スキーム、ホスト、またはポートのいずれかが異なる場合にURLが外部であると考える場合は、次のようにすることができます。

function isExternal(url) {
    var match = url.match(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);
    if (typeof match[1] === "string" && match[1].length > 0 && match[1].toLowerCase() !== location.protocol) return true;
    if (typeof match[2] === "string" && match[2].length > 0 && match[2].replace(new RegExp(":("+{"http:":80,"https:":443}[location.protocol]+")?$"), "") !== location.Host) return true;
    return false;
}
30
Gumbo

私はpsuedosavantの方法を使用してきましたが、ドメインのないリンク(_/about_、_image.jpg_)やアンカーリンク(_#about_)など、誤検知が発生するケースがいくつか発生しました。古い方法では、プロトコルが異なると結果も不正確になります(http vs https)。

これが私のわずかに変更されたバージョンです:

_var checkDomain = function(url) {
  if ( url.indexOf('//') === 0 ) { url = location.protocol + url; }
  return url.toLowerCase().replace(/([a-z])?:\/\//,'$1').split('/')[0];
};

var isExternal = function(url) {
  return ( ( url.indexOf(':') > -1 || url.indexOf('//') > -1 ) && checkDomain(location.href) !== checkDomain(url) );
};
_

更新された関数を使用したいくつかのテストを次に示します。

_isExternal('http://google.com'); // true
isExternal('https://google.com'); // true
isExternal('//google.com'); // true (no protocol)
isExternal('mailto:[email protected]'); // true
isExternal('http://samedomain.com:8080/port'); // true (same domain, different port)
isExternal('https://samedomain.com/secure'); // true (same domain, https)

isExternal('http://samedomain.com/about'); // false (same domain, different page)
isExternal('HTTP://SAMEDOMAIN.COM/about'); // false (same domain, but different casing)
isExternal('//samedomain.com/about'); // false (same domain, no protocol)
isExternal('/about'); // false
isExternal('image.jpg'); // false
isExternal('#anchor'); // false
_

いくつかの基本的な jsperfテスト によれば、全体的にはより正確であり、最終的にはわずかに高速になります。大文字と小文字を区別しないテストのために.toLowerCase()を省略した場合、さらに高速化できます。

16
shshaw

pseudosavantの答えは私には正確に機能しなかったので、私はそれを改善しました。

var isExternal = function(url) {
    return !(location.href.replace("http://", "").replace("https://", "").split("/")[0] === url.replace("http://", "").replace("https://", "").split("/")[0]);   
}
4
Jon

「//」で始まるURLやサブドメインを含まないURLのケースもキャッチする必要があったため、pseudosavantとJonの回答に基づいて構築する必要がありました。これが私のために働いたものです:

var getDomainName = function(domain) {
    var parts = domain.split('.').reverse();
    var cnt = parts.length;
    if (cnt >= 3) {
        // see if the second level domain is a common SLD.
        if (parts[1].match(/^(com|edu|gov|net|mil|org|nom|co|name|info|biz)$/i)) {
            return parts[2] + '.' + parts[1] + '.' + parts[0];
        }
    }
    return parts[1]+'.'+parts[0];
};
var isExternalUrl = function(url) {
        var curLocationUrl = getDomainName(location.href.replace("http://", "").replace("https://", "").replace("//", "").split("/")[0].toLowerCase());
        var destinationUrl = getDomainName(url.replace("http://", "").replace("https://", "").replace("//", "").split("/")[0].toLowerCase());
        return !(curLocationUrl === destinationUrl)
};

$(document).delegate('a', 'click', function() {
        var aHrefTarget = $(this).attr('target');
        if(typeof aHrefTarget === 'undefined')
                return;
        if(aHrefTarget !== '_blank')
                return;  // not an external link
        var aHrefUrl = $(this).attr('href');
        if(aHrefUrl.substr(0,2) !== '//' && (aHrefUrl.substr(0,1) == '/' || aHrefUrl.substr(0,1) == '#'))
                return;  // this is a relative link or anchor link
        if(isExternalUrl(aHrefUrl))
                alert('clicked external link');
});
<h3>Internal URLs:</h3>
<ul>
  <li><a href="stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls" target="_blank">stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls</a></li>
  <li><a href="www.stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls" target="_blank">www.stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls</a></li>
  <li><a href="//stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls" target="_blank">//stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls</a></li>
  <li><a href="//www.stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls" target="_blank">//www.stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls</a></li>
</ul>
<h3>External URLs:</h3>
<ul>
  <li><a href="http://www.yahoo.com" target="_blank">http://www.yahoo.com</a></li>
  <li><a href="yahoo.com" target="_blank">yahoo.com</a></li>
  <li><a href="www.yahoo.com" target="_blank">www.yahoo.com</a></li>
  <li><a href="//www.yahoo.com" target="_blank">//www.yahoo.com</a></li>
</ul>
1
BumbleB2na

私の目的のために、shshawの回答に少し変更を加えて、リンクが空でないか、または1文字だけ(「#」であると想定)かどうかを確認しました。元の回答メソッドは誤検知を返します。これは、FAアイコンを追加して、ユーザーが私のページを離れることをユーザーに示すためのものです。

// same thing here, no edit
function checkDomain(url) {
    if ( url.indexOf('//') === 0 ) { url = location.protocol + url; }
    return url.toLowerCase().replace(/([a-z])?:\/\//,'$1').split('/')[0];
};

function isExternal(url) {
    // verify if link is empty or just 1 char + original answer
    return (url.length > 1 && url.indexOf(':') > -1 || url.indexOf('//') > -1 ) && checkDomain(location.href) !== checkDomain(url);
};

// add some icon to external links (function is called in an init method)
function addExternalLinkIcon(){
    $("a[href]").each(function(i,ob){
        // we check it
        if(isExternal($(ob).attr("href"))){
            // then add some beauty if it's external
            // (we assume Font Awesome CSS and font is loaded for my example, of course :-P)
            $(ob).append(" <i class='fa fa-external-link'></i> ");
        }
    });
}
1

すべきではない

function is_external( url ) {
    return url.match( /[a-zA-Z0-9]*:\/\/[^\s]*/g ) != null;
}

トリックをしますか?絶対(内部)URLでは機能しません。

0
user3116736

主な問題は、URLを解析し、そのホスト名を取得する方法です。それは次の方法で行うことができます:

var _getHostname = function(url) {
  var parser = document.createElement('a');
  parser.href = url;

  return parser.hostname;
}

var isExternal = (_getHostname(window.location.href) !== _getHostname('http://john.doe'));

または、 is-url-external モジュールを使用できます。

var isExternal = require('is-url-external');
isExternal('http://john.doe'); // true | false 
0
mrded