使用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
<<
相同的输出,十进制的字符(我认为)。我甚至无法复制/粘贴它,我必须输入它。这就是我在text-mode
中为前三列打开PDF时的样子:
"%PDF-1.1
%\357\277\275\357\277\275\357\277\275\357\277\275
1"
如果我将编码的字符串写入名为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")
然后在shell:
➜ output git:(master) ✗ cat encoded.txt| base64 --decode > decoded2.pdf
➜ output git:(master) ✗ diff decoded.pdf decoded2.pdf
2c2
< %áâÎÒ
---
> %����
➜ output git:(master) ✗
(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" ))
答案 0 :(得分:0)
将结果作为字符串返回,我得到:
(b64/decode-str "JVBERi0xLjENCiXi48/TDQoxIDAgb2JqDQo8PCAN")
=> "%PDF-1.1\r\n%����\r\n1 0 obj\r\n<< \r"
并作为整数的载体:
(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字符串可能格式不正确?
我去了http://www.base64decode.org并得到了结果
"Malformed input... :("
问题的根源是源字符 不 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[]
输入,即使设置了高位(使它们看起来像#34;负值&#34;值),只要你告诉构造函数用于解释值的正确java.nio.charset.Charset
。
有趣的是对象类型为sun.nio.cs.ISO_8859_1
。
请参阅下面的SO问题,了解可以(通常)自动检测字节流编码的库列表(例如UTF-8,ISO-8859-1,...)
答案 1 :(得分:0)
我认为您需要验证两种方案中生成的实际字节数。我会将解码结果保存在一个文件中,然后使用例如xxd
命令行工具对它们进行比较,以获得文件中字节的十六进制显示。
我怀疑你的emacs和clojure应用程序使用不同的字体导致相同的非ASCII字节被不同地呈现,例如相同的字节值在emacs中呈现为â
,在clojure输出中呈现为�
。
我还会检查elisp是否确实使用UTF-8创建了结果字符串。 base64-decode-string
提及unibytes,我不确定它是否真的是UTF-8。 Unibyte 听起来像使用每个字符一个字节编码字符,而UTF-8每个字符使用一到四个字节。
答案 2 :(得分:0)
@glts在他对这个问题的评论中提出了正确的观点。如果我们转到http://www.utilities-online.info/base64/(例如),我们尝试解码原始字符串,我们得到第三个不同的结果:
%PDF-1.1
%⣏Ӎ
1 0 obj
<<
但是,如果我们尝试对OP发布的数据进行编码,我们会得到一个不同的Base64字符串:JVBERi0xLjEKICXDosOjw4/DkwogMSAwIG9iagogPDwg
,如果我们使用OP编写的原始decode
实现运行,我们会得到相同的输出:
(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"