Base64 decode string - emacs отличается от jvm?

87
9

С строкой, закодированной base64 JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN, я получаю результаты различий от emacs, чем от кода clojure ниже.


Может кто-нибудь объяснить мне, почему?


elisp ниже дает правильный результат, дающий мне в конечном итоге действительный документ PDF (когда я прошел всю строку). Я уверен, что мой буфер emacs установлен на utf-8:


(base64-decode-string "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN")

"%PDF-1.1
%âãÏÓ
1 0 obj
<<


Вот тот же результат с символами в десятичной форме (я думаю):


  "%PDF-1.1
%\342\343\317\323
1

Ниже clojure приведен неверный вывод, что делает документ pdf недействительным, когда я передаю всю строку:


(import 'java.util.Base64 )

(defn decode [to-decode]
(let [
byts (.getBytes to-decode "UTF-8")
decoded (.decode (java.util.Base64/getDecoder) byts)
]
(String. decoded "UTF-8")))

(decode "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN")

"%PDF-1.1
%����
1 0 obj
<<


Тот же вывод, символы в десятичной (я думаю). Я даже не мог его скопировать/вставить, мне пришлось ввести его. Вот как это выглядит, когда я открыл PDF в text-mode для первых трех столбцов:


 "%PDF-1.1
%\357\277\275\357\277\275\357\277\275\357\277\275
1"

Edit Извлечение emacs из уравнения:


Если я напишу закодированную строку в файл с именем encoded.txt и протрубит ее через программу linux base64 --decode, я получу действительный вывод и хороший pdf также:
Это clojure:


(defn decode  [to-decode]
(let [byts (.getBytes to-decode "ASCII")
decoded (.decode (java.util.Base64/getDecoder) byts)
flip-negatives #(if (neg? %) (char (+ 255 %)) (char %))
]
(String. (char-array (map flip-negatives decoded)) )))

(spit "./output/decoded.pdf" (decode "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN"))

(spit "./output/encoded.txt" "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN")


Тогда это в оболочке:


➜  output git:(master) ✗ cat encoded.txt| base64 --decode > decoded2.pdf 
➜ output git:(master) ✗ diff decoded.pdf decoded2.pdf
2c2
< %áâÎÒ
---
> %����
➜ output git:(master) ✗

update - это работает


Алан Томпсон ответил ниже, чтобы поместить меня на правильный трек, но вы не можете понять, какую боль получить там.
Здесь идея того, что работает:


(def iso-latin-1-charset (java.nio.charset.Charset/forName "ISO-8859-1" ))

(as-> some-giant-string-i-hate-at-this-point $
(.getBytes $)
(String. $ iso-latin-1-charset)
(base64/decode $ "ISO-8859-1")
(spit "./output/a-pdf-that-actually-works.pdf" $ :encoding "ISO-8859-1" ))

спросил(а) 2021-01-19T20:03:24+03:00 2 месяца, 3 недели назад
1
Решение
136

Возвращая результаты в виде строки, я получаю:


(b64/decode-str "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN")  
=> "%PDF-1.1\r\n%����\r\n1 0 obj\r\n<< \r"

и как вектор ints:


(mapv int (b64/decode-str "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN")) 

=> [37 80 68 70 45 49 46 49 13 10 37 65533 65533 65533 65533 13 10 49 32 48
32 111 98 106 13 10 60 60 32 13]


Так как начало и конец строки выглядят ОК, я подозреваю, что строка B64 может быть неверной?

Update

Я пошел в http://www.base64decode.org и получил результат


"Malformed input... :("

введите описание изображения здесь

Обновление # 2

Корень проблемы состоит в том, что исходные символы не кодируются в кодировке UTF-8. Скорее, они кодируются ISO-8859-1 (иначе ISO-LATIN-1). Смотрите этот код:


  (defn decode-bytes
"Decodes a byte array from base64, returning a new byte array."
[code-bytes]
(.decode (java.util.Base64/getDecoder) code-bytes))

(def iso-latin-1-charset (java.nio.charset.Charset/forName "ISO-8859-1" )) ; aka ISO-LATIN-1

(let [b64-str "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN"
bytes-default (vec (.getBytes b64-str))
bytes-8859 (vec (.getBytes b64-str iso-latin-1-charset))

src-byte-array (decode-bytes (byte-array bytes-default))
src-bytes (vec src-byte-array)
src-str-8859 (String. src-byte-array iso-latin-1-charset)
]... ))


с результатом:


iso-latin-1-charset => <#sun.nio.cs.ISO_8859_1 #object[sun.nio.cs.ISO_8859_1 0x3edbd6e8 "ISO-8859-1"]>

