Русский

Инструменты разработчика · Кодер и декодер Base64

Декодирование Base64 в UTF-8 без кракозябр: atob и TextDecoder

· Как это работает

base64 кодирование Юникод

Конвейер от Base64 до UTF-8, показывающий сбои mojibake
Оригинальная векторная иллюстрация ToolAcre

atob() возвращает байты, замаскированные под символы, поэтому после декодирования акцентированный текст выглядит сломанным. В этом посте показан правильный конвейер из Base64 в байты в текст UTF-8 и как распознать шаблон сбоя.

Ответ API, который декодируется как «Кафе» — конкретный признак моджибаке и два байта за двумя неправильными символами.

Строка Base64 Q2Fmw6k= декодируется в байты (67, 97, 102, 195, 169), которые представляют собой UTF-8 текст Café. Вставьте в наивный декодер (просто преобразование atob и строк), и на выходе часто будет Café, где каждое ударение заменяется двумя неправильными символами. Этот моджибаке происходит потому, что atob возвращает строку байтов (кодовые единицы 0–255), а не текст UTF-8. Байты 195 и 169 кодируют ударение é в UTF-8.

Обработка их так, как если бы они были отдельными символами Latin-1, дает шаблон моджибаке. Правильный конвейер — это atob (байты как строка), затем TextDecoder (интерпретирует байты как UTF-8), и возвращается исходный текст. Функция atob не нарушена; он предназначен для двоичных данных. Его имя происходит от ASCII-to-binary, а создаваемая им двоичная строка представляет собой последовательность кодовых единиц 0–255, каждая из которых представляет один байт.

Что на самом деле возвращает atob() — строка кодовых единиц 0–255, которые обозначают байты, а не декодированный текст.

Если вы передадите ему Q2Fmw6k= (стандартный Base64), он выведет строку, в которой каждый символ представляет собой один байт: кодовая единица 67, затем 97, затем 102, затем 195, затем 169. Если отобразить эту строку напрямую или интерпретировать как текст Latin-1, вы увидите искаженный вывод. Недостающий шаг — преобразование кодовых единиц в массив байтов, а затем декодирование массива как UTF-8.

Цикл charCodeAt восстанавливает байтовые значения: для каждого символа в выходных данных atob вызовите charCodeAt, чтобы получить кодовую единицу (номер 0–255), сохраните ее в Uint8Array. Как только массив байтов существует, перейдите в TextDecoder с кодировкой utf-8. TextDecoder считывает последовательность байтов и интерпретирует как текст UTF-8, объединяя последовательности байтов, такие как (195, 169), в отдельные символы, такие как é. Байты (67, 97, 102, 195, 169) превращаются в четырехсимвольную строку Café. Цикл намеренно скучный: читайте каждую возвращаемую единицу кода с помощью charCodeAt и присваивайте ее соответствующей позиции Uint8Array. Никакого решения по набору символов там не происходит. Единственная интерпретация происходит, когда TextDecoder получает этот массив и применяет UTF-8 с обработкой фатальных ошибок.

Превращение этой строки в Uint8Array — цикл charCodeAt и почему это копия байта, а не преобразование

Этот двухэтапный процесс — восстановление байтов, затем интерпретация UTF-8 — это то, что кодировщик и декодер Base64 выполняют внутри себя. Шаблон моджибаке является явным признаком этой ошибки. Если Café отображается как Café, вы видите интерпретацию Latin-1 UTF-8 байт. UTF-8 байт для é — это 0xC3 0xA9 (десятичные 195, 169). В латинице 1 кодовая единица 195 — это Ã, а кодовая единица 169 — ©.

Когда последовательность байтов UTF-8 считывается так, как если бы каждый байт был отдельным символом Latin-1, каждая многобайтовая последовательность UTF-8 создает неправильные символы замены. Если Café отображается как Caf, за которым следует заменяющий символ, или как Caf? или Caf плюс U+FFFD, вы видите другую ошибку: декодер не распознал последовательность байтов как допустимую UTF-8. Конкретный пример: Base64 SGVsbG8sIOS4lueVjCEg8J-Zgg== декодирует следующим образом.

TextDecoder и решение о кодировке — декодирование как UTF-8, и почему кодировка — это отдельный факт, который вы должны знать

atob создает двоичную строку с байтами (72, 101, 108, 108, 111, 44, 32, 228, 184, 180, 149, 140, 33, 32, 240, 159, 152, 130). Первые шесть байтов — ASCII: становятся Hello. Байты 228, 184, 180 представляют собой трехбайтовую последовательность UTF-8, представляющую символ CJK. Байты 149, 140 являются частью следующей последовательности. Полная последовательность включает четырехбайтовую последовательность эмодзи (240, 159, 152, 130) для последнего символа.

