Як перевірити, чи рядок URL-адреси абсолютний чи відносний?


76

Як я можу протестувати URL-адресу, якщо це відносний або абсолютний шлях у Javascript або jQuery? Я хочу обробляти відповідно, залежно від того, переданий URL-адреса є локальним або зовнішнім шляхом.

if (urlString starts with http:// or https://)
 //do this

Відповіді:


38
var pat = /^https?:\/\//i;
if (pat.test(urlString))
{
    //do stuff
}

Для відносних URL-адрес протоколів використовуйте цей регулярний вираз:

/^https?:\/\/|^\/\//i


14
Це відповідає на запитання, але ви також можете розглянути відносні URL-адреси протоколів, які починаються з //.
gerryster

3
Що робити, якщо url містить "file: //"? БУМ! Трагедія. Відповідь від @Philipp є більш надійною.
Скай

2
Прийнята відповідь недійсна, принаймні в 2019 році. Chrome із задоволенням приймає http: example.com.
Gene S

179

ШВИДКО

Якщо вам потрібно лише протестувати на, http://або https://тоді найефективніший спосіб:

if (urlString.indexOf('http://') === 0 || urlString.indexOf('https://') === 0)

УНІВЕРСАЛЬНА

Однак я б запропонував більш універсальний, не враховуючи регістр, протокольно-агностичний підхід:

var r = new RegExp('^(?:[a-z]+:)?//', 'i');
r.test('http://example.com'); // true - regular http absolute URL
r.test('HTTP://EXAMPLE.COM'); // true - HTTP upper-case absolute URL
r.test('https://www.exmaple.com'); // true - secure http absolute URL
r.test('ftp://example.com/file.txt'); // true - file transfer absolute URL
r.test('//cdn.example.com/lib.js'); // true - protocol-relative absolute URL
r.test('/myfolder/test.txt'); // false - relative URL
r.test('test'); // false - also relative URL

Поясніть RegExp

^(?:[a-z]+:)?//

^- початок рядка
(?:- початок незахопленої групи
[a-z]+- будь-який символ від 'a' до 'z' 1 або більше разів
:- рядок (символ двокрапки)
)?- кінець незахопленої групи. Група, що з'являється 0 або 1 раз
//- рядок (два символи косої риски вперед)
'i'- прапор, що не враховує регістр


чому аз? чи не може доменне ім'я в доменному імені мати 0-9 та дефіс?
Атул Гупта

3
правильно, але ми не перевіряємо тут доменне ім’я, правда? Це все одно буде працювати:/^(?:[a-z]+:)?\/\//i.test('https://www.ex-maple-123.com');
Geo

Чи може схема містити цифри? Ми всі знаємо http, https, ftp та mailto. Хтось визначає власні схеми для внутрішніх інструментів? Я думаю, OneNote та Outlook роблять у Windows.
yzorg

1
Це не захоплює URL-адреси "mailto:". Не те, що я знаю, чи є URL-адреси поштового зв’язку абсолютними чи відносними ;-)
Пітер,

