Что такое 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 в своих примерах, но этот анти-шаблон является полиглоттальным. Как и в большинстве антишаблонов, проблема не в синтаксисе, а в подходе.