From a8e0b50c09781bbe6c1e2f8576fb0bffcf8fbfc4 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Tue, 8 Sep 2026 02:49:10 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=80=D0=B0=D0=B7=D0=B1=D0=BE=D1=80=20URL=20?= =?UTF-8?q?=D1=81=D0=BE=20=D1=81=D1=85=D0=B5=D0=BC=D0=BE=D0=B9=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=83=D0=BC=D0=BE=D0=BB=D1=87=D0=B0=D0=BD=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- docs/README.md | 3 +- ...210\320\270\320\261\320\276\320\272URL.md" | 1 + ...260\320\267\320\261\320\276\321\200URL.md" | 96 +++++- ...276\321\200\321\211\320\270\320\272URL.os" | 295 ++++++++++++++++++ ...276\321\202\320\265\320\272\320\270URL.os" | 16 +- ...210\320\270\320\261\320\276\320\272URL.os" | 10 + ...260\320\267\320\261\320\276\321\200URL.os" | 56 +++- ...21\207\320\260\320\275\320\270\321\216.os" | 239 ++++++++++++++ ...276\321\202\320\265\320\272\320\270URL.os" | 4 + ...21\210\320\270\320\261\320\276\320\272.os" | 1 + 11 files changed, 704 insertions(+), 19 deletions(-) create mode 100644 "src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\260\320\267\320\261\320\276\321\200\321\211\320\270\320\272URL.os" create mode 100644 "tests/URL/\320\240\320\260\320\267\320\261\320\276\321\200\320\241\320\276\320\241\321\205\320\265\320\274\320\276\320\271\320\237\320\276\320\243\320\274\320\276\320\273\321\207\320\260\320\275\320\270\321\216.os" diff --git a/README.md b/README.md index cda417c..41baeac 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ https://%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80.%D1%80%D1%84/%D0%BF%D1%83%D1%82%D1% - [Справочник API](docs/README.md) - [URL](docs/URL.md) - единая изменяемая абсолютная или относительная URI-ссылка -- [РазборURL](docs/РазборURL.md) - разбор без передачи исключений +- [РазборURL](docs/РазборURL.md) - разбор URI-ссылок и серверных адресов - [ВидыХостаURL](docs/ВидыХостаURL.md) - классификация хоста - [ПараметрыЗапросаURL](docs/ПараметрыЗапросаURL.md) - коллекция параметров запроса - [ФорматыПараметровЗапросаURL](docs/ФорматыПараметровЗапросаURL.md) - форматы параметров запроса diff --git a/docs/README.md b/docs/README.md index 7b6e4a9..a2bbb6c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,7 @@ - [Установка](../README.md#установка) и [быстрый старт](../README.md#быстрый-старт) - [Работа с относительными ссылками](../README.md#относительные-ссылки) +- [Разбор серверного адреса со схемой по умолчанию](РазборURL.md#разобратьсосхемойпоумолчанию) - [Разбор ввода без передачи исключений](РазборURL.md) - Изменение [пути](URL.md#путь-url), [запроса](URL.md#строка-запроса-url) и [фрагмента](URL.md#фрагмент-url) - [Работа с параметрами запроса](ПараметрыЗапросаURL.md) @@ -32,6 +33,6 @@ - [КодированиеURL](КодированиеURL.md) - кодирование компонентов и параметров формы - [КодыОшибокURL](КодыОшибокURL.md) - машинные коды ошибок -- [РазборURL](РазборURL.md) - разбор с результатом `URL` или `Неопределено` +- [РазборURL](РазборURL.md) - разбор URI-ссылок и серверных адресов - [ВидыХостаURL](ВидыХостаURL.md) - значения классификации хоста - [ФорматыПараметровЗапросаURL](ФорматыПараметровЗапросаURL.md) - форматы разбора и сериализации параметров diff --git "a/docs/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272URL.md" "b/docs/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272URL.md" index 40907ed..b5cdd0d 100644 --- "a/docs/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272URL.md" +++ "b/docs/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272URL.md" @@ -54,6 +54,7 @@ | --- | --- | --- | | `НедопустимыйСинтаксис()` | `url.invalid_syntax` | Нарушена общая грамматика URI | | `НедопустимаяСхема()` | `url.invalid_scheme` | Нарушена грамматика схемы | +| `ТребуетсяСерверныйАдрес()` | `url.server_address_required` | Требуется серверный адрес | | `НедопустимоеПроцентноеКодирование()` | `url.invalid_percent_encoding` | Нарушена форма `%HH` | | `НедопустимаяСервернаяЧасть()` | `url.invalid_authority` | Нарушена структура серверной части | | `НедопустимыйХост()` | `url.invalid_host` | Хост не соответствует допустимой форме | diff --git "a/docs/\320\240\320\260\320\267\320\261\320\276\321\200URL.md" "b/docs/\320\240\320\260\320\267\320\261\320\276\321\200URL.md" index dc1d74c..261e045 100644 --- "a/docs/\320\240\320\260\320\267\320\261\320\276\321\200URL.md" +++ "b/docs/\320\240\320\260\320\267\320\261\320\276\321\200URL.md" @@ -1,16 +1,19 @@ # РазборURL -Публичный модуль разбирает URI-ссылки без передачи исключений вызывающему коду. Использует ту же -общую грамматику, что и конструктор [`URL`](URL.md), по +Публичный модуль разбирает URI-ссылки и серверные адреса. Использует ту же общую грамматику, что и конструктор +[`URL`](URL.md), по [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986.html) и [RFC 3987](https://www.rfc-editor.org/rfc/rfc3987.html). -Успешный разбор не проверяет ограничения конкретной схемы, доверие к узлу или его сетевую доступность. -Для `http`, `https`, `ws`, `wss` и `ftp` дополнительно вызывайте -[`URL.СоответствуетСхеме()`](URL.md#соответствуетсхеме). +`ПопытатьсяРазобрать()` и `ЭтоКорректнаяСсылка()` не проверяют ограничения конкретной схемы. Для `http`, `https`, +`ws`, `wss` и `ftp` после них дополнительно вызывайте [`URL.СоответствуетСхеме()`](URL.md#соответствуетсхеме). +`РазобратьСоСхемойПоУмолчанию()` и `ПопытатьсяРазобратьСоСхемойПоУмолчанию()` выполняют проверку автоматически. +Ни одна операция модуля не проверяет доверие к узлу или его сетевую доступность. ## Оглавление - [ПопытатьсяРазобрать](#попытатьсяразобрать) +- [РазобратьСоСхемойПоУмолчанию](#разобратьсосхемойпоумолчанию) +- [ПопытатьсяРазобратьСоСхемойПоУмолчанию](#попытатьсяразобратьсосхемойпоумолчанию) - [ЭтоКорректнаяСсылка](#этокорректнаяссылка) - [Связанные сущности](#связанные-сущности) @@ -44,6 +47,89 @@ Модуль перехватывает любое исключение конструктора, поэтому причину следует определять по параметру `Ошибка`. +## РазобратьСоСхемойПоУмолчанию + +```bsl +РазборURL.РазобратьСоСхемойПоУмолчанию(<Значение>, <СхемаПоУмолчанию>) +``` + +Разбирает серверный адрес с необязательной схемой. Если схема в адресе отсутствует, добавляет переданную схему. +Явно заданную схему сохраняет. Операция предназначена для серверных адресов, а не для произвольных относительных +URI-ссылок. + +### Параметры + +| Параметр | Тип | Описание | +| --- | --- | --- | +| Значение | `Строка` | Серверный адрес или абсолютный иерархический URL | +| СхемаПоУмолчанию | `Строка` | Схема без завершающего двоеточия, применяемая при отсутствии явной | + +### Возвращаемое значение + +Новый независимый [`URL`](URL.md) с определённой серверной частью и непустым хостом. + +### Исключения + +| Код | Условие | +| --- | --- | +| `argument.invalid_type` | Значение или схема по умолчанию имеют тип, отличный от `Строка` | +| `argument.required` | Значение или схема по умолчанию не заполнены | +| `url.invalid_scheme` | Схема не соответствует грамматике RFC 3986 | +| `url.server_address_required` | Значение не имеет поддерживаемой формы серверного адреса | +| Коды разбора URI | Сформированный URL нарушает общую грамматику | +| `url.invalid_for_scheme` | URL нарушает ограничения известной сетевой схемы | + +Грамматика схемы определена в +[RFC 3986, раздел 3.1](https://www.rfc-editor.org/rfc/rfc3986.html#section-3.1). + +Для `url.server_address_required` исходная строка не включается в текст и данные ошибки. Это защищает пароль из +данных пользователя от случайного вывода в журнал. + +### Пример + +```bsl +#Использовать url + +Адрес = РазборURL.РазобратьСоСхемойПоУмолчанию("hub.example/private", "https"); +Сообщить(Адрес.ВСтроку()); // https://hub.example/private +``` + +Сетевые ссылки вида `//hub.example/private` и абсолютные иерархические URL также поддерживаются. Согласно +[RFC 3986, раздел 4.2](https://www.rfc-editor.org/rfc/rfc3986.html#section-4.2), строка +`hub.example/private` сама по себе является относительным путём. Именно поэтому для неё нужна отдельная операция. + +## ПопытатьсяРазобратьСоСхемойПоУмолчанию + +```bsl +РазборURL.ПопытатьсяРазобратьСоСхемойПоУмолчанию(<Значение>, <СхемаПоУмолчанию>, <Ошибка> = Неопределено) +``` + +Разбирает те же формы серверного адреса, что и `РазобратьСоСхемойПоУмолчанию()`, но не выбрасывает исключения. + +### Параметры + +| Параметр | Тип | Описание | +| --- | --- | --- | +| Значение | `Строка` | Серверный адрес или абсолютный иерархический URL | +| СхемаПоУмолчанию | `Строка` | Схема без завершающего двоеточия, применяемая при отсутствии явной | +| Ошибка | `ИнформацияОбОшибке`, `Неопределено` | Выходной параметр с ошибкой | + +### Возвращаемое значение + +Новый независимый [`URL`](URL.md) или `Неопределено` при ошибке. При успехе параметр `Ошибка` получает значение +`Неопределено`. При ошибке он содержит её причину с тем же предметным кодом, который выбрасывает +`РазобратьСоСхемойПоУмолчанию()`. + +### Пример + +```bsl +#Использовать url + +Ошибка = Неопределено; +Адрес = РазборURL.ПопытатьсяРазобратьСоСхемойПоУмолчанию("hub.example/private", "https", Ошибка); +``` + + ## ЭтоКорректнаяСсылка ```bsl diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\260\320\267\320\261\320\276\321\200\321\211\320\270\320\272URL.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\260\320\267\320\261\320\276\321\200\321\211\320\270\320\272URL.os" new file mode 100644 index 0000000..196ff1c --- /dev/null +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\260\320\267\320\261\320\276\321\200\321\211\320\270\320\272URL.os" @@ -0,0 +1,295 @@ +#Использовать errors + +Перем _ПроверяющийСинтаксисURI; // ПроверяющийСинтаксисURI +Перем _ПроверяющийСхемыURL; // ПроверяющийСхемыURL + +#Область ОбработчикиСобытий + +// Создаёт разборщик URL. +// +// Параметры: +// ПроверяющийСинтаксисURI - ПроверяющийСинтаксисURI - Проверяющий синтаксис URI. +// ПроверяющийСхемыURL - ПроверяющийСхемыURL - Проверяющий ограничения схем URL. +Процедура ПриСозданииОбъекта(ПроверяющийСинтаксисURI, ПроверяющийСхемыURL) + + _ПроверяющийСинтаксисURI = ПроверяющийСинтаксисURI; + _ПроверяющийСхемыURL = ПроверяющийСхемыURL; + +КонецПроцедуры + +#КонецОбласти + +#Область ПрограммныйИнтерфейс + +// Разбирает серверный адрес с необязательной схемой. +// +// Параметры: +// Значение - Строка - Серверный адрес или абсолютный иерархический URL. +// СхемаПоУмолчанию - Строка - Схема без завершающего двоеточия. +// +// Возвращаемое значение: +// URL - Новый независимый URL. +// +// Исключения: +// argument.invalid_type - Значение или схема по умолчанию имеют тип, отличный от Строка. +// argument.required - Значение или схема по умолчанию не заполнены. +// url.invalid_syntax - Сформированный URL нарушает общую грамматику URI или IRI. +// url.invalid_scheme - Схема не соответствует RFC 3986. +// url.server_address_required - Значение не имеет поддерживаемой формы серверного адреса. +// url.invalid_percent_encoding - Значение содержит некорректную последовательность %HH. +// url.invalid_authority - Сформированный URL содержит некорректную серверную часть. +// url.invalid_host - Сформированный URL содержит некорректный хост. +// url.invalid_for_scheme - URL нарушает ограничения известной схемы. +Функция РазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию) Экспорт + + ПроверкаАргументов.ПроверитьТип("Значение", Значение, Тип("Строка")); + ПроверкаАргументов.ПроверитьТип("СхемаПоУмолчанию", СхемаПоУмолчанию, Тип("Строка")); + ПроверкаАргументов.ПроверитьЗаполненность("Значение", Значение); + ПроверкаАргументов.ПроверитьЗаполненность("СхемаПоУмолчанию", СхемаПоУмолчанию); + + Если Не _ПроверяющийСинтаксисURI.ЭтоДопустимаяСхема(СхемаПоУмолчанию) Тогда + ВызватьНедопустимуюСхему(); + КонецЕсли; + + Если ЕстьПробельныйИлиУправляющийСимволПоКраю(Значение) Тогда + ВызватьТребованиеСерверногоАдреса("пробельный или управляющий символ по краю значения"); + КонецЕсли; + + Если Лев(Значение, 2) = "//" Тогда + ПредставлениеURL = СхемаПоУмолчанию + ":" + Значение; + Иначе + ПозицияПервогоРазделителя = ПозицияПервогоИз(Значение, ":/?#"); + ЭтоАбсолютныйURL = ПозицияПервогоРазделителя > 0 + И Сред(Значение, ПозицияПервогоРазделителя, 1) = ":" + И Сред(Значение, ПозицияПервогоРазделителя + 1, 2) = "//"; + + Если ЭтоАбсолютныйURL Тогда + СхемаЗначения = Лев(Значение, ПозицияПервогоРазделителя - 1); + Если Не _ПроверяющийСинтаксисURI.ЭтоДопустимаяСхема(СхемаЗначения) Тогда + ВызватьНедопустимуюСхему(); + КонецЕсли; + + ПредставлениеURL = Значение; + Иначе + ПроверитьФормуСерверногоАдреса(Значение); + ПредставлениеURL = СхемаПоУмолчанию + "://" + Значение; + КонецЕсли; + КонецЕсли; + + Адрес = Новый URL(ПредставлениеURL); + + Если Лев(Значение, 2) = "//" И Адрес.ТекстПорта() = "" Тогда + ВызватьТребованиеСерверногоАдреса("не заполнен порт"); + КонецЕсли; + + Если Адрес.КодированнаяСервернаяЧасть() = Неопределено Или Адрес.КодированныйХост() = "" Тогда + ВызватьТребованиеСерверногоАдреса("не заполнен хост"); + КонецЕсли; + + _ПроверяющийСхемыURL.Проверить(Адрес); + + Возврат Адрес; + +КонецФункции + +// Разбирает серверный адрес с необязательной схемой без исключений. +// +// Параметры: +// Значение - Строка - Серверный адрес или абсолютный иерархический URL. +// СхемаПоУмолчанию - Строка - Схема без завершающего двоеточия. +// Ошибка - ИнформацияОбОшибке, Неопределено - Выходной параметр с информацией об ошибке разбора. +// +// Возвращаемое значение: +// URL, Неопределено - Разобранный URL или Неопределено при ошибке. +Функция ПопытатьсяРазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию, Ошибка = Неопределено) Экспорт + + Ошибка = Неопределено; + + Попытка + Возврат РазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию); + Исключение + Ошибка = ИнформацияОбОшибке(); + Возврат Неопределено; + КонецПопытки; + +КонецФункции + +// Разбирает URL как URI-ссылку без исключений. +// +// Параметры: +// Значение - Строка - Разбираемая URI-ссылка. +// Ошибка - ИнформацияОбОшибке, Неопределено - Выходной параметр с информацией об ошибке разбора. +// +// Возвращаемое значение: +// URL, Неопределено - Разобранная ссылка или Неопределено при ошибке. +Функция ПопытатьсяРазобрать(Значение, Ошибка = Неопределено) Экспорт + + Ошибка = Неопределено; + + Попытка + ПроверкаАргументов.ПроверитьТип("Значение", Значение, Тип("Строка")); + Возврат Новый URL(Значение); + Исключение + Ошибка = ИнформацияОбОшибке(); + Возврат Неопределено; + КонецПопытки; + +КонецФункции + +// Проверяет, является ли значение корректной URI-ссылкой. +// +// Параметры: +// Значение - Строка - Проверяемая URI-ссылка. +// +// Возвращаемое значение: +// Булево - Истина, если значение успешно разобрано. +Функция ЭтоКорректнаяСсылка(Значение) Экспорт + + Возврат ПопытатьсяРазобрать(Значение) <> Неопределено; + +КонецФункции + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +Процедура ПроверитьФормуСерверногоАдреса(Значение) + + ПозицияРазделителя = ПозицияПервогоИз(Значение, "/?#"); + Если ПозицияРазделителя = 0 Тогда + СерверныйАдрес = Значение; + Иначе + СерверныйАдрес = Лев(Значение, ПозицияРазделителя - 1); + КонецЕсли; + + Если СерверныйАдрес = "" Тогда + ВызватьТребованиеСерверногоАдреса("не заполнен серверный адрес"); + КонецЕсли; + + Если СтрНайти(СерверныйАдрес, "@") > 0 Тогда + ВызватьТребованиеСерверногоАдреса("данные пользователя требуют маркера серверной части"); + КонецЕсли; + + Если Лев(СерверныйАдрес, 1) = "[" Тогда + ПроверитьЛитералIP(СерверныйАдрес); + Иначе + ПроверитьИмяХостаИПорт(СерверныйАдрес); + КонецЕсли; + +КонецПроцедуры + +Процедура ПроверитьЛитералIP(СерверныйАдрес) + + ПозицияЗакрывающейСкобки = СтрНайти(СерверныйАдрес, "]"); + Если ПозицияЗакрывающейСкобки <= 2 Тогда + ВызватьТребованиеСерверногоАдреса("некорректный литерал IP"); + КонецЕсли; + + Остаток = Сред(СерверныйАдрес, ПозицияЗакрывающейСкобки + 1); + Если Остаток = "" Тогда + Возврат; + КонецЕсли; + + Если Лев(Остаток, 1) <> ":" Или СтрДлина(Остаток) = 1 + Или Не _ПроверяющийСинтаксисURI.ТолькоЦифрыASCII(Сред(Остаток, 2)) Тогда + ВызватьТребованиеСерверногоАдреса("некорректный порт"); + КонецЕсли; + +КонецПроцедуры + +Процедура ПроверитьИмяХостаИПорт(СерверныйАдрес) + + Если СтрНайти(СерверныйАдрес, "[") > 0 Или СтрНайти(СерверныйАдрес, "]") > 0 Тогда + ВызватьТребованиеСерверногоАдреса("некорректный литерал IP"); + КонецЕсли; + + КоличествоДвоеточий = КоличествоСимволов(СерверныйАдрес, ":"); + Если КоличествоДвоеточий > 1 Тогда + ВызватьТребованиеСерверногоАдреса("литерал IPv6 должен быть заключен в квадратные скобки"); + КонецЕсли; + + Если КоличествоДвоеточий = 0 Тогда + Возврат; + КонецЕсли; + + ПозицияДвоеточия = СтрНайти(СерверныйАдрес, ":"); + Хост = Лев(СерверныйАдрес, ПозицияДвоеточия - 1); + Порт = Сред(СерверныйАдрес, ПозицияДвоеточия + 1); + + Если Хост = "" Или Порт = "" Или Не _ПроверяющийСинтаксисURI.ТолькоЦифрыASCII(Порт) Тогда + ВызватьТребованиеСерверногоАдреса("некорректный хост или порт"); + КонецЕсли; + +КонецПроцедуры + +Функция ПозицияПервогоИз(Значение, ИскомыеСимволы) + + Результат = 0; + Для Индекс = 1 По СтрДлина(ИскомыеСимволы) Цикл + Позиция = СтрНайти(Значение, Сред(ИскомыеСимволы, Индекс, 1)); + Если Позиция > 0 И (Результат = 0 Или Позиция < Результат) Тогда + Результат = Позиция; + КонецЕсли; + КонецЦикла; + + Возврат Результат; + +КонецФункции + +Функция КоличествоСимволов(Значение, ИскомыйСимвол) + + Результат = 0; + Для Индекс = 1 По СтрДлина(Значение) Цикл + Если Сред(Значение, Индекс, 1) = ИскомыйСимвол Тогда + Результат = Результат + 1; + КонецЕсли; + КонецЦикла; + + Возврат Результат; + +КонецФункции + +Функция ЕстьПробельныйИлиУправляющийСимволПоКраю(Значение) + + КодПервого = КодСимвола(Лев(Значение, 1)); + КодПоследнего = КодСимвола(Прав(Значение, 1)); + + Возврат ЭтоПробельныйИлиУправляющийСимвол(КодПервого) + Или ЭтоПробельныйИлиУправляющийСимвол(КодПоследнего); + +КонецФункции + +Функция ЭтоПробельныйИлиУправляющийСимвол(Код) + + Возврат Код <= 32 // U+0000..U+0020: управляющие C0 и пробел + Или Код >= 127 И Код <= 160 // U+007F..U+00A0: DEL, управляющие C1, NBSP + Или Код = 5760 // U+1680: пробел огама + Или Код >= 8192 И Код <= 8202 // U+2000..U+200A: типографские пробелы + Или Код = 8232 // U+2028: разделитель строк + Или Код = 8233 // U+2029: разделитель абзацев + Или Код = 8239 // U+202F: узкий неразрывный пробел + Или Код = 8287 // U+205F: средний математический пробел + Или Код = 12288; // U+3000: идеографический пробел + +КонецФункции + +Процедура ВызватьНедопустимуюСхему() + + ВызватьИсключение ФабрикаОшибок.Создать( + КодыОшибокURL.НедопустимаяСхема(), + "Недопустимая схема URL" + ); + +КонецПроцедуры + +Процедура ВызватьТребованиеСерверногоАдреса(Причина) + + ВызватьИсключение ФабрикаОшибок.Создать( + КодыОшибокURL.ТребуетсяСерверныйАдрес(), + "Требуется серверный адрес URL: " + Причина, + Новый Структура("Причина", Причина) + ); + +КонецПроцедуры + +#КонецОбласти diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\320\265\321\200\320\262\320\270\321\201\321\213\320\221\320\270\320\261\320\273\320\270\320\276\321\202\320\265\320\272\320\270URL.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\320\265\321\200\320\262\320\270\321\201\321\213\320\221\320\270\320\261\320\273\320\270\320\276\321\202\320\265\320\272\320\270URL.os" index 0bcf3ec..9cafc27 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\320\265\321\200\320\262\320\270\321\201\321\213\320\221\320\270\320\261\320\273\320\270\320\276\321\202\320\265\320\272\320\270URL.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\320\265\321\200\320\262\320\270\321\201\321\213\320\221\320\270\320\261\320\273\320\270\320\276\321\202\320\265\320\272\320\270URL.os" @@ -54,6 +54,16 @@ КонецФункции +// Возвращает разборщик URL. +// +// Возвращаемое значение: +// РазборщикURL - Сохранённый экземпляр разборщика. +Функция РазборщикURL() Экспорт + + Возврат ПолучитьИлиСоздатьСервис("РазборщикURL"); + +КонецФункции + // Возвращает проверяющего синтаксис URI. // // Возвращаемое значение: @@ -238,6 +248,9 @@ Параметры.Добавить(ПроверяющийСинтаксисURI()); Параметры.Добавить(КодировщикКомпонентаIRI()); Параметры.Добавить(КлассификаторХостаURI()); + ИначеЕсли ИмяКласса = "РазборщикURL" Тогда + Параметры.Добавить(ПроверяющийСинтаксисURI()); + Параметры.Добавить(ПроверяющийСхемыURL()); ИначеЕсли ИмяКласса = "РазрешительURI" Тогда Параметры.Добавить(СборщикURL()); ИначеЕсли ИмяКласса = "РелятивизаторURI" Тогда @@ -280,7 +293,8 @@ Процедура ИнициализироватьПулыСервисов() ИменаСервисов = "КодировщикКомпонентовURI, ПреобразовательКодовыхТочекUnicode, НормализаторСтрокUnicode, " - + "ПреобразовательДоменов, РазборщикКомпонентовURL, ПроверяющийСинтаксисURI, КлассификаторХостаURI, " + + "ПреобразовательДоменов, РазборщикКомпонентовURL, РазборщикURL, " + + "ПроверяющийСинтаксисURI, КлассификаторХостаURI, " + "КодировщикPunycode, КодировщикКомпонентаIRI, НормализаторURI, ОпределительСтандартногоПортаURI, " + "ПарсерIRI, ПроверяющийСхемыURL, РазрешительURI, РелятивизаторURI, СборщикURL, СравнивательURL"; diff --git "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272URL.os" "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272URL.os" index d8f62f7..2f122aa 100644 --- "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272URL.os" +++ "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272URL.os" @@ -20,6 +20,16 @@ КонецФункции +// Возвращает код ошибки, возникающей при отсутствии поддерживаемой формы серверного адреса. +// +// Возвращаемое значение: +// Строка - Код ошибки обязательного серверного адреса URL. +Функция ТребуетсяСерверныйАдрес() Экспорт + + Возврат "url.server_address_required"; + +КонецФункции + // Возвращает код ошибки URI с недопустимой процентной последовательностью. // // Возвращаемое значение: diff --git "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\267\320\261\320\276\321\200URL.os" "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\267\320\261\320\276\321\200URL.os" index 36afd6e..c2b40b0 100644 --- "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\267\320\261\320\276\321\200URL.os" +++ "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\267\320\261\320\276\321\200URL.os" @@ -1,7 +1,49 @@ -#Использовать errors +#Использовать "../internal" #Область ПрограммныйИнтерфейс +// Разбирает серверный адрес с необязательной схемой. +// +// Параметры: +// Значение - Строка - Серверный адрес или абсолютный иерархический URL. +// СхемаПоУмолчанию - Строка - Схема без завершающего двоеточия. +// +// Возвращаемое значение: +// URL - Новый независимый URL. +// +// Исключения: +// argument.invalid_type - Значение или схема по умолчанию имеют тип, отличный от Строка. +// argument.required - Значение или схема по умолчанию не заполнены. +// url.invalid_syntax - Сформированный URL нарушает общую грамматику URI или IRI. +// url.invalid_scheme - Схема не соответствует RFC 3986. +// url.server_address_required - Значение не имеет поддерживаемой формы серверного адреса. +// url.invalid_percent_encoding - Значение содержит некорректную последовательность %HH. +// url.invalid_authority - Сформированный URL содержит некорректную серверную часть. +// url.invalid_host - Сформированный URL содержит некорректный хост. +// url.invalid_for_scheme - URL нарушает ограничения известной схемы. +Функция РазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию) Экспорт + + Возврат СервисыБиблиотекиURL.РазборщикURL() + .РазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию); + +КонецФункции + +// Разбирает серверный адрес с необязательной схемой без исключений. +// +// Параметры: +// Значение - Строка - Серверный адрес или абсолютный иерархический URL. +// СхемаПоУмолчанию - Строка - Схема без завершающего двоеточия. +// Ошибка - ИнформацияОбОшибке, Неопределено - Выходной параметр с информацией об ошибке разбора. +// +// Возвращаемое значение: +// URL, Неопределено - Разобранный URL или Неопределено при ошибке. +Функция ПопытатьсяРазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию, Ошибка = Неопределено) Экспорт + + Возврат СервисыБиблиотекиURL.РазборщикURL() + .ПопытатьсяРазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию, Ошибка); + +КонецФункции + // Разбирает URL как URI-ссылку без исключений. // // Параметры: @@ -12,15 +54,7 @@ // URL, Неопределено - Разобранная ссылка или Неопределено при ошибке. Функция ПопытатьсяРазобрать(Значение, Ошибка = Неопределено) Экспорт - Ошибка = Неопределено; - - Попытка - ПроверкаАргументов.ПроверитьТип("Значение", Значение, Тип("Строка")); - Возврат Новый URL(Значение); - Исключение - Ошибка = ИнформацияОбОшибке(); - Возврат Неопределено; - КонецПопытки; + Возврат СервисыБиблиотекиURL.РазборщикURL().ПопытатьсяРазобрать(Значение, Ошибка); КонецФункции @@ -33,7 +67,7 @@ // Булево - Истина, если значение успешно разобрано. Функция ЭтоКорректнаяСсылка(Значение) Экспорт - Возврат ПопытатьсяРазобрать(Значение) <> Неопределено; + Возврат СервисыБиблиотекиURL.РазборщикURL().ЭтоКорректнаяСсылка(Значение); КонецФункции diff --git "a/tests/URL/\320\240\320\260\320\267\320\261\320\276\321\200\320\241\320\276\320\241\321\205\320\265\320\274\320\276\320\271\320\237\320\276\320\243\320\274\320\276\320\273\321\207\320\260\320\275\320\270\321\216.os" "b/tests/URL/\320\240\320\260\320\267\320\261\320\276\321\200\320\241\320\276\320\241\321\205\320\265\320\274\320\276\320\271\320\237\320\276\320\243\320\274\320\276\320\273\321\207\320\260\320\275\320\270\321\216.os" new file mode 100644 index 0000000..90ac01d --- /dev/null +++ "b/tests/URL/\320\240\320\260\320\267\320\261\320\276\321\200\320\241\320\276\320\241\321\205\320\265\320\274\320\276\320\271\320\237\320\276\320\243\320\274\320\276\320\273\321\207\320\260\320\275\320\270\321\216.os" @@ -0,0 +1,239 @@ +#Использовать asserts +#Использовать errors +#Использовать "../../src" +#Использовать "../fixtures" + +Перем _ЗначениеДляРазбора; +Перем _СхемаПоУмолчаниюДляРазбора; + +#Область Тесты + +&Тест +Процедура ТестДолжен_ДобавитьПодразумеваемуюСхемуКСерверномуАдресу() Экспорт + + // Подготовка + Значение = "hub.example/private"; + + // Действие + Адрес = РазборURL.РазобратьСоСхемойПоУмолчанию(Значение, "https"); + + // Проверка + Ожидаем.Что(Адрес.ВСтроку()).Равно("https://hub.example/private"); + +КонецПроцедуры + +&ПараметризованныйТест +&ИсточникЗначение("hub.example:8080/private", "https", "https://hub.example:8080/private") +&ИсточникЗначение("//hub.example/private", "https", "https://hub.example/private") +&ИсточникЗначение("http://hub.example/private", "https", "http://hub.example/private") +&ИсточникЗначение("https://hub.example/private", "http", "https://hub.example/private") +&ИсточникЗначение("[::1]:8080/private", "http", "http://[::1]:8080/private") +&ИсточникЗначение("192.0.2.1/private", "http", "http://192.0.2.1/private") +&ИсточникЗначение("пример.рф/путь", "https", "https://пример.рф/путь") +&ИсточникЗначение("hub.example/a%2Fb?q=1#part", "https", "https://hub.example/a%2Fb?q=1#part") +&ИсточникЗначение("hub.example/p:a@b?q=a:b@c#f:a@b", "https", "https://hub.example/p:a@b?q=a:b@c#f:a@b") +&ИсточникЗначение("//user:password@hub.example/private", "https", "https://user:password@hub.example/private") +&ИсточникЗначение("foo://hub.example/private", "https", "foo://hub.example/private") +&ИсточникЗначение("hub.example/private", "foo", "foo://hub.example/private") +Процедура ТестДолжен_РазобратьПоддерживаемыйСерверныйАдрес(Значение, СхемаПоУмолчанию, ОжидаемыйURL) Экспорт + + // Подготовка + // Параметры переданы источником теста. + + // Действие + Адрес = РазборURL.РазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию); + + // Проверка + Ожидаем.Что(Адрес.ВСтроку()).Равно(ОжидаемыйURL); + +КонецПроцедуры + +&ПараметризованныйТест +&ИсточникЗначение("", "https", "argument.required") +&ИсточникЗначение("hub.example", "", "argument.required") +&ИсточникЗначение("/private", "https", "url.server_address_required") +&ИсточникЗначение("?q=1", "https", "url.server_address_required") +&ИсточникЗначение("#part", "https", "url.server_address_required") +&ИсточникЗначение("mailto:user@example.com", "https", "url.server_address_required") +&ИсточникЗначение("urn:isbn:1", "https", "url.server_address_required") +&ИсточникЗначение("http:/private", "https", "url.server_address_required") +&ИсточникЗначение("user:password@hub.example", "https", "url.server_address_required") +&ИсточникЗначение("hub.example:", "https", "url.server_address_required") +&ИсточникЗначение("hub.example:abc/private", "https", "url.server_address_required") +&ИсточникЗначение("2001:db8::1/private", "https", "url.server_address_required") +&ИсточникЗначение("//", "https", "url.server_address_required") +&ИсточникЗначение("//user:secret@", "https", "url.server_address_required") +&ИсточникЗначение("//hub.example:", "https", "url.server_address_required") +&ИсточникЗначение("//[::1]:", "https", "url.server_address_required") +&ИсточникЗначение("http://", "https", "url.server_address_required") +&ИсточникЗначение("http://user:secret@", "https", "url.server_address_required") +&ИсточникЗначение("1http://hub.example", "https", "url.invalid_scheme") +&ИсточникЗначение("hub.example/private", "1https", "url.invalid_scheme") +&ИсточникЗначение("hub.example/%", "https", "url.invalid_percent_encoding") +&ИсточникЗначение("hub.example:65536/private", "https", "url.invalid_for_scheme") +&ИсточникЗначение(" hub.example", "https", "url.server_address_required") +&ИсточникЗначение("hub.example ", "https", "url.server_address_required") +&ИсточникЗначение("http://hub.example/a b", "https", "url.invalid_syntax") +&ИсточникЗначение("http://hub.example:named", "https", "url.invalid_authority") +&ИсточникЗначение("http://[not-an-ip]/", "https", "url.invalid_host") +Процедура ТестДолжен_ОтклонитьНеподдерживаемыйАдрес(Значение, СхемаПоУмолчанию, ОжидаемыйКод) Экспорт + + // Подготовка + _ЗначениеДляРазбора = Значение; + _СхемаПоУмолчаниюДляРазбора = СхемаПоУмолчанию; + Действие = Новый Действие(ЭтотОбъект, "РазобратьАдрес"); + + // Действие и Проверка + ПомощникТестированияURL.ПроверитьКодОшибки(Действие, ОжидаемыйКод); + +КонецПроцедуры + +&ПараметризованныйТест +&ИсточникЗначение(133) +&ИсточникЗначение(160) +&ИсточникЗначение(8232) +&ИсточникЗначение(12288) +Процедура ТестДолжен_ОтклонитьUnicodeПробелПоКраю(КодСимволаПробела) Экспорт + + // Подготовка + _ЗначениеДляРазбора = "hub.example/private" + Символ(КодСимволаПробела); + _СхемаПоУмолчаниюДляРазбора = "https"; + + // Действие и Проверка + ПомощникТестированияURL.ПроверитьКодОшибки( + Новый Действие(ЭтотОбъект, "РазобратьАдрес"), + "url.server_address_required" + ); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ОтклонитьНедопустимыйТипЗначения() Экспорт + + // Подготовка + _ЗначениеДляРазбора = 42; + _СхемаПоУмолчаниюДляРазбора = "https"; + + // Действие и Проверка + ПомощникТестированияURL.ПроверитьКодОшибки( + Новый Действие(ЭтотОбъект, "РазобратьАдрес"), + "argument.invalid_type" + ); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ОтклонитьНедопустимыйТипСхемы() Экспорт + + // Подготовка + _ЗначениеДляРазбора = "hub.example"; + _СхемаПоУмолчаниюДляРазбора = Неопределено; + + // Действие и Проверка + ПомощникТестированияURL.ПроверитьКодОшибки( + Новый Действие(ЭтотОбъект, "РазобратьАдрес"), + "argument.invalid_type" + ); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ОтклонитьУправляющийСимволПоКраю() Экспорт + + // Подготовка + _ЗначениеДляРазбора = "hub.example" + Символ(10); + _СхемаПоУмолчаниюДляРазбора = "https"; + + // Действие и Проверка + ПомощникТестированияURL.ПроверитьКодОшибки( + Новый Действие(ЭтотОбъект, "РазобратьАдрес"), + "url.server_address_required" + ); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_НеВключатьПарольВОшибку() Экспорт + + // Подготовка + Значение = "user:secret@hub.example"; + Ошибка = Неопределено; + + // Действие + Попытка + РазборURL.РазобратьСоСхемойПоУмолчанию(Значение, "https"); + Исключение + Ошибка = ИнформацияОбОшибке(); + КонецПопытки; + + // Проверка + Ожидаем.Что(ПодробноеПредставлениеОшибки(Ошибка)).Не_().Содержит("secret"); + Данные = ДиагностикаОшибок.Разобрать(Ошибка).Данные(); + Ожидаем.Что(Данные.Свойство("Значение")).ЭтоЛожь(); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ВернутьНезависимыеОбъекты() Экспорт + + // Подготовка + Первый = РазборURL.РазобратьСоСхемойПоУмолчанию("hub.example", "https"); + + // Действие + Второй = РазборURL.РазобратьСоСхемойПоУмолчанию("hub.example", "https"); + Первый.УстановитьПуть("/changed"); + + // Проверка + Ожидаем.Что(Второй.ВСтроку()).Равно("https://hub.example"); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_БезИсключенияРазобратьАдресСоСхемойПоУмолчанию() Экспорт + + // Подготовка + Ошибка = "предыдущее значение"; + + // Действие + Адрес = РазборURL.ПопытатьсяРазобратьСоСхемойПоУмолчанию("hub.example/private", "https", Ошибка); + + // Проверка + Ожидаем.Что(ТипЗнч(Адрес)).Равно(Тип("URL")); + Ожидаем.Что(Адрес.ВСтроку()).Равно("https://hub.example/private"); + Ожидаем.Что(Ошибка).Равно(Неопределено); + +КонецПроцедуры + +&ПараметризованныйТест +&ИсточникЗначение("/private", "https", "url.server_address_required") +&ИсточникЗначение("hub.example", "1https", "url.invalid_scheme") +&ИсточникЗначение(42, "https", "argument.invalid_type") +Процедура ТестДолжен_ВернутьНеопределеноПриОшибкеБезопасногоРазбора( + Значение, + СхемаПоУмолчанию, + ОжидаемыйКод +) Экспорт + + // Подготовка + Ошибка = Неопределено; + + // Действие + Адрес = РазборURL.ПопытатьсяРазобратьСоСхемойПоУмолчанию(Значение, СхемаПоУмолчанию, Ошибка); + + // Проверка + Ожидаем.Что(Адрес).Равно(Неопределено); + Ожидаем.Что(ДиагностикаОшибок.КодОшибки(Ошибка)).Равно(ОжидаемыйКод); + +КонецПроцедуры + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +Процедура РазобратьАдрес() Экспорт + + РазборURL.РазобратьСоСхемойПоУмолчанию(_ЗначениеДляРазбора, _СхемаПоУмолчаниюДляРазбора); + +КонецПроцедуры + +#КонецОбласти diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\265\320\232\320\276\320\274\320\277\320\276\320\275\320\265\320\275\321\202\321\213/\320\241\320\265\321\200\320\262\320\270\321\201\321\213\320\221\320\270\320\261\320\273\320\270\320\276\321\202\320\265\320\272\320\270URL.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\265\320\232\320\276\320\274\320\277\320\276\320\275\320\265\320\275\321\202\321\213/\320\241\320\265\321\200\320\262\320\270\321\201\321\213\320\221\320\270\320\261\320\273\320\270\320\276\321\202\320\265\320\272\320\270URL.os" index 44fde4c..8c2bea3 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\265\320\232\320\276\320\274\320\277\320\276\320\275\320\265\320\275\321\202\321\213/\320\241\320\265\321\200\320\262\320\270\321\201\321\213\320\221\320\270\320\261\320\273\320\270\320\276\321\202\320\265\320\272\320\270URL.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\265\320\232\320\276\320\274\320\277\320\276\320\275\320\265\320\275\321\202\321\213/\320\241\320\265\321\200\320\262\320\270\321\201\321\213\320\221\320\270\320\261\320\273\320\270\320\276\321\202\320\265\320\272\320\270URL.os" @@ -73,6 +73,10 @@ СервисыБиблиотекиURL.РазборщикКомпонентовURL(), СервисыБиблиотекиURL.РазборщикКомпонентовURL() ); + ПроверитьОдиночныйЭкземпляр( + СервисыБиблиотекиURL.РазборщикURL(), + СервисыБиблиотекиURL.РазборщикURL() + ); ПроверитьОдиночныйЭкземпляр( СервисыБиблиотекиURL.ПроверяющийСинтаксисURI(), СервисыБиблиотекиURL.ПроверяющийСинтаксисURI() diff --git "a/tests/\320\236\321\210\320\270\320\261\320\272\320\270/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272.os" "b/tests/\320\236\321\210\320\270\320\261\320\272\320\270/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272.os" index 36a363e..068ebc4 100644 --- "a/tests/\320\236\321\210\320\270\320\261\320\272\320\270/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272.os" +++ "b/tests/\320\236\321\210\320\270\320\261\320\272\320\270/\320\232\320\276\320\264\321\213\320\236\321\210\320\270\320\261\320\276\320\272.os" @@ -9,6 +9,7 @@ // Действие и Проверка Ожидаем.Что(КодыОшибокURL.НедопустимыйСинтаксис()).Равно("url.invalid_syntax"); Ожидаем.Что(КодыОшибокURL.НедопустимаяСхема()).Равно("url.invalid_scheme"); + Ожидаем.Что(КодыОшибокURL.ТребуетсяСерверныйАдрес()).Равно("url.server_address_required"); Ожидаем.Что(КодыОшибокURL.НедопустимоеПроцентноеКодирование()) .Равно("url.invalid_percent_encoding"); Ожидаем.Что(КодыОшибокURL.НедопустимаяСервернаяЧасть()).Равно("url.invalid_authority");