1
new RegExp('^(//|[a-z]+:)', 'i')має працювати для узгодження mailto:, about:, tel:і т.д. , включаючи існуючі тестові випадки. Ідея тут полягає в тому, щоб як і раніше забезпечувати абсолютні URL-адреси, що відносяться до протоколу, і одночасно розширювати існуючу функціональність виявлення абсолютних URL-адрес, не вимагаючи перевірки подвійних скісних рисок ( //). Таким чином, r.test('mailto:hi@example.com') === true, r.test('https:example.com') === trueі так далі.
Метт Борха

22

Оригінальна відповідь

Дуже швидко і дуже гнучко перевірка:

if (url.indexOf('://') > 0 || url.indexOf('//') === 0 ) {
    // URL is absolute; either "http://example.com" or "//example.com"
} else {
    // URL is relative
}

Це розпізнає абсолютну URL-адресу, якщо:

  • URL-адреса містить ": //" де завгодно після першого символу, або
  • URL починається з "//" (відносно протоколу)

  • Немає регулярного виразу.
  • Немає jQuery чи іншої залежності.
  • Жодних закодованих імен протоколів, які роблять умову чутливою до регістру.
  • Відсутність маніпуляцій із рядками (наприклад, toLowerCase або подібне).
  • Тільки перевірка на "відносну або абсолютну", але не робить ніяких інших перевірок осудності, може бути використана для веб-URL або будь-якого внутрішнього протоколу.

Оновлення 1 (повний приклад функції)

Ось швидка функція, яка повертає true / false для вказаної URL-адреси:

function isUrlAbsolute(url) { 
    return (url.indexOf('://') > 0 || url.indexOf('//') === 0);
}

І те саме в ES6:

const isUrlAbsolute = (url) => (url.indexOf('://') > 0 || url.indexOf('//') === 0)

Оновлення 2 (URL-адреси всередині параметра URL)

Для додаткової адреси URL-адрес у форматі /redirect?target=http://example.orgя рекомендую використовувати цей код:

function isUrlAbsolute(url) {
    if (url.indexOf('//') === 0) {return true;} // URL is protocol-relative (= absolute)
    if (url.indexOf('://') === -1) {return false;} // URL has no protocol (= relative)
    if (url.indexOf('.') === -1) {return false;} // URL does not contain a dot, i.e. no TLD (= relative, possibly REST)
    if (url.indexOf('/') === -1) {return false;} // URL does not contain a single slash (= relative)
    if (url.indexOf(':') > url.indexOf('/')) {return false;} // The first colon comes after the first slash (= relative)
    if (url.indexOf('://') < url.indexOf('.')) {return true;} // Protocol is defined before first dot (= absolute)
    return false; // Anything else must be relative
}

І те саме у короткій формі та ES 6

// Traditional JS, shortened
function isUrlAbsolute(url) {
    return url.indexOf('//') === 0 ? true : url.indexOf('://') === -1 ? false : url.indexOf('.') === -1 ? false : url.indexOf('/') === -1 ? false : url.indexOf(':') > url.indexOf('/') ? false : url.indexOf('://') < url.indexOf('.') ? true : false;
}

// ES 6
const isUrlAbsolute = (url) => (url.indexOf('//') === 0 ? true : url.indexOf('://') === -1 ? false : url.indexOf('.') === -1 ? false : url.indexOf('/') === -1 ? false : url.indexOf(':') > url.indexOf('/') ? false : url.indexOf('://') < url.indexOf('.') ? true : false)

Ось кілька тестових випадків:

// Test
console.log( isUrlAbsolute('http://stackoverflow.com') ) // -> true
console.log( isUrlAbsolute('//stackoverflow.com') ) // -> true
console.log( isUrlAbsolute('stackoverflow.com') ) // -> false
console.log( isUrlAbsolute('Ftp://example.net') ) // -> true
console.log( isUrlAbsolute('/redirect?target=http://example.org') ) // -> false

Оновлення 3 (уточнити відносні URL-адреси)

Я бачив кілька коментарів щодо недійсного виводу:

  • Рішення повертає значення false для localhost
  • Відповідь не вдається http:example.com

Однак ці URL-адреси насправді є відносними URL-адресами . Це легко перевірити:

  1. Скажімо, створіть кілька папок на вашому localhost webroot a/b/c/
  2. Створіть файл index.html і розмістіть у ньому таке посилання: <a href="localhost">test</a>
  3. Відкрийте сторінку індексу у вашому браузері: http: //localhost/a/b/c/index.html і натисніть на посилання. Ви закінчите на http: // localhost / a / b / c / localhost (а не на http: // localhost )
  4. Те саме відбувається при розміщенні посилання http:example.comу вашому файлі index.html. Ви закінчуєте на http: //localhost/a/b/c/example.com замість http://example.com

4
Ні. Я просто відстежував помилку у своєму проекті і виявив, що це занадто така функція. Веб-сторінка мала URL-адресу, наприклад /redirect?target=http://example.org
BeniBela

@BeniBela, ви можете це виправити, використовуючиfunction isUrlAbsolute(url) { var firstSlash = url.indexOf('/'); var colonDoubleSlash = url.indexOf('://'); return ((firstSlash > 0 && colonDoubleSlash > 0 && colonDoubleSlash < firstSlash) || url.indexOf('//') === 0); }
Себастьян

@BeniBela Ви маєте рацію, це може траплятися в деяких випадках. Для оновлення я оновив код вище. Однак я настійно рекомендую url-кодувати всі параметри запиту, тобто використовувати/redirect?target=http%3A%2F%2Fexample.com
Philipp

Це відповідає на запитання, але насправді не перевіряє, чи є введення абсолютним. Наприклад, "/ aaa / bbb" повертається як "відносний", коли він насправді абсолютний.
N73k

@ N73k насправді я розглядаю ваш приклад "/ aaa / bbb" щодо домену. Тобто, якщо у вас є <img src="/aaa/bbb">на сайтах site1.com та site2.com, обидва зображення різні (тобто відносні). Хоча <img src="//site1.com/aaa/bbb">ідентичний для всіх доменів (що є абсолютним)
Філіпп,

17

Використовуйте регулярний вираз:

if (/^(?:[a-z]+:)?\/\//i.test(url))

це видається найбільш універсальною відповіддю. Не вистачає лише URL-адреси, яка стосується протоколу (наприклад, //cdn.example.com/libary.js)
Geo

Хоча в запитанні згадуються лише http і https, загальне рішення, можливо, також повинно враховувати URL-адресу "mailto:", яка не має косих рисок.
mikebridge

@mikebridge ти хочеш сказати, що mailto:іноді може бути абсолютним або відносним?
Geo

1
@Geo: Ні; він каже, що mailto:це абсолютне, навіть якщо воно не має /персонажів.
Слакс

будь ласка, приєднуйтесь до чату тут chat.stackoverflow.com/rooms/44712/absolute-or-relative-url
Geo

13

Ще більш універсальний RFC-сумісний URI-підхід:

(?:^[a-z][a-z0-9+.-]*:|\/\/) пояснення регулярних виразів

Інші перелічені тут рішення не зможуть отримати посилання типу mailto:evan@nylas.com

RFC 3986 визначає схему як:

scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

3.1. Схема https://tools.ietf.org/html/rfc3986#section-3.1

Незважаючи на те, що URL-адреса, що стосується протоколу, є технічно дійсною згідно з розділом 4.2, Пол Ірланд повернувся в інший бік і вважає це анти-шаблоном. Див. Http://www.paulirish.com/2010/the-protocol-relative-url/

4.2. Відносне посилання http://tools.ietf.org/html/rfc3986#section-4.2

Якщо вам потрібен регулярний вираз без використання URL-адреси, яка стосується протоколу:

^[a-z][a-z0-9+.-]*:

Щоб побачити повний перелік інших типів дійсних випадків краю uri, ознайомтеся зі списком тут: https://en.wikipedia.org/wiki/URI_scheme


3
Це повинно ^виходити за межі групи? Як написано, він збігався б //у вихідній позиції (тому відповідна URL-адреса, як-от #//відповідатиме). Крім того, важливо вказати, що цей регулярний вираз не повинен враховувати регістр, щоб виглядало повне визначення /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.
sethobrien

Я вважаю, що односимвольні схеми слід вважати приводними буквами. Тож я заміню *на +.
Кну

12

Зараз, коли багато служб використовують URL-адресу, що стосується протоколу (наприклад, //cdn.example.com/libary.js ), цей метод безпечніший:

var isAbsolute = new RegExp('^([a-z]+://|//)', 'i');

if (isAbsolute.test(urlString)) {
  // go crazy here
}

1
Для перехоплення URL-адрес, таких як ' HTTP://WWW.GOOGLE.COM ', вам слід скористатися'^([A-Za-z]+://|//)'
Dean Meehan

3
Просто встановіть iпрапор, щоб ігнорувати регістр. Відповідь відредаговано. Дякую.
rgtk

9

Залежно від ваших потреб, я думаю, що більш надійним способом визначити це є використання вбудованого інтерфейсу URL-адреси для побудови пари об’єктів URL-адреси та порівняння початків.

new URL(document.baseURI).origin === new URL(urlToTest, document.baseURI).origin;

Це дозволяє браузеру проаналізувати і зрозуміти все це для вас, не турбуючись про побічні ефекти крайніх випадків.


Це чудове нове доповнення до інших більш набраних качок рішень. Однак цікаво, чому ви не пропонуєте new URL(document.baseURI).origin === new URL(urlToTest,document.baseURI).origin? Чи не буде це більш підходящим для тих випадків, коли веб-сторінка містить символ <base>?
humanityANDpeace

1
@humanityANDpeace Так, гарна ідея! Я оновив відповідь вашими вдосконаленнями.
Бред

5
var external = RegExp('^(https?:)?//');
if(external.test(el)){
    // do something
}

РЕДАГУВАТИ:

За допомогою наступного регулярного виразу ви навіть можете перевірити, переходить посилання на той самий домен або на зовнішній:

var external = RegExp('^((f|ht)tps?:)?//(?!' + location.host + ')');
if(external.test(el)){
    // do something
}

Вам потрібно уникнути .символів, які майже напевно будуть в імені хосту. В іншому випадку foo.example.com також буде відповідати fooXexample.com
Квентін

5

Не використовуйте речі низького рівня, такі як регулярний вираз тощо. Ці речі були вирішені багатьма іншими людьми. Особливо кромкові випадки.

Погляньте на URI.js , він повинен виконати цю роботу: http://medialize.github.io/URI.js/docs.html#is

var uri = new URI("http://example.org/");
uri.is("absolute") === true;

5
Корисно, якщо вам доводилося робити багато маніпуляцій, але здається надмірним використання бібліотеки JS саме для цього.
Еван Донован

4

Ось досить надійне рішення для середовища браузера:

Нехай браузер обробляє все. Не потрібні деякі складні / схильні до помилок регулярні вирази.

const isAbsoluteUrl = (url) => {
  const link = document.createElement('a');
  link.href = url;
  return link.origin + link.pathname + link.search + link.hash === url;
};

2
var adress = 'http://roflmao.com';
if (adress.substr(0,7) == 'http://' || adress.substr(0,8) == 'https://') {
    //
}

так, це правда. Я не використовую регулярний вираз, тому що я смокчу його. У будь-якому разі, чи не буде Http перетворено на http у сучасних браузерах?
OptimusCrime

2

Жодне із згаданих рішень не вирішило redirect_urlхакерство там, де хакер увійшов /\/example.comабо /\\/example.com. Це те, що я придумав, щоб визначити, чи була наша URL-адреса переспрямування відносною:

var isRelative = !redirectUrl.match(/(\:|\/\\*\/)/);  // Don't allow "//" (with optional "\"'s) or ":"

1

Наступна функція буде викликана, коли на гіперпосиланні відбувається подія кліку, тобто тег 'a', якщо тег містить URL-адресу, буде відносним або містить той самий хост, тоді нова сторінка завантажиться на ту саму вкладку браузера. Якщо вона містить іншу URL-адресу, сторінка завантажиться на новій вкладці браузера

jQuery(document).ready(function() {
    $('a').click(function(){

        var a = this;
        var a_href = $(this).attr('href');
        var regex = new RegExp('^(?:[a-z]+:)?//', 'i');     

        if(a.host == location.host || regex.test(a_href) == false){
            a.target = '_self';
        }else{
            a.target = '_blank';
        }
    }); 
});

0

Він не повинен починатися з косої риски або хешу, і він не повинен містити подвійну косу риску, якщо перед нею не стоять знак питання або хеш? Я б не перевірив, що з одним регулярним виразом було б дуже складно відповідати "без подвійної косої риски".

function test(s) {
    return s.charAt(0) != "#"
      && s.charAt(0) != "/"
      && ( s.indexOf("//") == -1 
        || s.indexOf("//") > s.indexOf("#")
        || s.indexOf("//") > s.indexOf("?")
    );
}

було б простіше, зрозуміліше та швидше.


0

Ви можете скористатися блоком try, catch, щоб допомогти в цьому. Замість того, щоб використовувати регулярний вираз, ви можете використовувати інтерфейс URL-адреси на кожному кроці.

isExternalUrl (urlString) {
  try {
    const url = new URL(urlString) // THROW ON MISSING SCHEME

    // DOES THIS URL ORIGINATE FROM THIS WEBSITE?
    if (url.origin !== new URL(document.URL, document.baseURI).origin) {
      return true // IS EXTERNAL URL
    }
  } catch (_e) {
    // THROWS WHEN URL DOES NOT HAVE A SCHEME
    new URL(urlString, document.baseURL) // THROW AN EXCEPTION IF THE URL IS TRULY MALFORMED IN SOME WAY
  }

  return false
}

-1
var isExternalURL = url.toLowerCase().indexOf('http://') === 0 || url.toLowerCase().indexOf('https://') === 0 ;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.