url.el 关于 Multibyte text in HTTP request 的一件怪事

Emacs 生成的字符串,明明没有特殊字符却成了 Multibyte 的了,比如 symbol-name

(multibyte-string-p (symbol-name 'XXX))
;; t

以及从 Auth Source 中获得密码:

;; ~/.authinfo
;; machine example.com password 123456
(let ((pass
       (let ((plist (car (auth-source-search :max 1 :host "example.com"))))
         (let ((v (plist-get plist :secret)))
           (if (functionp v) (funcall v) v)))))
  (list pass (multibyte-string-p pass)))
;; => ("123456" t)

更加诡异的是(url-http-create-request 就是怎么干的),5 Bytes 是哪里来的?

(let ((s (concat (symbol-name 'a)
                 (encode-coding-string "λ" 'utf-8))))
  (list (length s)
        (string-bytes s)))
;; => (3 5)

url.el 发送请求时,如果 Header 中包含了上面的 Multibyte 数据,当 Body 包含 UTF-8 的 Unibyte 时会报错,否则则不会:

(defun test (author)
  (let ((url-request-extra-headers
         `(("Content-Type" . "application/json")
           ("XXX" . ,(symbol-name 'XXX))))
        (url-request-method "POST")
        (url-request-data (encode-coding-string
                           (json-encode `((author . ,author)))
                           'utf-8)))
    (display-buffer
     (url-retrieve-synchronously "http://httpbin.org/post"))))

(test "Li Bai") ;; 正常
(test "李白") ;; 报错 Multibyte text in HTTP request

目前正确的做法是把所有的传给 url.el 的参数都 Encode 一遍,比如上面的 (encode-coding-string (symbol-name 'XXX) 'utf-8),但是我实在是不明白怎么回事。


相关讨论:

看了下文档,multibyte好像是emacs内部使用的编码,codepoint包含了utf-8,但是字节序列不是utf-8方式,应该是用的比utf-8更高效一点的方式,更方便程序处理,编码名字叫utf-8-emacs.

此编码包含了utf-8,还包含了额外的codepoint,没法一对一转换到utf-8,可能因为这个,url.el里没有做自动转换。

(string-bytes (concat (symbol-name 'a) (encode-coding-string "λ" 'utf-8)))  ;; => 5
(string-bytes (concat "a" (encode-coding-string "λ" 'utf-8))) ;; => 3

第一种写法结果是5,有点奇怪。