Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1220 lines
51 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. ;;; request.el --- Compatible layer for URL request -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2012 Takafumi Arakaki
  3. ;; Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2012
  4. ;; Free Software Foundation, Inc.
  5. ;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
  6. ;; URL: https://github.com/tkf/emacs-request
  7. ;; Package-Version: 20210214.37
  8. ;; Package-Commit: accd430ee706f5b10fb20003b06bd8209bcdaa82
  9. ;; Package-Requires: ((emacs "24.4"))
  10. ;; Version: 0.3.3
  11. ;; This file is NOT part of GNU Emacs.
  12. ;; request.el is free software: you can redistribute it and/or modify
  13. ;; it under the terms of the GNU General Public License as published by
  14. ;; the Free Software Foundation, either version 3 of the License, or
  15. ;; (at your option) any later version.
  16. ;; request.el is distributed in the hope that it will be useful,
  17. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. ;; GNU General Public License for more details.
  20. ;; You should have received a copy of the GNU General Public License
  21. ;; along with request.el.
  22. ;; If not, see <https://www.gnu.org/licenses/>.
  23. ;;; Commentary:
  24. ;; Uses ``curl`` as its backend or Emacs's native ``url.el`` library if
  25. ;; ``curl`` is not found.
  26. ;;
  27. ;; The default encoding for requests is ``utf-8``. Please explicitly specify
  28. ;; ``:encoding 'binary`` for binary data.
  29. ;;; Code:
  30. (eval-when-compile
  31. (defvar url-http-method)
  32. (defvar url-http-response-status))
  33. (require 'cl-lib)
  34. (require 'url)
  35. (require 'mail-utils)
  36. (require 'autorevert)
  37. (require 'auth-source)
  38. (require 'mailheader)
  39. (defgroup request nil
  40. "Compatible layer for URL request in Emacs."
  41. :group 'comm
  42. :prefix "request-")
  43. (defconst request-version "0.3.3")
  44. (defcustom request-storage-directory
  45. (concat (file-name-as-directory user-emacs-directory) "request")
  46. "Directory to store data related to request.el."
  47. :type 'directory)
  48. (defcustom request-curl "curl"
  49. "Executable for curl command."
  50. :type 'string)
  51. (defcustom request-curl-options nil
  52. "List of curl command options.
  53. List of strings that will be passed to every curl invocation.
  54. You can pass extra options here, like setting the proxy."
  55. :type '(repeat string))
  56. (defcustom request-backend (if (executable-find request-curl)
  57. 'curl
  58. 'url-retrieve)
  59. "Backend to be used for HTTP request.
  60. Automatically set to `curl' if curl command is found."
  61. :type '(choice (const :tag "cURL backend" curl)
  62. (const :tag "url-retrieve backend" url-retrieve)))
  63. (defcustom request-timeout nil
  64. "Default request timeout in second.
  65. nil means no timeout."
  66. :type '(choice (integer :tag "Request timeout seconds")
  67. (boolean :tag "No timeout" nil)))
  68. (make-obsolete-variable 'request-temp-prefix nil "0.3.3")
  69. (defcustom request-log-level -1
  70. "Logging level for request.
  71. One of `error'/`warn'/`info'/`verbose'/`debug'/`trace'/`blather'.
  72. -1 means no logging."
  73. :type '(choice (integer :tag "No logging" -1)
  74. (const :tag "Level error" error)
  75. (const :tag "Level warn" warn)
  76. (const :tag "Level info" info)
  77. (const :tag "Level Verbose" verbose)
  78. (const :tag "Level DEBUG" debug)
  79. (const :tag "Level TRACE" trace)
  80. (const :tag "Level BLATHER" blather)))
  81. (defcustom request-message-level 'warn
  82. "Logging level for request.
  83. See `request-log-level'."
  84. :type '(choice (integer :tag "No logging" -1)
  85. (const :tag "Level error" error)
  86. (const :tag "Level warn" warn)
  87. (const :tag "Level info" info)
  88. (const :tag "Level Verbose" verbose)
  89. (const :tag "Level DEBUG" debug)
  90. (const :tag "Level TRACE" trace)
  91. (const :tag "Level BLATHER" blather)))
  92. (defmacro request--document-function (function docstring)
  93. "Document FUNCTION with DOCSTRING. Use this for defstruct accessor etc."
  94. (declare (indent defun)
  95. (doc-string 2))
  96. `(put ',function 'function-documentation ,docstring))
  97. (defconst request--log-level-def
  98. '(;; debugging
  99. (blather . 60) (trace . 50) (debug . 40)
  100. ;; information
  101. (verbose . 30) (info . 20)
  102. ;; errors
  103. (warn . 10) (error . 0))
  104. "Named logging levels.")
  105. (defvar request-log-buffer-name " *request-log*")
  106. (defmacro request-log (level fmt &rest args)
  107. "Main logging function at warning LEVEL in FMT with ARGS."
  108. (declare (indent 1))
  109. `(cl-flet ((log-level-as-int
  110. (level)
  111. (if (integerp level)
  112. level
  113. (or (cdr (assq level request--log-level-def)) 0))))
  114. (let ((level (log-level-as-int ,level))
  115. (log-level (log-level-as-int request-log-level))
  116. (msg-level (log-level-as-int request-message-level)))
  117. (when (<= level (max log-level msg-level))
  118. (let ((msg (format "[%s] %s" ,level (format ,fmt ,@args))))
  119. (when (<= level log-level)
  120. (with-current-buffer (get-buffer-create request-log-buffer-name)
  121. (setq buffer-read-only t)
  122. (let ((inhibit-read-only t))
  123. (goto-char (point-max))
  124. (insert msg "\n"))))
  125. (when (<= level msg-level)
  126. (message "%s" msg)))))))
  127. (defconst request--url-unreserved-chars
  128. '(?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z
  129. ?A ?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z
  130. ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9
  131. ?- ?_ ?. ?~)
  132. "`url-unreserved-chars' copied from Emacs 24.3 release candidate.
  133. This is used for making `request--urlencode-alist' RFC 3986 compliant
  134. for older Emacs versions.")
  135. (defun request--urlencode-alist (alist)
  136. "Hexify ALIST fields according to RFC3986."
  137. (let ((url-unreserved-chars request--url-unreserved-chars))
  138. (cl-loop for sep = "" then "&"
  139. for (k . v) in alist
  140. concat sep
  141. concat (url-hexify-string (format "%s" k))
  142. concat "="
  143. concat (url-hexify-string (format "%s" v)))))
  144. (defun request--parse-response-at-point ()
  145. "Parse the first header line such as \"HTTP/1.1 200 OK\"."
  146. (when (re-search-forward "\\=[ \t\n]*HTTP/\\([0-9\\.]+\\) +\\([0-9]+\\)" nil t)
  147. (list :version (match-string 1)
  148. :code (string-to-number (match-string 2)))))
  149. (defun request--goto-next-body (&optional noerror)
  150. "Scan forward to next blank line allowing NOERROR if missing."
  151. (re-search-forward "^\r\n" nil noerror))
  152. (cl-defstruct request-response
  153. "A structure holding all relevant information of a request."
  154. status-code history data error-thrown symbol-status url
  155. done-p settings
  156. ;; internal variables
  157. -buffer -raw-header -timer -backend)
  158. (defmacro request--document-response (function docstring)
  159. "Append to FUNCTION's DOCSTRING some more canned verbiage."
  160. (declare (indent defun) (doc-string 2))
  161. `(request--document-function ,function ,(concat docstring "
  162. .. This is an accessor for `request-response' object.
  163. \(fn RESPONSE)")))
  164. (request--document-response request-response-status-code
  165. "Integer HTTP response code (e.g., 200).")
  166. (request--document-response request-response-history
  167. "Redirection history (a list of response object).
  168. The first element is the oldest redirection.
  169. You can use restricted portion of functions for the response
  170. objects in the history slot. It also depends on backend. Here
  171. is the table showing what functions you can use for the response
  172. objects in the history slot.
  173. ==================================== ============== ==============
  174. Slots Backends
  175. ------------------------------------ -----------------------------
  176. \\ curl url-retrieve
  177. ==================================== ============== ==============
  178. request-response-url yes yes
  179. request-response-header yes no
  180. other functions no no
  181. ==================================== ============== ==============
  182. ")
  183. (request--document-response request-response-data
  184. "Response parsed by the given parser.")
  185. (request--document-response request-response-error-thrown
  186. "Error thrown during request.
  187. It takes the form of ``(ERROR-SYMBOL . DATA)``, which can be
  188. re-raised (`signal'ed) by ``(signal ERROR-SYMBOL DATA)``.")
  189. (request--document-response request-response-symbol-status
  190. "A symbol representing the status of request (not HTTP response code).
  191. One of success/error/timeout/abort/parse-error.")
  192. (request--document-response request-response-url
  193. "Final URL location of response.")
  194. (request--document-response request-response-done-p
  195. "Return t when the request is finished or aborted.")
  196. (request--document-response request-response-settings
  197. "Keyword arguments passed to `request' function.
  198. Some arguments such as HEADERS is changed to the one actually
  199. passed to the backend. Also, it has additional keywords such
  200. as URL which is the requested URL.")
  201. (defun request-response-header (response field-name)
  202. "Fetch the values of RESPONSE header field named FIELD-NAME.
  203. It returns comma separated values when the header has multiple
  204. field with the same name, as :RFC:`2616` specifies.
  205. Examples::
  206. (request-response-header response
  207. \"content-type\") ; => \"text/html; charset=utf-8\"
  208. (request-response-header response
  209. \"unknown-field\") ; => nil"
  210. (let ((raw-header (request-response--raw-header response)))
  211. (when raw-header
  212. (with-temp-buffer
  213. (erase-buffer)
  214. (insert raw-header)
  215. ;; ALL=t to fetch all fields with the same name to get comma
  216. ;; separated value [#rfc2616-sec4]_.
  217. (mail-fetch-field field-name nil t)))))
  218. ;; .. [#rfc2616-sec4] RFC2616 says this is the right thing to do
  219. ;; (see https://tools.ietf.org/html/rfc2616.html#section-4.2).
  220. ;; Python's requests module does this too.
  221. (defun request-response-headers (response)
  222. "Return RESPONSE headers as an alist.
  223. I would have chosen a function name that wasn't so suggestive that
  224. `headers` is a member of the `request-response` struct, but
  225. as there's already precedent with `request-response-header', I
  226. hew to consistency."
  227. (let ((raw-header (request-response--raw-header response)))
  228. (when raw-header
  229. raw-header
  230. (with-temp-buffer
  231. (save-excursion (insert raw-header))
  232. (when (save-excursion (request--parse-response-at-point))
  233. (forward-line))
  234. (mail-header-extract-no-properties)))))
  235. (defconst request--backend-alist
  236. '((url-retrieve
  237. . ((request . request--url-retrieve)
  238. (request-sync . request--url-retrieve-sync)
  239. (terminate-process . delete-process)
  240. (get-cookies . request--url-retrieve-get-cookies)))
  241. (curl
  242. . ((request . request--curl)
  243. (request-sync . request--curl-sync)
  244. (terminate-process . interrupt-process)
  245. (get-cookies . request--curl-get-cookies))))
  246. "Map backend and method name to actual method (symbol).
  247. It's alist of alist, of the following form::
  248. ((BACKEND . ((METHOD . FUNCTION) ...)) ...)
  249. It would be nicer if I can use EIEIO. But as CEDET is included
  250. in Emacs by 23.2, using EIEIO means abandon older Emacs versions.
  251. It is probably necessary if I need to support more backends. But
  252. let's stick to manual dispatch for now.")
  253. ;; See: (view-emacs-news "23.2")
  254. (defun request--choose-backend (method)
  255. "Return `fucall'able object for METHOD of current `request-backend'."
  256. (assoc-default
  257. method
  258. (or (assoc-default request-backend request--backend-alist)
  259. (error "%S is not valid `request-backend'" request-backend))))
  260. (defun request-cookie-string (host &optional localpart secure)
  261. "Lookup HOST LOCALPART SECURE in cookie jar as`document.cookie` string.
  262. Example::
  263. (request-cookie-string \"127.0.0.1\" \"/\") ; => \"key=value; key2=value2\""
  264. (mapconcat (lambda (nv) (concat (car nv) "=" (cdr nv)))
  265. (request-cookie-alist host localpart secure)
  266. "; "))
  267. (defun request-cookie-alist (host &optional localpart secure)
  268. "Lookup HOST LOCALPART SECURE in cookie jar as alist.
  269. Example::
  270. (request-cookie-alist \"127.0.0.1\" \"/\") ; => ((\"key\" . \"value\") ...)"
  271. (funcall (request--choose-backend 'get-cookies) host localpart secure))
  272. (cl-defun request (url &rest settings
  273. &key
  274. (params nil)
  275. (data nil)
  276. (headers nil)
  277. (encoding 'utf-8)
  278. (error nil)
  279. (sync nil)
  280. (response (make-request-response))
  281. &allow-other-keys)
  282. "Main entry requesting URL with property list SETTINGS as follow.
  283. ==================== ========================================================
  284. Keyword argument Explanation
  285. ==================== ========================================================
  286. TYPE (string) type of request to make: POST/GET/PUT/DELETE
  287. PARAMS (alist) set \"?key=val\" part in URL
  288. DATA (string/alist) data to be sent to the server
  289. FILES (alist) files to be sent to the server (see below)
  290. PARSER (symbol) a function that reads current buffer and return data
  291. HEADERS (alist) additional headers to send with the request
  292. ENCODING (symbol) encoding for request body (utf-8 by default)
  293. SUCCESS (function) called on success
  294. ERROR (function) called on error
  295. COMPLETE (function) called on both success and error
  296. TIMEOUT (number) timeout in second
  297. STATUS-CODE (alist) map status code (int) to callback
  298. SYNC (bool) If non-nil, wait until request is done. Default is nil.
  299. ==================== ========================================================
  300. * Callback functions
  301. Callback functions STATUS, ERROR, COMPLETE and `cdr's in element of
  302. the alist STATUS-CODE take same keyword arguments listed below. For
  303. forward compatibility, these functions must ignore unused keyword
  304. arguments (i.e., it's better to use `&allow-other-keys' [#]_).::
  305. (CALLBACK ; SUCCESS/ERROR/COMPLETE/STATUS-CODE
  306. :data data ; whatever PARSER function returns, or nil
  307. :error-thrown error-thrown ; (ERROR-SYMBOL . DATA), or nil
  308. :symbol-status symbol-status ; success/error/timeout/abort/parse-error
  309. :response response ; request-response object
  310. ...)
  311. .. [#] `&allow-other-keys' is a special \"markers\" available in macros
  312. in the CL library for function definition such as `cl-defun' and
  313. `cl-function'. Without this marker, you need to specify all arguments
  314. to be passed. This becomes problem when request.el adds new arguments
  315. when calling callback functions. If you use `&allow-other-keys'
  316. (or manually ignore other arguments), your code is free from this
  317. problem. See info node `(cl) Argument Lists' for more information.
  318. Arguments data, error-thrown, symbol-status can be accessed by
  319. `request-response-data', `request-response-error-thrown',
  320. `request-response-symbol-status' accessors, i.e.::
  321. (request-response-data RESPONSE) ; same as data
  322. Response object holds other information which can be accessed by
  323. the following accessors:
  324. `request-response-status-code',
  325. `request-response-url' and
  326. `request-response-settings'
  327. * STATUS-CODE callback
  328. STATUS-CODE is an alist of the following format::
  329. ((N-1 . CALLBACK-1)
  330. (N-2 . CALLBACK-2)
  331. ...)
  332. Here, N-1, N-2,... are integer status codes such as 200.
  333. * FILES
  334. FILES is an alist of the following format::
  335. ((NAME-1 . FILE-1)
  336. (NAME-2 . FILE-2)
  337. ...)
  338. where FILE-N is a list of the form::
  339. (FILENAME &key PATH BUFFER STRING MIME-TYPE)
  340. FILE-N can also be a string (path to the file) or a buffer object.
  341. In that case, FILENAME is set to the file name or buffer name.
  342. Example FILES argument::
  343. `((\"passwd\" . \"/etc/passwd\") ; filename = passwd
  344. (\"scratch\" . ,(get-buffer \"*scratch*\")) ; filename = *scratch*
  345. (\"passwd2\" . (\"password.txt\" :file \"/etc/passwd\"))
  346. (\"scratch2\" . (\"scratch.txt\" :buffer ,(get-buffer \"*scratch*\")))
  347. (\"data\" . (\"data.csv\" :data \"1,2,3\\n4,5,6\\n\")))
  348. .. note:: FILES is implemented only for curl backend for now.
  349. As furl.el_ supports multipart POST, it should be possible to
  350. support FILES in pure elisp by making furl.el_ another backend.
  351. Contributions are welcome.
  352. .. _furl.el: https://code.google.com/p/furl-el/
  353. * PARSER function
  354. PARSER function takes no argument and it is executed in the
  355. buffer with HTTP response body. The current position in the HTTP
  356. response buffer is at the beginning of the buffer. As the HTTP
  357. header is stripped off, the cursor is actually at the beginning
  358. of the response body. So, for example, you can pass `json-read'
  359. to parse JSON object in the buffer. To fetch whole response as a
  360. string, pass `buffer-string'.
  361. When using `json-read', it is useful to know that the returned
  362. type can be modified by `json-object-type', `json-array-type',
  363. `json-key-type', `json-false' and `json-null'. See docstring of
  364. each function for what it does. For example, to convert JSON
  365. objects to plist instead of alist, wrap `json-read' by `lambda'
  366. like this.::
  367. (request
  368. \"https://...\"
  369. :parser (lambda ()
  370. (let ((json-object-type 'plist))
  371. (json-read)))
  372. ...)
  373. This is analogous to the `dataType' argument of jQuery.ajax_.
  374. Only this function can access to the process buffer, which
  375. is killed immediately after the execution of this function.
  376. * SYNC
  377. Synchronous request is functional, but *please* don't use it
  378. other than testing or debugging. Emacs users have better things
  379. to do rather than waiting for HTTP request. If you want a better
  380. way to write callback chains, use `request-deferred'.
  381. If you can't avoid using it (e.g., you are inside of some hook
  382. which must return some value), make sure to set TIMEOUT to
  383. relatively small value.
  384. Due to limitation of `url-retrieve-synchronously', response slots
  385. `request-response-error-thrown', `request-response-history' and
  386. `request-response-url' are unknown (always nil) when using
  387. synchronous request with `url-retrieve' backend.
  388. * Note
  389. API of `request' is somewhat mixture of jQuery.ajax_ (Javascript)
  390. and requests.request_ (Python).
  391. .. _jQuery.ajax: https://api.jquery.com/jQuery.ajax/
  392. .. _requests.request: https://docs.python-requests.org"
  393. (declare (indent defun))
  394. ;; FIXME: support CACHE argument (if possible)
  395. ;; (unless cache
  396. ;; (setq url (request--url-no-cache url)))
  397. (unless error
  398. (setq error (cl-function
  399. (lambda (&rest args &key symbol-status &allow-other-keys)
  400. (request-log 'error
  401. "request-default-error-callback: %s %s"
  402. url symbol-status))))
  403. (setq settings (plist-put settings :error error)))
  404. (unless (or (stringp data)
  405. (null data)
  406. (assoc-string "Content-Type" headers t))
  407. (setq data (request--urlencode-alist data))
  408. (setq settings (plist-put settings :data data)))
  409. (when params
  410. (cl-assert (listp params) nil "PARAMS must be an alist. Given: %S" params)
  411. (setq url (concat url (if (string-match-p "\\?" url) "&" "?")
  412. (request--urlencode-alist params))))
  413. (setq settings (plist-put settings :url url))
  414. (setq settings (plist-put settings :response response))
  415. (setq settings (plist-put settings :encoding encoding))
  416. (setf (request-response-settings response) settings)
  417. (setf (request-response-url response) url)
  418. (setf (request-response--backend response) request-backend)
  419. ;; Call `request--url-retrieve'(`-sync') or `request--curl'(`-sync').
  420. (apply (if sync
  421. (request--choose-backend 'request-sync)
  422. (request--choose-backend 'request))
  423. url settings)
  424. response)
  425. (defun request--clean-header (response)
  426. "Strip off carriage return in the header of RESPONSE."
  427. (let* ((buffer (request-response--buffer response))
  428. (backend (request-response--backend response))
  429. ;; FIXME: a workaround when `url-http-clean-headers' fails...
  430. (sep-regexp (if (eq backend 'url-retrieve) "^\r?$" "^\r$")))
  431. (when (buffer-live-p buffer)
  432. (with-current-buffer buffer
  433. (goto-char (point-min))
  434. (when (and (re-search-forward sep-regexp nil t)
  435. (not (equal (match-string 0) "")))
  436. (request-log 'trace "request--clean-header: cleaning\n%s"
  437. (buffer-substring (save-excursion
  438. (forward-line -1)
  439. (line-beginning-position))
  440. (save-excursion
  441. (forward-line 1)
  442. (line-end-position))))
  443. (while (re-search-backward "\r$" (point-min) t)
  444. (replace-match "")))))))
  445. (defun request--cut-header (response)
  446. "Move the header to the raw-header slot of RESPONSE object."
  447. (let ((buffer (request-response--buffer response)))
  448. (when (buffer-live-p buffer)
  449. (with-current-buffer buffer
  450. (goto-char (point-min))
  451. (when (re-search-forward "^$" nil t)
  452. (setf (request-response--raw-header response)
  453. (buffer-substring (point-min) (point)))
  454. (request-log 'trace "request--cut-header: cutting\n%s"
  455. (buffer-substring (point-min) (min (1+ (point)) (point-max))))
  456. (delete-region (point-min) (min (1+ (point)) (point-max))))))))
  457. (defun request-untrampify-filename (file)
  458. "Return FILE as the local file name."
  459. (or (file-remote-p file 'localname) file))
  460. (defun request--parse-data (response encoding parser)
  461. "In RESPONSE buffer, decode via ENCODING, then send to PARSER."
  462. (let ((buffer (request-response--buffer response)))
  463. (when (buffer-live-p buffer)
  464. (with-current-buffer buffer
  465. (request-log 'trace "request--parse-data: %s" (buffer-string))
  466. (unless (eq (request-response-status-code response) 204)
  467. (recode-region (point-min) (point-max) encoding 'no-conversion)
  468. (goto-char (point-min))
  469. (setf (request-response-data response)
  470. (if parser (funcall parser) (buffer-string))))))))
  471. (defsubst request-url-file-p (url)
  472. "Return non-nil if URL looks like a file URL."
  473. (let ((scheme (and (stringp url) (url-type (url-generic-parse-url url)))))
  474. (and (stringp scheme)
  475. (not (string-match-p "^http" scheme)))))
  476. (cl-defun request--callback (buffer
  477. &key
  478. parser success error complete
  479. status-code response
  480. encoding
  481. &allow-other-keys)
  482. "Parse BUFFER according to PARSER.
  483. Delegate to callbacks SUCCESS, ERROR, and COMPLETE the STATUS-CODE of
  484. RESPONSE via ENCODING."
  485. (request-log 'debug "request--callback: UNPARSED\n%s"
  486. (when (buffer-live-p buffer)
  487. (with-current-buffer buffer (buffer-string))))
  488. ;; Reset RESPONSE buffer to argument BUFFER.
  489. (setf (request-response--buffer response) buffer)
  490. (cl-symbol-macrolet ((timer (request-response--timer response)))
  491. (when timer
  492. (cancel-timer timer)
  493. (setq timer nil)))
  494. (cl-symbol-macrolet
  495. ((error-thrown (request-response-error-thrown response))
  496. (symbol-status (request-response-symbol-status response))
  497. (data (request-response-data response))
  498. (done-p (request-response-done-p response)))
  499. (let* ((response-url (request-response-url response))
  500. (curl-file-p (and (eq (request-response--backend response) 'curl)
  501. (request-url-file-p response-url))))
  502. (unless curl-file-p
  503. (request--clean-header response)
  504. (request--cut-header response)))
  505. ;; Parse response even if `error-thrown' is set, e.g., timeout
  506. (condition-case err
  507. (request--parse-data response encoding parser)
  508. (error (unless error-thrown (setq error-thrown err))
  509. (unless symbol-status (setq symbol-status 'parse-error))))
  510. (kill-buffer buffer)
  511. ;; Ensuring `symbol-status' and `error-thrown' are consistent
  512. ;; is why we should get rid of `symbol-status'
  513. ;; (but downstream apps might ill-advisedly rely on it).
  514. (if error-thrown
  515. (progn
  516. (request-log 'error "request--callback: %s"
  517. (error-message-string error-thrown))
  518. (unless symbol-status (setq symbol-status 'error)))
  519. (unless symbol-status (setq symbol-status 'success))
  520. (request-log 'debug "request--callback: PARSED\n%s" data))
  521. (let ((args (list :data data
  522. :symbol-status symbol-status
  523. :error-thrown error-thrown
  524. :response response)))
  525. (let* ((success-p (eq symbol-status 'success))
  526. (cb (if success-p success error))
  527. (name (if success-p "success" "error")))
  528. (when cb
  529. (request-log 'debug "request--callback: executing %s" name)
  530. (apply cb args)))
  531. (let ((cb (cdr (assq (request-response-status-code response)
  532. status-code))))
  533. (when cb
  534. (request-log 'debug "request--callback: executing status-code")
  535. (apply cb args)))
  536. (when complete
  537. (request-log 'debug "request--callback: executing complete")
  538. (apply complete args)))
  539. (setq done-p t)))
  540. (cl-defun request-response--timeout-callback (response)
  541. "If RESPONSE times out, ensure `request--callback' gets called."
  542. (setf (request-response-symbol-status response) 'timeout)
  543. (setf (request-response-error-thrown response) '(error . ("Timeout")))
  544. (let* ((buffer (request-response--buffer response))
  545. (proc (and (buffer-live-p buffer) (get-buffer-process buffer))))
  546. (if proc
  547. ;; This implicitly calls `request--callback'!
  548. (funcall (request--choose-backend 'terminate-process) proc)
  549. (cl-symbol-macrolet ((done-p (request-response-done-p response)))
  550. (unless done-p
  551. (when (buffer-live-p buffer)
  552. (cl-destructuring-bind (&key code &allow-other-keys)
  553. (with-current-buffer buffer
  554. (goto-char (point-min))
  555. (request--parse-response-at-point))
  556. (setf (request-response-status-code response) code)))
  557. (apply #'request--callback
  558. buffer
  559. (request-response-settings response))
  560. (setq done-p t))))))
  561. (defun request-abort (response)
  562. "Abort request for RESPONSE (the object returned by `request').
  563. Note that this function invoke ERROR and COMPLETE callbacks.
  564. Callbacks may not be called immediately but called later when
  565. associated process is exited."
  566. (cl-symbol-macrolet ((buffer (request-response--buffer response))
  567. (symbol-status (request-response-symbol-status response))
  568. (done-p (request-response-done-p response)))
  569. (let ((process (get-buffer-process buffer)))
  570. (unless symbol-status ; should I use done-p here?
  571. (setq symbol-status 'abort)
  572. (setq done-p t)
  573. (when (process-live-p process)
  574. (funcall (request--choose-backend 'terminate-process) process))))))
  575. (cl-defun request--url-retrieve-preprocess-settings
  576. (&rest settings &key type data files headers &allow-other-keys)
  577. "Augment SETTINGS with properties TYPE DATA FILES HEADERS."
  578. (when files
  579. (error "`url-retrieve' backend does not support FILES"))
  580. (when (and (equal type "POST")
  581. data
  582. (not (assoc-string "Content-Type" headers t)))
  583. (push '("Content-Type" . "application/x-www-form-urlencoded") headers)
  584. (setq settings (plist-put settings :headers headers)))
  585. settings)
  586. (cl-defun request--url-retrieve (url &rest settings
  587. &key type data timeout response
  588. &allow-other-keys
  589. &aux headers)
  590. "Internal workhorse querying URL via curl.
  591. SETTINGS is a property list with keys (some optional) such as GET or POST TYPE,
  592. DATA for posting fields, TIMEOUT in seconds, RESPONSE a mandatory struct.
  593. HEADERS needs to be assigned after SETTINGS is preprocessed."
  594. (setq settings (apply #'request--url-retrieve-preprocess-settings settings))
  595. (setq headers (plist-get settings :headers))
  596. (let* ((url-request-extra-headers headers)
  597. (url-request-method type)
  598. (url-request-data data)
  599. (buffer (url-retrieve url #'request--url-retrieve-callback
  600. (nconc (list :response response) settings) t))
  601. (proc (get-buffer-process buffer)))
  602. (request--install-timeout timeout response)
  603. (setf (request-response--buffer response) buffer)
  604. (process-put proc :request-response response)
  605. (set-process-query-on-exit-flag proc nil)))
  606. (cl-defun request--url-retrieve-callback (status &rest settings
  607. &key response url
  608. &allow-other-keys)
  609. "Ensure `request--callback' gets called for STATUS.
  610. SETTINGS should include RESPONSE and URL properties which
  611. inform any necessary redirect or history recording logic."
  612. (when (featurep 'url-http)
  613. (setf (request-response-status-code response) url-http-response-status))
  614. (let ((redirect (plist-get status :redirect)))
  615. (when redirect
  616. (setf (request-response-url response) redirect)))
  617. ;; Construct history slot
  618. (cl-loop for v in
  619. (cl-loop with first = t
  620. with l = nil
  621. for (k v) on status by 'cddr
  622. when (eq k :redirect)
  623. if first
  624. do (setq first nil)
  625. else
  626. do (push v l)
  627. finally do (cons url l))
  628. do (let ((r (make-request-response :-backend 'url-retrieve)))
  629. (setf (request-response-url r) v)
  630. (push r (request-response-history response))))
  631. (cl-symbol-macrolet ((error-thrown (request-response-error-thrown response))
  632. (status-error (plist-get status :error)))
  633. (when status-error
  634. (request-log 'warn "request--url-retrieve-callback: %s" status-error)
  635. (unless error-thrown
  636. (setq error-thrown status-error))))
  637. (apply #'request--callback (current-buffer) settings))
  638. (cl-defun request--url-retrieve-sync (url &rest settings
  639. &key type data timeout response
  640. &allow-other-keys
  641. &aux headers)
  642. "Internal synchronous retrieve of URL.
  643. SETTINGS include typical TYPE DATA TIMEOUT RESPONSE properties.
  644. HEADERS needs to be assigned after SETTINGS is preprocessed."
  645. (setq settings (apply #'request--url-retrieve-preprocess-settings settings))
  646. (setq headers (plist-get settings :headers))
  647. (let* ((url-request-extra-headers headers)
  648. (url-request-method type)
  649. (url-request-data data)
  650. (buffer (if timeout
  651. (with-timeout
  652. (timeout
  653. (setf (request-response-symbol-status response)
  654. 'timeout)
  655. (setf (request-response-done-p response) t)
  656. nil)
  657. (url-retrieve-synchronously url t))
  658. (url-retrieve-synchronously url t))))
  659. (setf (request-response--buffer response) buffer)
  660. ;; It seems there is no way to get redirects and URL here...
  661. (when buffer
  662. ;; Fetch HTTP response code
  663. (with-current-buffer buffer
  664. (goto-char (point-min))
  665. (cl-destructuring-bind (&key code &allow-other-keys)
  666. (request--parse-response-at-point)
  667. (setf (request-response-status-code response) code)))
  668. ;; Parse response body, etc.
  669. (apply #'request--callback buffer settings)))
  670. response)
  671. (defun request--url-retrieve-get-cookies (host localpart secure)
  672. "Retrieve cookies corresponding to HOST LOCALPART SECURE."
  673. (mapcar
  674. (lambda (c) (cons (url-cookie-name c) (url-cookie-value c)))
  675. (url-cookie-retrieve host localpart secure)))
  676. (defvar request--curl-cookie-jar nil
  677. "Override what the function `request--curl-cookie-jar' returns.
  678. Currently it is used only for testing.")
  679. (defun request--curl-cookie-jar ()
  680. "Cookie storage for curl backend."
  681. (or request--curl-cookie-jar
  682. (expand-file-name "curl-cookie-jar" request-storage-directory)))
  683. (defvar request--curl-capabilities-cache
  684. (make-hash-table :test 'eq :weakness 'key)
  685. "Used to avoid invoking curl more than once for version info. By skeeto/elfeed.")
  686. (defun request--curl-capabilities ()
  687. "Return capabilities plist for curl. By skeeto/elfeed.
  688. :version -- cURL's version string
  689. :compression -- non-nil if --compressed is supported."
  690. (let ((cache-value (gethash request-curl request--curl-capabilities-cache)))
  691. (if cache-value
  692. cache-value
  693. (with-temp-buffer
  694. (call-process request-curl nil t nil "--version")
  695. (let ((version
  696. (progn
  697. (setf (point) (point-min))
  698. (when (re-search-forward "[.0-9]+" nil t)
  699. (match-string 0))))
  700. (compression
  701. (progn
  702. (setf (point) (point-min))
  703. (not (null (re-search-forward "libz\\>" nil t))))))
  704. (setf (gethash request-curl request--curl-capabilities-cache)
  705. `(:version ,version :compression ,compression)))))))
  706. (defconst request--curl-write-out-template
  707. (if (eq system-type 'windows-nt)
  708. "\\n(:num-redirects %{num_redirects} :url-effective %{url_effective})"
  709. "\\n(:num-redirects %{num_redirects} :url-effective \"%{url_effective}\")"))
  710. (cl-defun request--curl-command
  711. (url &key type data headers files unix-socket auth
  712. &allow-other-keys
  713. &aux (cookie-jar (convert-standard-filename
  714. (expand-file-name (request--curl-cookie-jar)))))
  715. "Internal command cobbler for curl to URL.
  716. TYPE, DATA, HEADERS, FILES, UNIX-SOCKET, AUTH are as described in `request'.
  717. COOKIE-JAR is the file location for the netscape cookie jar, usually
  718. in the request subdirectory of `user-emacs-directory'.
  719. BUG: Simultaneous requests are a known cause of cookie-jar corruption."
  720. (append
  721. (list request-curl
  722. "--silent" "--location"
  723. "--cookie" cookie-jar "--cookie-jar" cookie-jar)
  724. (when auth
  725. (let* ((host (url-host (url-generic-parse-url url)))
  726. (auth-source-creation-prompts `((user . ,(format "%s user: " host))
  727. (secret . "Password for %u: ")))
  728. (cred (car (auth-source-search
  729. :host host :require '(:user :secret) :create t :max 1))))
  730. (split-string (format "--%s --user %s:%s"
  731. auth
  732. (plist-get cred :user)
  733. (let ((secret (plist-get cred :secret)))
  734. (if (functionp secret)
  735. (funcall secret)
  736. secret))))))
  737. (unless (request-url-file-p url)
  738. (list "--include" "--write-out" request--curl-write-out-template))
  739. request-curl-options
  740. (when (plist-get (request--curl-capabilities) :compression) (list "--compressed"))
  741. (when unix-socket (list "--unix-socket" unix-socket))
  742. (cl-loop with stdin-p = data
  743. for (name . item) in files
  744. collect "--form"
  745. collect
  746. (apply #'format "%s=@%s;filename=%s%s"
  747. (cond ((stringp item)
  748. (list name item (file-name-nondirectory item) ""))
  749. ((bufferp item)
  750. (if stdin-p
  751. (error (concat "request--curl-command: "
  752. "only one buffer or data entry permitted"))
  753. (setq stdin-p t))
  754. (list name "-" (buffer-name item) ""))
  755. ((listp item)
  756. (unless (plist-get (cdr item) :file)
  757. (if stdin-p
  758. (error (concat "request--curl-command: "
  759. "only one buffer or data entry permitted"))
  760. (setq stdin-p t)))
  761. (list name (or (plist-get (cdr item) :file) "-") (car item)
  762. (if (plist-get item :mime-type)
  763. (format ";type=%s" (plist-get item :mime-type))
  764. "")))
  765. (t (error (concat "request--curl-command: "
  766. "%S not string, buffer, or list")
  767. item)))))
  768. (when data
  769. (split-string "--data-binary @-"))
  770. (when type (if (equal "head" (downcase type))
  771. (list "--head")
  772. (list "--request" type)))
  773. (cl-loop for (k . v) in headers
  774. collect "--header"
  775. collect (format "%s: %s" k v))
  776. (list url)))
  777. (defun request--install-timeout (timeout response)
  778. "Out-of-band trigger after TIMEOUT seconds to forestall a hung RESPONSE."
  779. (when (numberp timeout)
  780. (setf (request-response--timer response)
  781. (run-at-time timeout nil
  782. #'request-response--timeout-callback response))))
  783. (defun request--curl-occlude-secret (command)
  784. "Simple regex filter on anything looking like a secret in COMMAND."
  785. (let ((matched
  786. (string-match (concat (regexp-quote "--user") "\\s-*\\(\\S-+\\)") command)))
  787. (if matched
  788. (replace-match "elided" nil nil command 1)
  789. command)))
  790. (cl-defun request--curl (url &rest settings
  791. &key data files timeout response encoding semaphore
  792. &allow-other-keys)
  793. "Internal workhorse querying URL via curl.
  794. SETTINGS is a property list with keys (some optional) such as DATA for
  795. posting fields, FILES containing one or more lists of the form
  796. (NAME . FILENAME)
  797. (NAME . BUFFER)
  798. (NAME . (FILENAME :buffer BUFFER))
  799. (NAME . (FILENAME :data DATA))
  800. with NAME and FILENAME defined by curl(1)'s overwrought `--form` switch format,
  801. TIMEOUT in seconds, RESPONSE a mandatory struct, ENCODING, and SEMAPHORE,
  802. an internal semaphore.
  803. Redirection handling strategy
  804. -----------------------------
  805. curl follows redirection when --location is given. However,
  806. all headers are printed when it is used with --include option.
  807. Number of redirects is printed out sexp-based message using
  808. --write-out option (see `request--curl-write-out-template').
  809. This number is used for removing extra headers and parse
  810. location header from the last redirection header.
  811. Sexp at the end of buffer and extra headers for redirects are
  812. removed from the buffer before it is shown to the parser function."
  813. (ignore-errors
  814. (make-directory (file-name-directory (request--curl-cookie-jar)) t))
  815. (let* (process-connection-type ;; pipe, not pty, else curl hangs
  816. (home-directory (or (file-remote-p default-directory) "~/"))
  817. (default-directory (expand-file-name home-directory))
  818. (buffer (generate-new-buffer " *request curl*"))
  819. (command (apply #'request--curl-command url settings))
  820. (proc (apply #'start-process "request curl" buffer command))
  821. (file-items (mapcar #'cdr files))
  822. (file-buffer (or (cl-some (lambda (item)
  823. (when (bufferp item) item))
  824. file-items)
  825. (cl-some (lambda (item)
  826. (and (listp item)
  827. (plist-get (cdr item) :buffer)))
  828. file-items)))
  829. (file-data (cl-some (lambda (item)
  830. (and (listp item)
  831. (plist-get (cdr item) :data)))
  832. file-items)))
  833. (request--install-timeout timeout response)
  834. (request-log 'debug "request--curl: %s"
  835. (request--curl-occlude-secret (mapconcat #'identity command " ")))
  836. (setf (request-response--buffer response) buffer)
  837. (process-put proc :request-response response)
  838. (set-process-coding-system proc 'no-conversion 'no-conversion)
  839. (set-process-query-on-exit-flag proc nil)
  840. (when (or data file-buffer file-data)
  841. ;; We dynamic-let the global `buffer-file-coding-system' to `no-conversion'
  842. ;; in case the user-configured `encoding' doesn't fly.
  843. ;; If we do not dynamic-let the global, `select-safe-coding-system' would
  844. ;; plunge us into an undesirable interactive dialogue.
  845. (let* ((buffer-file-coding-system-orig
  846. (default-value 'buffer-file-coding-system))
  847. (select-safe-coding-system-accept-default-p
  848. (lambda (&rest _) t)))
  849. (unwind-protect
  850. (progn
  851. (setf (default-value 'buffer-file-coding-system) 'no-conversion)
  852. (with-temp-buffer
  853. (setq-local buffer-file-coding-system encoding)
  854. (insert (or data
  855. (when file-buffer
  856. (with-current-buffer file-buffer
  857. (buffer-substring-no-properties (point-min) (point-max))))
  858. file-data))
  859. (process-send-region proc (point-min) (point-max))
  860. (process-send-eof proc)))
  861. (setf (default-value 'buffer-file-coding-system)
  862. buffer-file-coding-system-orig))))
  863. (let ((callback-2 (apply-partially #'request--curl-callback url)))
  864. (if semaphore
  865. (set-process-sentinel proc (lambda (&rest args)
  866. (apply callback-2 args)
  867. (apply semaphore args)))
  868. (set-process-sentinel proc callback-2)))))
  869. (defun request--curl-read-and-delete-tail-info ()
  870. "Read a sexp at the end of buffer and remove it and preceding character.
  871. This function moves the point at the end of buffer by side effect.
  872. See also `request--curl-write-out-template'."
  873. (let (forward-sexp-function)
  874. (goto-char (point-max))
  875. (forward-sexp -1)
  876. (let ((beg (1- (point))))
  877. (prog1
  878. (read (current-buffer))
  879. (delete-region beg (point-max))))))
  880. (defconst request--cookie-reserved-re
  881. (mapconcat
  882. (lambda (x) (concat "\\(^" x "\\'\\)"))
  883. '("comment" "commenturl" "discard" "domain" "max-age" "path" "port"
  884. "secure" "version" "expires")
  885. "\\|")
  886. "Uninterested keys in cookie.
  887. See \"set-cookie-av\" in https://www.ietf.org/rfc/rfc2965.txt")
  888. (defun request--consume-100-continue ()
  889. "Remove \"HTTP/* 100 Continue\" header at the point."
  890. (cl-destructuring-bind (&key code &allow-other-keys)
  891. (save-excursion (request--parse-response-at-point))
  892. (when (equal code 100)
  893. (request-log 'debug "request--consume-100-continue: consuming\n%s"
  894. (buffer-substring (point)
  895. (save-excursion
  896. (request--goto-next-body t)
  897. (point))))
  898. (delete-region (point) (progn (request--goto-next-body) (point)))
  899. ;; FIXME: Does this make sense? Is it possible to have multiple 100?
  900. (request--consume-100-continue))))
  901. (defun request--consume-200-connection-established ()
  902. "Remove \"HTTP/* 200 Connection established\" header at the point."
  903. (when (looking-at-p "HTTP/1\\.[0-1] 200 Connection established")
  904. (delete-region (point) (progn (request--goto-next-body) (point)))))
  905. (defun request--curl-preprocess (&optional url)
  906. "Pre-process current buffer before showing it to user.
  907. Curl switches need to be adjusted if URL is a file://."
  908. (let (history)
  909. (cl-destructuring-bind (&key num-redirects url-effective)
  910. (if (request-url-file-p url)
  911. `(:num-redirects 0 :url-effective ,url)
  912. (request--curl-read-and-delete-tail-info))
  913. (goto-char (point-min))
  914. (request--consume-100-continue)
  915. (request--consume-200-connection-established)
  916. (when (> num-redirects 0)
  917. (cl-loop with case-fold-search = t
  918. repeat num-redirects
  919. ;; Do not store code=100 headers:
  920. do (request--consume-100-continue)
  921. do (let ((response (make-request-response
  922. :-buffer (current-buffer)
  923. :-backend 'curl)))
  924. (request--clean-header response)
  925. (request--cut-header response)
  926. (push response history))))
  927. (goto-char (point-min))
  928. (nconc (list :num-redirects num-redirects :url-effective url-effective
  929. :history (nreverse history))
  930. (request--parse-response-at-point)))))
  931. (defun request--curl-absolutify-redirects (start-url redirects)
  932. "Convert relative paths in REDIRECTS to absolute URLs.
  933. START-URL is the URL requested."
  934. (cl-loop for prev-url = start-url then url
  935. for url in redirects
  936. unless (string-match url-nonrelative-link url)
  937. do (setq url (url-expand-file-name url prev-url))
  938. collect url))
  939. (defun request--curl-absolutify-location-history (start-url history)
  940. "Convert relative paths in HISTORY to absolute URLs.
  941. START-URL is the URL requested."
  942. (when history
  943. (setf (request-response-url (car history)) start-url))
  944. (cl-loop for url in (request--curl-absolutify-redirects
  945. start-url
  946. (mapcar (lambda (response)
  947. (or (request-response-header response "location")
  948. (request-response-url response)))
  949. history))
  950. for response in (cdr history)
  951. do (setf (request-response-url response) url)))
  952. (defun request--curl-callback (url proc event)
  953. "Ensure `request--callback' gets called after curl to URL finishes.
  954. See info entries on sentinels regarding PROC and EVENT."
  955. (let* ((buffer (process-buffer proc))
  956. (response (process-get proc :request-response))
  957. (settings (request-response-settings response)))
  958. (request-log 'debug "request--curl-callback: event %s" event)
  959. (request-log 'trace "request--curl-callback: raw-bytes=\n%s"
  960. (when (buffer-live-p buffer)
  961. (with-current-buffer buffer (buffer-string))))
  962. (cond
  963. ((and (memq (process-status proc) '(exit signal))
  964. (/= (process-exit-status proc) 0))
  965. (setf (request-response-error-thrown response) (cons 'error event))
  966. (apply #'request--callback buffer settings))
  967. ((cl-search "finished" event)
  968. (cl-destructuring-bind (&key code history error url-effective &allow-other-keys)
  969. (condition-case err
  970. (with-current-buffer buffer
  971. (request--curl-preprocess url))
  972. ((debug error)
  973. (list :error err)))
  974. (request--curl-absolutify-location-history (plist-get settings :url)
  975. history)
  976. (setf (request-response-status-code response) code)
  977. (setf (request-response-url response) url-effective)
  978. (setf (request-response-history response) history)
  979. (setf (request-response-error-thrown response)
  980. (or error (and (numberp code) (>= code 400) `(error . (http ,code)))))
  981. (apply #'request--callback buffer settings))))))
  982. (defun request-auto-revert-notify-rm-watch ()
  983. "Backport of M. Engdegard's fix of `auto-revert-notify-rm-watch'."
  984. (let ((desc auto-revert-notify-watch-descriptor)
  985. (table (if (boundp 'auto-revert--buffers-by-watch-descriptor)
  986. auto-revert--buffers-by-watch-descriptor
  987. (when (boundp 'auto-revert-notify-watch-descriptor-hash-list)
  988. auto-revert-notify-watch-descriptor-hash-list))))
  989. (when (and desc table)
  990. (let ((buffers (delq (current-buffer) (gethash desc table))))
  991. (if buffers
  992. (puthash desc buffers table)
  993. (remhash desc table)))
  994. (condition-case nil ;; ignore-errors doesn't work for me, sorry
  995. (file-notify-rm-watch desc)
  996. (error))
  997. (remove-hook 'kill-buffer-hook #'auto-revert-notify-rm-watch t)))
  998. (setq auto-revert-notify-watch-descriptor nil
  999. auto-revert-notify-modified-p nil))
  1000. (cl-defun request--curl-sync (url &rest settings &key response &allow-other-keys)
  1001. "Internal synchronous curl call to URL with SETTINGS bespeaking RESPONSE."
  1002. (let (finished)
  1003. (prog1 (apply #'request--curl url
  1004. :semaphore (lambda (&rest _) (setq finished t))
  1005. settings)
  1006. (let* ((proc (get-buffer-process (request-response--buffer response)))
  1007. (interval 0.05)
  1008. (timeout 5)
  1009. (maxiter (truncate (/ timeout interval))))
  1010. (auto-revert-set-timer)
  1011. (when auto-revert-use-notify
  1012. (dolist (buf (buffer-list))
  1013. (with-current-buffer buf
  1014. (request-auto-revert-notify-rm-watch))))
  1015. (with-local-quit
  1016. (cl-loop with iter = 0
  1017. until (or (>= iter maxiter) finished)
  1018. do (accept-process-output nil interval)
  1019. unless (process-live-p proc)
  1020. do (cl-incf iter)
  1021. end
  1022. finally (when (>= iter maxiter)
  1023. (let ((m "request--curl-sync: semaphore never called"))
  1024. (princ (format "%s\n" m) #'external-debugging-output)
  1025. (request-log 'error m)))))))))
  1026. (defun request--curl-get-cookies (host localpart secure)
  1027. "Return entry for HOST LOCALPART SECURE in cookie jar."
  1028. (request--netscape-get-cookies (request--curl-cookie-jar)
  1029. host localpart secure))
  1030. (defun request--netscape-cookie-parse ()
  1031. "Parse Netscape/Mozilla cookie format."
  1032. (goto-char (point-min))
  1033. (let ((tsv-re (concat "^\\(#HttpOnly_\\)?"
  1034. (cl-loop repeat 6 concat "\\([^\t\n]+\\)\t")
  1035. "\\(.*\\)"))
  1036. cookies)
  1037. (while (not (eobp))
  1038. ;; HttpOnly cookie starts with '#' but its line is not comment line(#60)
  1039. (cond ((and (looking-at-p "^#") (not (looking-at-p "^#HttpOnly_"))) t)
  1040. ((looking-at-p "^$") t)
  1041. ((looking-at tsv-re)
  1042. (let ((cookie (cl-loop for i from 1 to 8 collect (match-string i))))
  1043. (push cookie cookies))))
  1044. (forward-line 1))
  1045. (setq cookies (nreverse cookies))
  1046. (cl-loop for (http-only domain flag path secure expiration name value) in cookies
  1047. collect (list domain
  1048. (equal flag "TRUE")
  1049. path
  1050. (equal secure "TRUE")
  1051. (null (not http-only))
  1052. (string-to-number expiration)
  1053. name
  1054. value))))
  1055. (defun request--netscape-filter-cookies (cookies host localpart secure)
  1056. "Filter COOKIES for entries containing HOST LOCALPART SECURE."
  1057. (cl-loop for (domain _flag path secure-1 _http-only _expiration name value) in cookies
  1058. when (and (equal domain host)
  1059. (equal path localpart)
  1060. (or secure (not secure-1)))
  1061. collect (cons name value)))
  1062. (defun request--netscape-get-cookies (filename host localpart secure)
  1063. "Get cookies from FILENAME corresponding to HOST LOCALPART SECURE."
  1064. (when (file-readable-p filename)
  1065. (with-temp-buffer
  1066. (erase-buffer)
  1067. (insert-file-contents filename)
  1068. (request--netscape-filter-cookies (request--netscape-cookie-parse)
  1069. host localpart secure))))
  1070. (provide 'request)
  1071. ;;; request.el ends here