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.

1311 lines
52 KiB

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