При правильной обработке с помощью TextDecoder все байты объединяются для создания исходного текста со смешанным алфавитом. Последовательности байтов UTF-8 имеют предсказуемую длину: байт, начинающийся с 0xxxxxxx, является однобайтовым ASCII; байт, начинающийся с 110xxxxxx, ожидает следующий байт, начинающийся с 10xxxxxx (всего два байта); байт, начинающийся с 1110xxxx, ожидает два следующих байта (всего три байта); Байт, начинающийся с 11110xxx, ожидает три следующих байта (всего четыре байта). Для смешанного образца CJK и эмодзи байтовое представление особенно полезно, поскольку интуиция ASCII больше не помогает. Каждому видимому символу принадлежит несколько байтов, а удаление одного байта превращает оставшуюся последовательность в недействительную UTF-8. Строгая расшифровка превращает этот сдвиг в именованный сбой, а не в правдоподобный ущерб.

Рабочий пример: декодирование строки Base64, содержащей CJK и эмодзи — байты, кодовые точки и финальная строка по сравнению с оригиналом.

Последовательность, начинающаяся с 1111110x, недействительна в UTF-8 (зарезервирована на будущее, не используется). Байт, начинающийся с 10xxxxxx, никогда не должен выступать в качестве ведущего байта; это продолжение. Если поток байтов нарушает правила, он недействителен UTF-8. TextDecoder с кодировкой utf-8 интерпретирует массив по этим правилам и успешно обрабатывает допустимые последовательности. В случае недействительного сообщает об ошибке.

Кодер и декодер Base64 использует TextDecoder с флагом строгого режима true. Это означает, что недопустимый UTF-8 вызывает ошибку, а не автоматически вставляет символы замены (U+FFFD). Если строка Base64 декодируется в недопустимые байты UTF-8, строгий режим выдаст ошибку вместо продолжения работы с искаженным текстом. Это выбор дизайна: двоичные полезные данные (изображения, ключи, сжатые данные) не являются текстом и не должны декодироваться как текст.

Распознавание шаблона: Ã, †и � — как отличить проблему Base64 от проблемы с кодировкой

Если попытаться декодировать JPEG как Base64, поток байтов не будет представлять действительный UTF-8, и строгое декодирование отклонит его. Инструмент предлагает шестнадцатеричное представление для таких полезных данных: вы можете видеть необработанные байты, не притворяясь, что это текст. Распознавание ошибок декодирования UTF-8 предполагает просмотр байтов в контексте. Являются ли они нечетными числами там, где ожидается многобайтовая последовательность?

Является ли первый байт потенциальной последовательности недействительным (начиная с 10xxxxxx)? Отсутствуют ли байты продолжения? Кнопка байтового вывода обеспечивает самое чистое ответвление в расследовании. Если появляется шестнадцатеричный код, но декодирование текста завершается неудачно, анализ Base64 выполнен успешно, а полезные данные либо двоичные, повреждены, либо закодированы с использованием другой кодировки. Изменение пунктуации Base64 не может исправить несоответствие кодировки после того, как правильные байты уже появились.

Что здесь не распространяется — полезные нагрузки UTF-16, двоичный вывод, например изображения, и обработка недопустимых байтов.

Шаблоны последовательны. Одиночный байт 0xFF никогда не является допустимым в UTF-8; он не может быть ASCII байтом (только 0–127 являются ASCII) и не может быть ведущим байтом (ведущие байты — 0xC0–0xFD, 0xFF зарезервирован). Одиночные суррогаты (концепция UTF-16) не могут появляться в UTF-8; если вы видите последовательность байтов 0xED 0xA0 0x80 (которая кодирует суррогатный U+D800 в стиле UTF-8), это недопустимый UTF-8.

Одним из исторических обходных путей является btoa(unescape(encodeURIComponent(text))). encodeURIComponent превращает cafe в %C3%A9 (процентное кодирование UTF-8 байт), unescape переупаковывает как кодовые единицы, btoa кодирует кодовые единицы. Это работает для большинства текстов, но ненадежно для одиночных суррогатов и трудночитаемо. Современный конвейер — TextEncoder в байты, затем в base64 — более понятен и стандартен. TextEncoder встроен во все современные браузеры и Node.js, что делает правильный выбор. UTF-16, устаревшие однобайтовые кодировки и произвольное содержимое файлов требуют декодера, выбранного для этих байтов, или программы просмотра, поддерживающей двоичные файлы. ToolAcre намеренно не угадывает среди них. Догадки могут превратить неверную последовательность в вводящий в заблуждение текст, в то время как шестнадцатеричный дамп сохраняет каждый байт для более поздней, обоснованной интерпретации.

Вывод: Base64 дает вам байты, UTF-8 дает вам текст — как кодировщик и декодер Base64 выполняют оба шага, чтобы декодированный текст точно соответствовал входным данным.

Если у вас есть строка Base64 и вам нужен текст UTF-8, выполните следующие действия: декодируйте Base64 в байты (используя atob или библиотеку декодирования base64), создайте Uint8Array из байтов, передайте массив в TextDecoder с кодировкой utf-8, прочитайте результат как строку.

Если входные данные представляют собой двоичные данные, а не текст, пропустите TextDecoder и проверьте байты напрямую. Кодер и декодер Base64 обеспечивает представление шестнадцатеричных байтов, сохраняя значения, которые строгое декодирование UTF-8 отклонит. Эта вилка является диагностической: успешные байты плюс неудавшийся текст означают, что синтаксический анализ Base64 сработал, в то время как полезные данные являются двоичными, поврежденными или закодированы с помощью кодировки, которую этот инструмент не угадывает.