Что такое URL?
URL - полезная вещь. Он сообщает как людям, так и пользователям, где найти ресурсы в Интернете. В URL-адрес упаковано много информации, от обозначений протоколов до привязок к документам, и когда мы обращаемся с ним как со строкой, мы направляемся в опасность.
URL-адрес представляет собой упакованное значение. Он содержит ужасно много данных:
- Схема протокола
- Имя хоста
- Номер порта
- Дорожка
- Имя файла
- Параметры поиска (также известные как параметры строки запроса)
- Якорь (который также можно использовать для параметров)
Проблема с манипулированием строками
В зависимости от ваших конкретных потребностей URL-адрес может содержать несколько зарезервированных символов. Некоторые из этих символов включают ?
, #
, =
, &
, %
, :
, ,
и /
. Это не исчерпывающий список. Наличие этих символов в неправильном месте в вашем URL-адресе может вызвать недопонимание.
Хорошая реализация должна быть достаточно гибкой, чтобы справляться с любыми разумными входными данными, и способной предсказуемо дать сбой, когда входные данные не являются разумными. Упакованные значения, такие как URL, должны обрабатываться как упакованные значения, а не обрабатываться с помощью манипуляций со строками.
Вывод:
https://api.foobar.com/api/bookmark/?url=https://www.reddit.com/r/chickens/search/?q=silkie&restrict_sr=1 URL { href: 'https://api.foobar.com/api/bookmark/?url=https://www.reddit.com/r/chickens/search/?q=silkie&restrict_sr=1', origin: 'https://api.foobar.com', protocol: 'https:', username: '', password: '', host: 'api.foobar.com', hostname: 'api.foobar.com', port: '', pathname: '/api/bookmark/', search: '?url=https://www.reddit.com/r/chickens/search/?q=silkie&restrict_sr=1', searchParams: URLSearchParams { 'url' => 'https://www.reddit.com/r/chickens/search/?q=silkie', 'restrict_sr' => '1' }, hash: '' }
Здесь вы можете видеть, как q рассматривается как часть URL-адреса, но restrict_sr
интерпретируется как еще один параметр URL-адреса, параллельный url
. Хотя может показаться заманчивым просто использовать функцию для URL-кодирования, я хотел бы призвать вас передумать. Эти методы кодирования URL-адресов не подходят для всех возможных символов, которые вы хотели бы туда поместить, и они, вероятно, сделают множество предположений, которые не будут верны.
Вывод:
URL { href: 'https://api.foobar.com/api/bookmark/?url=https://www.reddit.com/r/chickens/search/?q=silkie&restrict_sr=1', origin: 'https://api.foobar.com', protocol: 'https:', username: '', password: '', host: 'api.foobar.com', hostname: 'api.foobar.com', port: '', pathname: '/api/bookmark/', search: '?url=https://www.reddit.com/r/chickens/search/?q=silkie&restrict_sr=1', searchParams: URLSearchParams { 'url' => 'https://www.reddit.com/r/chickens/search/?q=silkie', 'restrict_sr' => '1' }, hash: '' }
Хотя вы могли использовать более эффективную кодировку для достижения того же результата, вам придется пересматривать процедуру обработки строк каждый раз, когда вы меняете параметры, которые хотите добавить. Вы должны беспокоиться о том, с какого URL вы начинаете, и вы должны доверять своим предположениям. Мы не должны доверять этим предположениям, и, к счастью, в этом нет необходимости.
Лучший подход
Здесь вы можете видеть, что кодирование URL-адреса не решило проблему. Давайте попробуем другой подход: воспользуемся URL API.
Вывод:
https://api.foobar.com/?url=https%3A%2F%2Fwww.reddit.com%2Fr%2Fchickens%2Fsearch%2F%3Fq%3Dsilkie%26restrict_sr%3D1 URL { href: 'https://api.foobar.com/?url=https%3A%2F%2Fwww.reddit.com%2Fr%2Fchickens%2Fsearch%2F%3Fq%3Dsilkie%26restrict_sr%3D1', origin: 'https://api.foobar.com', protocol: 'https:', username: '', password: '', host: 'api.foobar.com', hostname: 'api.foobar.com', port: '', pathname: '/', search: '?url=https%3A%2F%2Fwww.reddit.com%2Fr%2Fchickens%2Fsearch%2F%3Fq%3Dsilkie%26restrict_sr%3D1', searchParams: URLSearchParams { 'url' => 'https://www.reddit.com/r/chickens/search/?q=silkie&restrict_sr=1' }, hash: '' }
URL-адрес, который вы получили с помощью манипуляций со строками, был: https://api.foobar.com/api/bookmark/?url=https://www.reddit.com/r/chickens/search/?q=silkie&restrict_sr=1
URL-адрес, который вы получаете с помощью URL API: https://api.foobar.com/?url=https%3A%2F%2Fwww.reddit.com%2Fr%2Fchickens%2Fsearch%2F%3Fq%3Dsilkie%26restrict_sr%3D1
Используя URL API здесь, вы можете видеть, что URL-адрес, который используется в качестве параметра, безопасно закодирован в параметре url, и вам не нужно беспокоиться о том, что его параметры будут перепутаны с параметрами в URL-адресе foobar.com
.
Обращаясь с нашим URL-адресом как с упакованным значением, мы решаем всевозможные проблемы, связанные с кодировкой, структурой параметров и другими проблемными предположениями. Мы можем даже легче работать с URL-адресами, содержащими хеш-якоря. Подумайте о том, насколько сложными должны были бы быть ваши манипуляции со строками, если бы вы использовали одну из тех библиотек, которые поддерживают некоторые параметры в качестве хэш-якорей!
https://www.somefake.url/path/interface.html?q=silkies#page=2&thumbnails=on
Почему это важно?
Две основные проблемы, вызванные анти-паттерном манипулирования строками URL-адресов, — это ошибки и уязвимости URL-инъекций.
Плохо закодированные URL-адреса затрудняют понимание веб-серверами и приложениями поступающих к ним параметров. Если они не могут достоверно понять свои входные данные, возможно неожиданное или нежелательное поведение.
URL-адреса, созданные с использованием предсказуемых манипуляций со строками, также представляют реальный риск внедрения URL-адресов. Внедрение URL-адресов может привести к внедрению SQL, внедрению NoSQL, межсайтовому скриптингу (XSS) и целому ряду других дыр в безопасности.
Заключение
URL-адрес не является строкой. Подобно упакованным битовым полям прошлого, это упакованное значение. Не относитесь к нему как к строке, относитесь к нему как к первоклассному объекту или структуре. И никогда не пишите свои собственные анализаторы URL-адресов, каждый язык имеет хорошую библиотеку URL-адресов, которую вы можете использовать.
Примечание. Эта часть ориентирована на Node в своих примерах, но этот анти-шаблон является полиглоттальным. Как и в большинстве антишаблонов, проблема не в синтаксисе, а в подходе.