Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions docs/URL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- [УстановитьКодированныеДанныеПользователя](#установитькодированныеданныепользователя)
- [Хост URL](#хост-url)
- [ВидХоста](#видхоста)
- [ЯвляетсяПетлевым](#являетсяпетлевым)
- [КодированныйХост](#кодированныйхост)
- [УстановитьКодированныйХост](#установитькодированныйхост)
- [УстановитьХост](#установитьхост)
Expand Down Expand Up @@ -539,6 +540,50 @@ ASCII, но не заменяет доменное имя на Punycode.

`Строка` из модуля [`ВидыХостаURL`](ВидыХостаURL.md) или `Неопределено` без серверной части.

### ЯвляетсяПетлевым

Проверяет, указывает ли явно заданный хост URL на сетевой интерфейс обратной петли (loopback).
Проверка выполняется без DNS-запросов и обращения к файлу hosts.

```bsl
.ЯвляетсяПетлевым()
```

Метод возвращает `Истина` для:

- IPv4-адресов из `127.0.0.0/8` по
[RFC 1122 §3.2.1.3](https://www.rfc-editor.org/rfc/rfc1122.html#section-3.2.1.3);
- IPv6-адреса `::1` по
[RFC 4291 §2.5.3](https://www.rfc-editor.org/rfc/rfc4291.html#section-2.5.3);
- `localhost`, `localhost.` и имён внутри `.localhost` без учёта регистра по
[RFC 6761 §6.3](https://www.rfc-editor.org/rfc/rfc6761.html#section-6.3).

При распознавании дерева `localhost` процентное кодирование незарезервированных символов не меняет
результат. Например, `local%68ost` распознаётся как `localhost` по правилам
[RFC 3986 §6.2.2.2](https://www.rfc-editor.org/rfc/rfc3986.html#section-6.2.2.2).

IPv6-адрес с отображённым IPv4 по
[RFC 4291 §2.5.5.2](https://www.rfc-editor.org/rfc/rfc4291.html#section-2.5.5.2)
классифицируется по вложенному IPv4-адресу. Например, `::ffff:127.0.0.1` считается петлевым,
а `::ffff:192.0.2.1` нет.

#### Возвращаемое значение

`Булево`: `Истина`, если хост является петлевым.

#### Ошибки

| Код | Условие |
| --- | --- |
| `encoding.invalid` | Процентные байты имени не образуют корректную последовательность UTF-8 |
| `idna.data.invalid` | Встроенные данные IDNA повреждены или несовместимы |

#### Ограничения

Метод классифицирует только текст URL и не является защитой от SSRF. Другое доменное имя может
разрешиться в петлевой или иной специальный IP-адрес, а результат DNS может измениться между проверкой и
подключением.

### КодированныйХост

Возвращает кодированный хост без преобразования в Punycode. Квадратные скобки IP-литерала сохраняются.
Expand Down
46 changes: 46 additions & 0 deletions src/Классы/URL.os
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#Использовать errors
#Использовать ip
#Использовать "../internal"

Перем _Схема; // Строка, Неопределено
Expand Down Expand Up @@ -268,6 +269,51 @@

КонецФункции

// Проверяет, указывает ли хост URL на сетевой интерфейс обратной петли.
// Распознаёт IPv4 из 127.0.0.0/8, IPv6 ::1 и дерево имён localhost без сетевых запросов.
//
// Ссылки:
// RFC 1122 §3.2.1.3 - петлевые IPv4-адреса:
// https://www.rfc-editor.org/rfc/rfc1122.html#section-3.2.1.3
// RFC 4291 §2.5.3 - петлевой IPv6-адрес:
// https://www.rfc-editor.org/rfc/rfc4291.html#section-2.5.3
// RFC 4291 §2.5.5.2 - IPv6-адреса с отображённым IPv4:
// https://www.rfc-editor.org/rfc/rfc4291.html#section-2.5.5.2
// RFC 6761 §6.3 - специальные имена localhost:
// https://www.rfc-editor.org/rfc/rfc6761.html#section-6.3
//
// Возвращаемое значение:
// Булево - Истина, если явно заданный хост является петлевым.
//
// Исключения:
// encoding.invalid - Процентные байты имени не образуют корректный UTF-8.
// idna.data.invalid - Встроенные данные IDNA повреждены или несовместимы.
Функция ЯвляетсяПетлевым() Экспорт

Если _ВидХоста = ВидыХостаURL.IPv4() Тогда
Возврат Новый IPАдрес(_КодированныйХост).ЭтоПетлевой();
КонецЕсли;

Если _ВидХоста = ВидыХостаURL.IPv6() Тогда
ТекстАдреса = Сред(_КодированныйХост, 2, СтрДлина(_КодированныйХост) - 2);
Адрес = Новый IPАдрес(ТекстАдреса);
Возврат Адрес.ЭтоПетлевой();
КонецЕсли;

Если _ВидХоста <> ВидыХостаURL.ЗарегистрированноеИмя() Тогда
Возврат Ложь;
КонецЕсли;

ИмяХоста = НРег(Хост());

Если Прав(ИмяХоста, 1) = "." Тогда
ИмяХоста = Лев(ИмяХоста, СтрДлина(ИмяХоста) - 1);
КонецЕсли;

Возврат ИмяХоста = "localhost" Или Прав(ИмяХоста, 10) = ".localhost";

КонецФункции

// Возвращает кодированный хост по RFC 3986 §3.2.2.
//
// Возвращаемое значение:
Expand Down
130 changes: 130 additions & 0 deletions tests/URL/ПетлевойURL.os
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#Использовать asserts
#Использовать "../../src"
#Использовать "../fixtures"

Перем _АдресСНедопустимымUTF8; // URL

#Область Тесты

&ПараметризованныйТест
&ИсточникЗначение("http://127.0.0.0/", Истина)
&ИсточникЗначение("http://127.0.0.1/", Истина)
&ИсточникЗначение("http://127.255.255.255/", Истина)
&ИсточникЗначение("http://126.255.255.255/", Ложь)
&ИсточникЗначение("http://128.0.0.0/", Ложь)
Процедура ТестДолжен_ОпределитьПетлевойIPv4(СтрокаАдреса, ОжидаетсяПетлевой) Экспорт

// Подготовка
Адрес = Новый URL(СтрокаАдреса);

// Действие
Результат = Адрес.ЯвляетсяПетлевым();

// Проверка
Ожидаем.Что(Результат).Равно(ОжидаетсяПетлевой);

КонецПроцедуры

&ПараметризованныйТест
&ИсточникЗначение("http://[::1]/", Истина)
&ИсточникЗначение("http://[0:0:0:0:0:0:0:1]/", Истина)
&ИсточникЗначение("http://[::2]/", Ложь)
&ИсточникЗначение("http://[::ffff:127.0.0.1]/", Истина)
&ИсточникЗначение("http://[::ffff:127.255.255.255]/", Истина)
&ИсточникЗначение("http://[::ffff:192.0.2.1]/", Ложь)
Процедура ТестДолжен_ОпределитьПетлевойIPv6(СтрокаАдреса, ОжидаетсяПетлевой) Экспорт

// Подготовка
Адрес = Новый URL(СтрокаАдреса);

// Действие
Результат = Адрес.ЯвляетсяПетлевым();

// Проверка
Ожидаем.Что(Результат).Равно(ОжидаетсяПетлевой);

КонецПроцедуры

&ПараметризованныйТест
&ИсточникЗначение("http://localhost/", Истина)
&ИсточникЗначение("http://LOCALHOST/", Истина)
&ИсточникЗначение("http://localhost./", Истина)
&ИсточникЗначение("http://api.localhost/", Истина)
&ИсточникЗначение("http://api.localhost./", Истина)
&ИсточникЗначение("http://local%68ost/", Истина)
&ИсточникЗначение("http://api%2Elocalhost/", Истина)
&ИсточникЗначение("http://example.com/", Ложь)
&ИсточникЗначение("http://notlocalhost/", Ложь)
&ИсточникЗначение("http://localhost.example/", Ложь)
Процедура ТестДолжен_ОпределитьПетлевоеИмя(СтрокаАдреса, ОжидаетсяПетлевой) Экспорт

// Подготовка
Адрес = Новый URL(СтрокаАдреса);

// Действие
Результат = Адрес.ЯвляетсяПетлевым();

// Проверка
Ожидаем.Что(Результат).Равно(ОжидаетсяПетлевой);

КонецПроцедуры

&ПараметризованныйТест
&ИсточникЗначение("path", Ложь)
&ИсточникЗначение("//", Ложь)
&ИсточникЗначение("http://192.0.2.1/", Ложь)
&ИсточникЗначение("http://[2001:db8::1]/", Ложь)
&ИсточникЗначение("x://[v1.example]/", Ложь)
Процедура ТестДолжен_ОтклонитьНепетлевойХост(СтрокаАдреса, ОжидаетсяПетлевой) Экспорт

// Подготовка
Адрес = Новый URL(СтрокаАдреса);

// Действие
Результат = Адрес.ЯвляетсяПетлевым();

// Проверка
Ожидаем.Что(Результат).Равно(ОжидаетсяПетлевой);

КонецПроцедуры

&Тест
Процедура ТестДолжен_СохранитьСостояниеURLПриПроверке() Экспорт

// Подготовка
Адрес = Новый URL("http://user:pass@localhost:8080/path?query#fragment");
ИсходноеПредставление = Адрес.ВСтроку();

// Действие
Адрес.ЯвляетсяПетлевым();

// Проверка
Ожидаем.Что(Адрес.ВСтроку()).Равно(ИсходноеПредставление);

КонецПроцедуры

&Тест
Процедура ТестДолжен_СохранитьОшибкуДекодированияХоста() Экспорт

// Подготовка
_АдресСНедопустимымUTF8 = Новый URL("http://%FF/");

// Действие и Проверка
ПомощникТестированияURL.ПроверитьКодОшибки(
Новый Действие(ЭтотОбъект, "ПроверитьАдресСНедопустимымUTF8"),
"encoding.invalid"
);

КонецПроцедуры

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

Процедура ПроверитьАдресСНедопустимымUTF8() Экспорт

_АдресСНедопустимымUTF8.ЯвляетсяПетлевым();

КонецПроцедуры

#КонецОбласти
Loading