Строка запроса: проблемы и решения

Строка запроса проста… Нет!

Темы, затронутые в этой статье

  • Определение строки запроса
  • Спецификации строки запроса
  • Проблемы и решения

Определение строки запроса

Строка запроса — удобный и простой способ хранения данных с использованием URI (Uniform Resource Identifier — компактная последовательность символов, идентифицирующая абстрактный или физический ресурс).

Общий синтаксис URI состоит из иерархической последовательности компонентов, называемых схемой, полномочием, путем, запросом и фрагментом. Компонент запроса обозначается первым символом вопросительного знака ("?") и завершается знаком номера ("#") или концом URI.

foo://example.com:8042/over/there?name=ferret#nose
\_/   \______________/\_________/ \_________/ \__/
 |           |            |            |        |
scheme     authority     path        query   fragment
 |   _____________________|__
/ \ /                        \
urn:example:animal:ferret:nose

Компонент запроса содержит неиерархические данные. Это означает, что в строке запроса не указан путь к ресурсу.

Спецификации строки запроса

Строка запроса является частью синтаксиса URI. Первая спецификация URI rfc1630 была создана в 1994 году сетевой рабочей группой совместно с T. Berners-Lee и был расширен синтаксисом URN в 1997 году. Эта спецификация была пересмотрена и расширена IETF. rfc2986, созданный в январе 2005 года, стал одним из самых актуальных и широко известных стандартов. Этот стандарт неоднократно расширялся:

Группа технической архитектуры W3C (основная международная организация по стандартизации Всемирной паутины) также опубликовала несколько документов об URI.

Проблемы и решения

Специальные символы

Дело

Данные кодирования Base64 без процентного кодирования в параметре запроса

Проблема

Конфликтуют символы, закодированные в base64, с символами разделителей URI.

Описание

Например, ваше приложение использует строку запроса в кодировке base64.

import {encode} from 'js-base64';
encode('Георгий'); // 0JPQtdC+0YDQs9C40Lk=

Существует конфликт между символом подразделителя URI «+» и строкой запроса в кодировке base64. Следующий шаг — попытаться разобрать строку запроса:

import {parse} from 'qs';
parse('data=0JPQtdC+0YDQs9C40Lk=');
// { data: '0JPQtdC 0YDQs9C40Lk=' }

Строка с пробелом не является строкой base64. В конце концов, давайте выполним atob для этой строки.

import {decode} from 'js-base64';
decode('0JPQtdC 0YDQs9C40Lk='); // Гед`4,�.4.

Непредвиденный!

Решение

Вы должны использовать кодировку base64 для параметров запроса. Давайте прочитаем о кодировании и символах.

Символы и значения URI

URI состоит из ограниченного набора символов, состоящего из цифр, букв и нескольких графических символов. Зарезервированное подмножество этих символов может использоваться для разграничения компонентов синтаксиса в URI, в то время как остальные символы, включая как незарезервированный набор, так и те зарезервированные символы, которые не действуют как разделители, определяют данные идентификации каждого компонента.

Процентное кодирование

Механизм процентного кодирования используется для представления октета данных в компоненте, когда соответствующий символ этого октета находится за пределами допустимого набора или используется в качестве разделителя или внутри компонента.

Зарезервированные символы

URI включают компоненты и подкомпоненты, которые разделены символами в «зарезервированном» наборе. Эти символы называются «зарезервированными», потому что они могут (или не могут) быть определены как разделители общим синтаксисом, синтаксисом каждой конкретной схемы или синтаксисом конкретной реализации алгоритма разыменования URI.

gen-delims = “:” / “/” / “?” / «#» / «[“ / «]» / «@»

sub-delims = “!” / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / «=»

не зарезервировано = АЛЬФА / ЦИФРА / «-» / «.» / «_» / «~»

Незарезервированные символы

Символы, которые разрешены в URI, но не имеют зарезервированного назначения, называются незарезервированными. К ним относятся прописные и строчные буквы, десятичные цифры, дефис, точка, подчеркивание и тильда.

Если данные для компонента URI будут противоречить назначению зарезервированного символа в качестве разделителя, конфликтующие данные должны быть закодированы в процентах до формирования URI.

Base64 имеет потенциальные конфликты с зарезервированными символами URI. Я рекомендую вам использовать безопасное URL-адрес base64: tools.ietf.org.

Обращать внимание:

Эта кодировка не должна рассматриваться как такая же, как кодировка "base64", и не должна упоминаться только как "base64". Если не указано иное, «base64» относится к основанию 64 в предыдущем разделе.

Пакеты NPM для URL-безопасной кодировки base64:

Неожиданные зарезервированные символы

Дело

Использование параметров запроса без процентного кодирования

Проблема

Неожиданные зарезервированные символы

Описание

Как я упоминал выше, спецификация URI предоставляет список зарезервированных и незарезервированных символов. Если данные строки запроса содержат зарезервированные символы, они должны быть закодированы в процентах.

Давайте проверим пример:

axios.get(`/?q=${userInput}`);

Чтобы проиллюстрировать это, просто введите символ решетка (#), и строка запроса станет пустой, поскольку решетка является символом gen-delim (tools.ietf.org). Все данные после символа хэш перемещаются в хеш-значение компонента URI.

new URL(‘http://localhost:3000/?q=#myHashTag');
// {hash: '#myHashTag', search: '?q='}

Решение

Всегда кодируйте параметры запроса:

аксиос

axios({params: {q: '#myHashTag'}}); // q=%23myHashTag

qs

stringify({q: '#myHashTag'}); // q=%23myHashTag

encodeURIComponent

encodeURIComponent('q=#myHashTag'); // q%3D%23myHashTag

Процент закодированных символов URI

Дело

Декодировать параметр запроса base64 с процентом закодированных символов

Проблема

Неожиданные символы в параметрах запроса

Описание

Давайте проверим пример. Знак равенства (=) обычно используется в строке base64. Этот символ должен быть закодирован в процентах, потому что это зарезервированный символ URI:

encodeURIComponent('='); // %3D

Если вы попытаетесь декодировать закодированную в процентах строку с помощью window.atob — будет выброшено исключение:

atob('%3D'); 
// Uncaught DOMException: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

Тем не менее, js-base64, которая является одной из самых популярных библиотек для base64, не делает этого.

import {decode} from 'js-base64';
decode('%3D'); // �

Решение

В целом не доверяйте библиотекам и убедитесь, что вы используете декодированное значение строки запроса:

qs.parse('data=%3D'); // {data: '='}
decodeURIComponent('data=%3D'); // data==

Заключение

"Это не те дроиды, которых вы ищете..."

Простые черты могут принести много неприятностей. После этого вы должны потратить больше времени на изучение документации и исследование наиболее опасных угловых случаев строки запроса.