bytes-default => [74 86 66 69 82 105 48 120 76 106 69 78 67 105 88 105 52 56 47 84 68 81 111 120 73 68 65 103 98 50 74 113 68 81 111 56 80 67 65 78]
bytes-8859 => [74 86 66 69 82 105 48 120 76 106 69 78 67 105 88 105 52 56 47 84 68 81 111 120 73 68 65 103 98 50 74 113 68 81 111 56 80 67 65 78]

(= bytes-default bytes-8859) => true

src-bytes => [37 80 68 70 45 49 46 49 13 10 37 -30 -29 -49 -45 13 10 49 32 48 32 111 98 106 13 10 60 60 32 13]
src-str-8859 => "%PDF-1.1\r\n%âãÏÓ\r\n1 0 obj\r\n<< \r"


Таким образом, конструктор java.lang.String будет корректно работать с входом byte[], даже если установлен высокий бит (что делает их похожими на "отрицательные" значения), если вы укажете конструктору правильные значения java.nio.charset.Charset использовать для интерпретации значений.


Интересно, что тип объекта sun.nio.cs.ISO_8859_1.

Обновление # 3

См. ниже вопрос SO для списка библиотек, которые могут (обычно) автоматически определять кодировку байтового потока (например, UTF-8, ISO-8859-1,...)


Каков наиболее точный детектор кодирования?

ответил(а) 2021-01-19T20:03:24+03:00 2 месяца, 3 недели назад
44

Я думаю, вам нужно проверить фактические байты, которые создаются в обоих сценариях. Я бы сохранил как декодированные результаты в файле, а затем сравнил их, используя, например, инструмент xxd командной строки, чтобы получить шестнадцатеричное отображение байтов в файлах.


Я подозреваю, что ваши emacs и приложение clojure используют другой шрифт, заставляя обрабатывать одни и те же не-ASCII-байты по-разному, например. то же значение байта отображается как â в emacs и в выводе clojure.

Я также хотел бы проверить, действительно ли elisp создает результирующую строку, используя UTF-8. base64-decode-string упоминает unibytes, и я не уверен это действительно UTF-8. Unibyte звучит как символы кодирования, используя всегда один байт на символ, тогда как UTF-8 использует от одного до четырех байтов на символ.

ответил(а) 2021-01-19T20:03:24+03:00 2 месяца, 3 недели назад
44

Update


@glts сделал правильный вывод в своем комментарии к вопросу. Если мы перейдем к http://www.utilities-online.info/base64/ (например), и мы попытаемся декодировать исходную строку, получим третий, другой результат:


%PDF-1.1
%⣏Ӎ
1 0 obj
<<

Однако, если мы попытаемся закодировать данные, опубликованные OP, мы получим другую строку Base64: JVBERi0xLjEKICXDosOjw4/DkwogMSAwIG9iagogPDwg, которая, если мы запустим исходную реализацию decode, как написано OP, получим тот же результат:


(decode "JVBERi0xLjEKICXDosOjw4/DkwogMSAwIG9iagogPDwg")
"%PDF-1.1\n %âãÏÓ\n 1 0 obj\n << "

Не нужно делать никаких конверсий. Я думаю, вы должны проверить кодировщик.


Оригинальный ответ


Эта проблема связана с подпиской java Byte.. Настолько весело!


Когда вы конвертируете его в строку, он обрезает все отрицательные значения до 65533, что неверно:


(map long (decode "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN"))

;; (37 80 68 70 45 49 46 49 13 10 37 65533 65533 65533 65533 13 10 49 32 48 32 111 98 106 13 10 60 60 32 13)


позволяет увидеть, что происходит:


(defn decode  [to-decode]
(let [byts (.getBytes to-decode "UTF-8")
decoded (.decode (java.util.Base64/getDecoder) byts)]
decoded))

(into [] (decode "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN"))

;; [37 80 68 70 45 49 46 49 13 10 37 -30 -29 -49 -45 13 10 49 32 48 32 111 98 106 13 10 60 60 32 13]


Смотрите минусы? позволяет исправить это:


 (into [] (char-array (map #(if (neg? %) (char (+ 255 %)) (char %))(decode "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN"))))

;; [\% \P \D \F \- \1 \. \1 \return \newline \% \á \â \Î \Ò \return \newline \1 \space \0 \space \o \b \j \return \newline \< \< \space \return]


И если мы превратим это в строку, мы получим то, что дали нам emacs:


(String. (char-array (map #(if (neg? %) (char (+ 255 %)) (char %)) (decode "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN"))))
;; "%PDF-1.1\r\n%áâÎÒ\r\n1 0 obj\r\n<< \r"

ответил(а) 2021-01-19T20:03:24+03:00 2 месяца, 3 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема