(org-level-color-stars-only): Fix typo in doc string.
[bpt/emacs.git] / lisp / url / url-http.el
CommitLineData
8c8b8430 1;;; url-http.el --- HTTP retrieval routines
b43eb9ab 2
71ddfde5 3;; Copyright (C) 1999, 2001, 2004, 2005 Free Software Foundation, Inc.
b43eb9ab 4
8c8b8430 5;; Author: Bill Perry <wmperry@gnu.org>
8c8b8430 6;; Keywords: comm, data, processes
3f19601e 7
b43eb9ab
SM
8;; This file is part of GNU Emacs.
9;;
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14;;
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19;;
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
4fc5845f
LK
22;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23;; Boston, MA 02110-1301, USA.
8c8b8430 24
b43eb9ab
SM
25;;; Commentary:
26
27;;; Code:
8c8b8430
SM
28
29(eval-when-compile
30 (require 'cl)
2344ffc1
JB
31 (defvar url-http-extra-headers)
32 (defvar url-http-cookies-sources))
8c8b8430
SM
33(require 'url-gw)
34(require 'url-util)
35(require 'url-parse)
36(require 'url-cookie)
37(require 'mail-parse)
38(require 'url-auth)
39(autoload 'url-retrieve-synchronously "url")
40(autoload 'url-retrieve "url")
41(autoload 'url-cache-create-filename "url-cache")
42(autoload 'url-mark-buffer-as-dead "url")
43
44(defconst url-http-default-port 80 "Default HTTP port.")
45(defconst url-http-asynchronous-p t "HTTP retrievals are asynchronous.")
46(defalias 'url-http-expand-file-name 'url-default-expander)
47
48(defvar url-http-real-basic-auth-storage nil)
49(defvar url-http-proxy-basic-auth-storage nil)
50
51(defvar url-http-open-connections (make-hash-table :test 'equal
52 :size 17)
53 "A hash table of all open network connections.")
54
55(defvar url-http-version "1.1"
56 "What version of HTTP we advertise, as a string.
57Valid values are 1.1 and 1.0.
58This is only useful when debugging the HTTP subsystem.
59
60Setting this to 1.0 will tell servers not to send chunked encoding,
61and other HTTP/1.1 specific features.
62")
63
64(defvar url-http-attempt-keepalives t
65 "Whether to use a single TCP connection multiple times in HTTP.
66This is only useful when debugging the HTTP subsystem. Setting to
67`nil' will explicitly close the connection to the server after every
68request.
69")
70
71;(eval-when-compile
72;; These are all macros so that they are hidden from external sight
73;; when the file is byte-compiled.
74;;
75;; This allows us to expose just the entry points we want.
76
77;; These routines will allow us to implement persistent HTTP
78;; connections.
79(defsubst url-http-debug (&rest args)
80 (if quit-flag
81 (let ((proc (get-buffer-process (current-buffer))))
82 ;; The user hit C-g, honor it! Some things can get in an
83 ;; incredibly tight loop (chunked encoding)
84 (if proc
85 (progn
86 (set-process-sentinel proc nil)
87 (set-process-filter proc nil)))
88 (error "Transfer interrupted!")))
89 (apply 'url-debug 'http args))
90
91(defun url-http-mark-connection-as-busy (host port proc)
92 (url-http-debug "Marking connection as busy: %s:%d %S" host port proc)
93 (puthash (cons host port)
94 (delq proc (gethash (cons host port) url-http-open-connections))
95 url-http-open-connections)
96 proc)
97
98(defun url-http-mark-connection-as-free (host port proc)
99 (url-http-debug "Marking connection as free: %s:%d %S" host port proc)
100 (set-process-buffer proc nil)
101 (set-process-sentinel proc 'url-http-idle-sentinel)
102 (puthash (cons host port)
103 (cons proc (gethash (cons host port) url-http-open-connections))
104 url-http-open-connections)
105 nil)
106
107(defun url-http-find-free-connection (host port)
108 (let ((conns (gethash (cons host port) url-http-open-connections))
109 (found nil))
110 (while (and conns (not found))
111 (if (not (memq (process-status (car conns)) '(run open)))
112 (progn
113 (url-http-debug "Cleaning up dead process: %s:%d %S"
114 host port (car conns))
115 (url-http-idle-sentinel (car conns) nil))
116 (setq found (car conns))
117 (url-http-debug "Found existing connection: %s:%d %S" host port found))
118 (pop conns))
119 (if found
120 (url-http-debug "Reusing existing connection: %s:%d" host port)
121 (url-http-debug "Contacting host: %s:%d" host port))
122 (url-lazy-message "Contacting host: %s:%d" host port)
123 (url-http-mark-connection-as-busy host port
124 (or found
125 (url-open-stream host nil host
126 port)))))
127
128;; Building an HTTP request
129(defun url-http-user-agent-string ()
130 (if (or (eq url-privacy-level 'paranoid)
131 (and (listp url-privacy-level)
132 (memq 'agent url-privacy-level)))
133 ""
134 (format "User-Agent: %sURL/%s%s\r\n"
135 (if url-package-name
136 (concat url-package-name "/" url-package-version " ")
137 "")
138 url-version
139 (cond
140 ((and url-os-type url-system-type)
141 (concat " (" url-os-type "; " url-system-type ")"))
142 ((or url-os-type url-system-type)
143 (concat " (" (or url-system-type url-os-type) ")"))
144 (t "")))))
145
146(defun url-http-create-request (url &optional ref-url)
147 "Create an HTTP request for URL, referred to by REF-URL."
148 (declare (special proxy-object proxy-info))
149 (let* ((extra-headers)
150 (request nil)
151 (no-cache (cdr-safe (assoc "Pragma" url-request-extra-headers)))
152 (proxy-obj (and (boundp 'proxy-object) proxy-object))
153 (proxy-auth (if (or (cdr-safe (assoc "Proxy-Authorization"
154 url-request-extra-headers))
155 (not proxy-obj))
156 nil
157 (let ((url-basic-auth-storage
158 'url-http-proxy-basic-auth-storage))
159 (url-get-authentication url nil 'any nil))))
e893ce91 160 (real-fname (url-filename (or proxy-obj url)))
8c8b8430
SM
161 (host (url-host (or proxy-obj url)))
162 (auth (if (cdr-safe (assoc "Authorization" url-request-extra-headers))
163 nil
164 (url-get-authentication (or
165 (and (boundp 'proxy-info)
166 proxy-info)
167 url) nil 'any nil))))
168 (if (equal "" real-fname)
169 (setq real-fname "/"))
170 (setq no-cache (and no-cache (string-match "no-cache" no-cache)))
171 (if auth
172 (setq auth (concat "Authorization: " auth "\r\n")))
173 (if proxy-auth
174 (setq proxy-auth (concat "Proxy-Authorization: " proxy-auth "\r\n")))
175
176 ;; Protection against stupid values in the referer
177 (if (and ref-url (stringp ref-url) (or (string= ref-url "file:nil")
178 (string= ref-url "")))
179 (setq ref-url nil))
180
181 ;; We do not want to expose the referer if the user is paranoid.
182 (if (or (memq url-privacy-level '(low high paranoid))
183 (and (listp url-privacy-level)
184 (memq 'lastloc url-privacy-level)))
185 (setq ref-url nil))
186
187 ;; url-request-extra-headers contains an assoc-list of
188 ;; header/value pairs that we need to put into the request.
189 (setq extra-headers (mapconcat
190 (lambda (x)
191 (concat (car x) ": " (cdr x)))
192 url-request-extra-headers "\r\n"))
193 (if (not (equal extra-headers ""))
194 (setq extra-headers (concat extra-headers "\r\n")))
195
196 ;; This was done with a call to `format'. Concatting parts has
1430e7f9 197 ;; the advantage of keeping the parts of each header together and
8c8b8430
SM
198 ;; allows us to elide null lines directly, at the cost of making
199 ;; the layout less clear.
200 (setq request
1430e7f9
SM
201 ;; We used to concat directly, but if one of the strings happens
202 ;; to being multibyte (even if it only contains pure ASCII) then
203 ;; every string gets converted with `string-MAKE-multibyte' which
204 ;; turns the 127-255 codes into things like latin-1 accented chars
205 ;; (it would work right if it used `string-TO-multibyte' instead).
206 ;; So to avoid the problem we force every string to be unibyte.
207 (mapconcat
208 ;; FIXME: Instead of `string-AS-unibyte' we'd want
209 ;; `string-to-unibyte', so as to properly signal an error if one
210 ;; of the strings contains a multibyte char.
211 'string-as-unibyte
212 (delq nil
213 (list
214 ;; The request
215 (or url-request-method "GET") " "
216 (if proxy-obj (url-recreate-url proxy-obj) real-fname)
217 " HTTP/" url-http-version "\r\n"
218 ;; Version of MIME we speak
219 "MIME-Version: 1.0\r\n"
220 ;; (maybe) Try to keep the connection open
221 "Connection: " (if (or proxy-obj
222 (not url-http-attempt-keepalives))
223 "close" "keep-alive") "\r\n"
224 ;; HTTP extensions we support
225 (if url-extensions-header
226 (format
227 "Extension: %s\r\n" url-extensions-header))
228 ;; Who we want to talk to
229 (if (/= (url-port (or proxy-obj url))
230 (url-scheme-get-property
231 (url-type (or proxy-obj url)) 'default-port))
232 (format
233 "Host: %s:%d\r\n" host (url-port (or proxy-obj url)))
234 (format "Host: %s\r\n" host))
235 ;; Who its from
236 (if url-personal-mail-address
237 (concat
238 "From: " url-personal-mail-address "\r\n"))
239 ;; Encodings we understand
240 (if url-mime-encoding-string
241 (concat
242 "Accept-encoding: " url-mime-encoding-string "\r\n"))
243 (if url-mime-charset-string
244 (concat
245 "Accept-charset: " url-mime-charset-string "\r\n"))
246 ;; Languages we understand
247 (if url-mime-language-string
248 (concat
249 "Accept-language: " url-mime-language-string "\r\n"))
250 ;; Types we understand
251 "Accept: " (or url-mime-accept-string "*/*") "\r\n"
252 ;; User agent
253 (url-http-user-agent-string)
254 ;; Proxy Authorization
255 proxy-auth
256 ;; Authorization
257 auth
258 ;; Cookies
259 (url-cookie-generate-header-lines host real-fname
260 (equal "https" (url-type url)))
261 ;; If-modified-since
262 (if (and (not no-cache)
263 (member url-request-method '("GET" nil)))
264 (let ((tm (url-is-cached (or proxy-obj url))))
265 (if tm
266 (concat "If-modified-since: "
267 (url-get-normalized-date tm) "\r\n"))))
268 ;; Whence we came
269 (if ref-url (concat
270 "Referer: " ref-url "\r\n"))
271 extra-headers
272 ;; Length of data
273 (if url-request-data
274 (concat
275 "Content-length: " (number-to-string
276 (length url-request-data))
277 "\r\n"))
278 ;; End request
279 "\r\n"
280 ;; Any data
281 url-request-data))
282 ""))
8c8b8430
SM
283 (url-http-debug "Request is: \n%s" request)
284 request))
285
286;; Parsing routines
287(defun url-http-clean-headers ()
288 "Remove trailing \r from header lines.
289This allows us to use `mail-fetch-field', etc."
290 (declare (special url-http-end-of-headers))
291 (goto-char (point-min))
292 (while (re-search-forward "\r$" url-http-end-of-headers t)
293 (replace-match "")))
294
295(defun url-http-handle-authentication (proxy)
296 (declare (special status success url-http-method url-http-data
297 url-callback-function url-callback-arguments))
298 (url-http-debug "Handling %s authentication" (if proxy "proxy" "normal"))
299 (let ((auth (or (mail-fetch-field (if proxy "proxy-authenticate" "www-authenticate"))
300 "basic"))
301 (type nil)
302 (url (url-recreate-url url-current-object))
303 (url-basic-auth-storage 'url-http-real-basic-auth-storage)
304 )
305
306 ;; Cheating, but who cares? :)
307 (if proxy
308 (setq url-basic-auth-storage 'url-http-proxy-basic-auth-storage))
309
310 (setq auth (url-eat-trailing-space (url-strip-leading-spaces auth)))
311 (if (string-match "[ \t]" auth)
312 (setq type (downcase (substring auth 0 (match-beginning 0))))
313 (setq type (downcase auth)))
314
315 (if (not (url-auth-registered type))
316 (progn
317 (widen)
318 (goto-char (point-max))
319 (insert "<hr>Sorry, but I do not know how to handle " type
320 " authentication. If you'd like to write it,"
321 " send it to " url-bug-address ".<hr>")
322 (setq status t))
323 (let* ((args auth)
324 (ctr (1- (length args)))
325 auth)
326 (while (/= 0 ctr)
327 (if (char-equal ?, (aref args ctr))
328 (aset args ctr ?\;))
329 (setq ctr (1- ctr)))
330 (setq args (url-parse-args args)
331 auth (url-get-authentication url (cdr-safe (assoc "realm" args))
332 type t args))
333 (if (not auth)
334 (setq success t)
335 (push (cons (if proxy "Proxy-Authorization" "Authorization") auth)
336 url-http-extra-headers)
337 (let ((url-request-method url-http-method)
338 (url-request-data url-http-data)
339 (url-request-extra-headers url-http-extra-headers))
e817f9a2
TTN
340 (url-retrieve url url-callback-function
341 url-callback-arguments)))))))
8c8b8430
SM
342
343(defun url-http-parse-response ()
344 "Parse just the response code."
345 (declare (special url-http-end-of-headers url-http-response-status))
346 (if (not url-http-end-of-headers)
347 (error "Trying to parse HTTP response code in odd buffer: %s" (buffer-name)))
348 (url-http-debug "url-http-parse-response called in (%s)" (buffer-name))
349 (goto-char (point-min))
350 (skip-chars-forward " \t\n") ; Skip any blank crap
351 (skip-chars-forward "HTTP/") ; Skip HTTP Version
352 (read (current-buffer))
353 (setq url-http-response-status (read (current-buffer))))
354
355(defun url-http-handle-cookies ()
356 "Handle all set-cookie / set-cookie2 headers in an HTTP response.
357The buffer must already be narrowed to the headers, so mail-fetch-field will
358work correctly."
359 (let ((cookies (mail-fetch-field "Set-Cookie" nil nil t))
cacfe88b
AS
360 (cookies2 (mail-fetch-field "Set-Cookie2" nil nil t))
361 (url-current-object url-http-cookies-sources))
8c8b8430
SM
362 (and cookies (url-http-debug "Found %d Set-Cookie headers" (length cookies)))
363 (and cookies2 (url-http-debug "Found %d Set-Cookie2 headers" (length cookies2)))
364 (while cookies
365 (url-cookie-handle-set-cookie (pop cookies)))
366;;; (while cookies2
367;;; (url-cookie-handle-set-cookie2 (pop cookies)))
368 )
369 )
370
371(defun url-http-parse-headers ()
372 "Parse and handle HTTP specific headers.
373Return t if and only if the current buffer is still active and
374should be shown to the user."
375 ;; The comments after each status code handled are taken from RFC
376 ;; 2616 (HTTP/1.1)
377 (declare (special url-http-end-of-headers url-http-response-status
378 url-http-method url-http-data url-http-process
379 url-callback-function url-callback-arguments))
380
381 (url-http-mark-connection-as-free (url-host url-current-object)
382 (url-port url-current-object)
383 url-http-process)
384
385 (if (or (not (boundp 'url-http-end-of-headers))
386 (not url-http-end-of-headers))
387 (error "Trying to parse headers in odd buffer: %s" (buffer-name)))
388 (goto-char (point-min))
389 (url-http-debug "url-http-parse-headers called in (%s)" (buffer-name))
390 (url-http-parse-response)
391 (mail-narrow-to-head)
392 ;;(narrow-to-region (point-min) url-http-end-of-headers)
b43eb9ab 393 (let ((class nil)
8c8b8430
SM
394 (success nil))
395 (setq class (/ url-http-response-status 100))
396 (url-http-debug "Parsed HTTP headers: class=%d status=%d" class url-http-response-status)
397 (url-http-handle-cookies)
398
399 (case class
400 ;; Classes of response codes
401 ;;
402 ;; 5xx = Server Error
403 ;; 4xx = Client Error
404 ;; 3xx = Redirection
405 ;; 2xx = Successful
406 ;; 1xx = Informational
407 (1 ; Information messages
408 ;; 100 = Continue with request
409 ;; 101 = Switching protocols
410 ;; 102 = Processing (Added by DAV)
411 (url-mark-buffer-as-dead (current-buffer))
412 (error "HTTP responses in class 1xx not supported (%d)" url-http-response-status))
413 (2 ; Success
414 ;; 200 Ok
415 ;; 201 Created
416 ;; 202 Accepted
417 ;; 203 Non-authoritative information
418 ;; 204 No content
419 ;; 205 Reset content
420 ;; 206 Partial content
421 ;; 207 Multi-status (Added by DAV)
422 (case url-http-response-status
423 ((204 205)
424 ;; No new data, just stay at the same document
425 (url-mark-buffer-as-dead (current-buffer))
426 (setq success t))
427 (otherwise
428 ;; Generic success for all others. Store in the cache, and
429 ;; mark it as successful.
430 (widen)
76bad534 431 (if (and url-automatic-caching (equal url-http-method "GET"))
8c8b8430
SM
432 (url-store-in-cache (current-buffer)))
433 (setq success t))))
434 (3 ; Redirection
435 ;; 300 Multiple choices
436 ;; 301 Moved permanently
437 ;; 302 Found
438 ;; 303 See other
439 ;; 304 Not modified
440 ;; 305 Use proxy
441 ;; 307 Temporary redirect
442 (let ((redirect-uri (or (mail-fetch-field "Location")
443 (mail-fetch-field "URI"))))
444 (case url-http-response-status
445 (300
446 ;; Quoth the spec (section 10.3.1)
447 ;; -------------------------------
448 ;; The requested resource corresponds to any one of a set of
449 ;; representations, each with its own specific location and
450 ;; agent-driven negotiation information is being provided so
451 ;; that the user can select a preferred representation and
452 ;; redirect its request to that location.
453 ;; [...]
454 ;; If the server has a preferred choice of representation, it
455 ;; SHOULD include the specific URI for that representation in
456 ;; the Location field; user agents MAY use the Location field
457 ;; value for automatic redirection.
458 ;; -------------------------------
459 ;; We do not support agent-driven negotiation, so we just
460 ;; redirect to the preferred URI if one is provided.
461 nil)
462 ((301 302 307)
463 ;; If the 301|302 status code is received in response to a
464 ;; request other than GET or HEAD, the user agent MUST NOT
465 ;; automatically redirect the request unless it can be
466 ;; confirmed by the user, since this might change the
467 ;; conditions under which the request was issued.
468 (if (member url-http-method '("HEAD" "GET"))
469 ;; Automatic redirection is ok
470 nil
471 ;; It is just too big of a pain in the ass to get this
472 ;; prompt all the time. We will just silently lose our
473 ;; data and convert to a GET method.
474 (url-http-debug "Converting `%s' request to `GET' because of REDIRECT(%d)"
475 url-http-method url-http-response-status)
476 (setq url-http-method "GET"
cfdd484f 477 url-http-data nil)))
8c8b8430
SM
478 (303
479 ;; The response to the request can be found under a different
480 ;; URI and SHOULD be retrieved using a GET method on that
481 ;; resource.
482 (setq url-http-method "GET"
483 url-http-data nil))
484 (304
485 ;; The 304 response MUST NOT contain a message-body.
486 (url-http-debug "Extracting document from cache... (%s)"
487 (url-cache-create-filename (url-view-url t)))
488 (url-cache-extract (url-cache-create-filename (url-view-url t)))
489 (setq redirect-uri nil
490 success t))
491 (305
492 ;; The requested resource MUST be accessed through the
493 ;; proxy given by the Location field. The Location field
494 ;; gives the URI of the proxy. The recipient is expected
495 ;; to repeat this single request via the proxy. 305
496 ;; responses MUST only be generated by origin servers.
497 (error "Redirection thru a proxy server not supported: %s"
498 redirect-uri))
499 (otherwise
500 ;; Treat everything like '300'
501 nil))
502 (when redirect-uri
503 ;; Clean off any whitespace and/or <...> cruft.
504 (if (string-match "\\([^ \t]+\\)[ \t]" redirect-uri)
505 (setq redirect-uri (match-string 1 redirect-uri)))
506 (if (string-match "^<\\(.*\\)>$" redirect-uri)
507 (setq redirect-uri (match-string 1 redirect-uri)))
508
509 ;; Some stupid sites (like sourceforge) send a
510 ;; non-fully-qualified URL (ie: /), which royally confuses
511 ;; the URL library.
512 (if (not (string-match url-nonrelative-link redirect-uri))
513 (setq redirect-uri (url-expand-file-name redirect-uri)))
514 (let ((url-request-method url-http-method)
515 (url-request-data url-http-data)
516 (url-request-extra-headers url-http-extra-headers))
517 (url-retrieve redirect-uri url-callback-function
dc524e8b
RS
518 (cons :redirect
519 (cons redirect-uri
520 url-callback-arguments)))
8c8b8430
SM
521 (url-mark-buffer-as-dead (current-buffer))))))
522 (4 ; Client error
523 ;; 400 Bad Request
524 ;; 401 Unauthorized
525 ;; 402 Payment required
526 ;; 403 Forbidden
527 ;; 404 Not found
528 ;; 405 Method not allowed
529 ;; 406 Not acceptable
530 ;; 407 Proxy authentication required
531 ;; 408 Request time-out
532 ;; 409 Conflict
533 ;; 410 Gone
534 ;; 411 Length required
535 ;; 412 Precondition failed
536 ;; 413 Request entity too large
537 ;; 414 Request-URI too large
538 ;; 415 Unsupported media type
539 ;; 416 Requested range not satisfiable
540 ;; 417 Expectation failed
541 ;; 422 Unprocessable Entity (Added by DAV)
542 ;; 423 Locked
543 ;; 424 Failed Dependency
544 (case url-http-response-status
545 (401
546 ;; The request requires user authentication. The response
547 ;; MUST include a WWW-Authenticate header field containing a
548 ;; challenge applicable to the requested resource. The
549 ;; client MAY repeat the request with a suitable
550 ;; Authorization header field.
551 (url-http-handle-authentication nil))
552 (402
553 ;; This code is reserved for future use
554 (url-mark-buffer-as-dead (current-buffer))
555 (error "Somebody wants you to give them money"))
556 (403
557 ;; The server understood the request, but is refusing to
558 ;; fulfill it. Authorization will not help and the request
559 ;; SHOULD NOT be repeated.
560 (setq success t))
561 (404
562 ;; Not found
563 (setq success t))
564 (405
565 ;; The method specified in the Request-Line is not allowed
566 ;; for the resource identified by the Request-URI. The
567 ;; response MUST include an Allow header containing a list of
568 ;; valid methods for the requested resource.
569 (setq success t))
570 (406
571 ;; The resource identified by the request is only capable of
572 ;; generating response entities which have content
573 ;; characteristics nota cceptable according to the accept
574 ;; headers sent in the request.
575 (setq success t))
576 (407
577 ;; This code is similar to 401 (Unauthorized), but indicates
578 ;; that the client must first authenticate itself with the
579 ;; proxy. The proxy MUST return a Proxy-Authenticate header
580 ;; field containing a challenge applicable to the proxy for
581 ;; the requested resource.
582 (url-http-handle-authentication t))
583 (408
584 ;; The client did not produce a request within the time that
585 ;; the server was prepared to wait. The client MAY repeat
586 ;; the request without modifications at any later time.
587 (setq success t))
588 (409
589 ;; The request could not be completed due to a conflict with
590 ;; the current state of the resource. This code is only
591 ;; allowed in situations where it is expected that the user
592 ;; mioght be able to resolve the conflict and resubmit the
593 ;; request. The response body SHOULD include enough
594 ;; information for the user to recognize the source of the
595 ;; conflict.
596 (setq success t))
597 (410
598 ;; The requested resource is no longer available at the
599 ;; server and no forwarding address is known.
600 (setq success t))
601 (411
602 ;; The server refuses to accept the request without a defined
603 ;; Content-Length. The client MAY repeat the request if it
604 ;; adds a valid Content-Length header field containing the
605 ;; length of the message-body in the request message.
606 ;;
607 ;; NOTE - this will never happen because
608 ;; `url-http-create-request' automatically calculates the
609 ;; content-length.
610 (setq success t))
611 (412
612 ;; The precondition given in one or more of the
613 ;; request-header fields evaluated to false when it was
614 ;; tested on the server.
615 (setq success t))
616 ((413 414)
617 ;; The server is refusing to process a request because the
618 ;; request entity|URI is larger than the server is willing or
619 ;; able to process.
620 (setq success t))
621 (415
622 ;; The server is refusing to service the request because the
623 ;; entity of the request is in a format not supported by the
624 ;; requested resource for the requested method.
625 (setq success t))
626 (416
627 ;; A server SHOULD return a response with this status code if
628 ;; a request included a Range request-header field, and none
629 ;; of the range-specifier values in this field overlap the
630 ;; current extent of the selected resource, and the request
631 ;; did not include an If-Range request-header field.
632 (setq success t))
633 (417
634 ;; The expectation given in an Expect request-header field
635 ;; could not be met by this server, or, if the server is a
636 ;; proxy, the server has unambiguous evidence that the
637 ;; request could not be met by the next-hop server.
638 (setq success t))
639 (otherwise
640 ;; The request could not be understood by the server due to
641 ;; malformed syntax. The client SHOULD NOT repeat the
642 ;; request without modifications.
643 (setq success t))))
644 (5
645 ;; 500 Internal server error
646 ;; 501 Not implemented
647 ;; 502 Bad gateway
648 ;; 503 Service unavailable
649 ;; 504 Gateway time-out
650 ;; 505 HTTP version not supported
651 ;; 507 Insufficient storage
652 (setq success t)
653 (case url-http-response-status
654 (501
655 ;; The server does not support the functionality required to
656 ;; fulfill the request.
657 nil)
658 (502
659 ;; The server, while acting as a gateway or proxy, received
660 ;; an invalid response from the upstream server it accessed
661 ;; in attempting to fulfill the request.
662 nil)
663 (503
664 ;; The server is currently unable to handle the request due
665 ;; to a temporary overloading or maintenance of the server.
666 ;; The implication is that this is a temporary condition
667 ;; which will be alleviated after some delay. If known, the
668 ;; length of the delay MAY be indicated in a Retry-After
669 ;; header. If no Retry-After is given, the client SHOULD
670 ;; handle the response as it would for a 500 response.
671 nil)
672 (504
673 ;; The server, while acting as a gateway or proxy, did not
674 ;; receive a timely response from the upstream server
675 ;; specified by the URI (e.g. HTTP, FTP, LDAP) or some other
676 ;; auxiliary server (e.g. DNS) it needed to access in
677 ;; attempting to complete the request.
678 nil)
679 (505
680 ;; The server does not support, or refuses to support, the
681 ;; HTTP protocol version that was used in the request
682 ;; message.
683 nil)
684 (507 ; DAV
685 ;; The method could not be performed on the resource
686 ;; because the server is unable to store the representation
687 ;; needed to successfully complete the request. This
688 ;; condition is considered to be temporary. If the request
689 ;; which received this status code was the result of a user
690 ;; action, the request MUST NOT be repeated until it is
691 ;; requested by a separate user action.
692 nil)))
693 (otherwise
694 (error "Unknown class of HTTP response code: %d (%d)"
695 class url-http-response-status)))
696 (if (not success)
697 (url-mark-buffer-as-dead (current-buffer)))
698 (url-http-debug "Finished parsing HTTP headers: %S" success)
699 (widen)
700 success))
701
702;; Miscellaneous
703(defun url-http-activate-callback ()
704 "Activate callback specified when this buffer was created."
705 (declare (special url-http-process
706 url-callback-function
707 url-callback-arguments))
708 (url-http-mark-connection-as-free (url-host url-current-object)
709 (url-port url-current-object)
710 url-http-process)
711 (url-http-debug "Activating callback in buffer (%s)" (buffer-name))
712 (apply url-callback-function url-callback-arguments))
713
714;; )
715
716;; These unfortunately cannot be macros... please ignore them!
717(defun url-http-idle-sentinel (proc why)
718 "Remove this (now defunct) process PROC from the list of open connections."
719 (maphash (lambda (key val)
720 (if (memq proc val)
721 (puthash key (delq proc val) url-http-open-connections)))
722 url-http-open-connections))
723
724(defun url-http-end-of-document-sentinel (proc why)
725 ;; Sentinel used for old HTTP/0.9 or connections we know are going
726 ;; to die as the 'end of document' notifier.
727 (url-http-debug "url-http-end-of-document-sentinel in buffer (%s)"
728 (process-buffer proc))
729 (url-http-idle-sentinel proc why)
730 (save-excursion
731 (set-buffer (process-buffer proc))
732 (goto-char (point-min))
733 (if (not (looking-at "HTTP/"))
734 ;; HTTP/0.9 just gets passed back no matter what
735 (url-http-activate-callback)
736 (if (url-http-parse-headers)
737 (url-http-activate-callback)))))
738
739(defun url-http-simple-after-change-function (st nd length)
740 ;; Function used when we do NOT know how long the document is going to be
741 ;; Just _very_ simple 'downloaded %d' type of info.
742 (declare (special url-http-end-of-headers))
743 (url-lazy-message "Reading %s..." (url-pretty-length nd)))
744
745(defun url-http-content-length-after-change-function (st nd length)
746 "Function used when we DO know how long the document is going to be.
747More sophisticated percentage downloaded, etc.
748Also does minimal parsing of HTTP headers and will actually cause
749the callback to be triggered."
750 (declare (special url-current-object
751 url-http-end-of-headers
752 url-http-content-length
753 url-http-content-type
754 url-http-process))
755 (if url-http-content-type
756 (url-display-percentage
757 "Reading [%s]... %s of %s (%d%%)"
758 (url-percentage (- nd url-http-end-of-headers)
759 url-http-content-length)
760 url-http-content-type
761 (url-pretty-length (- nd url-http-end-of-headers))
762 (url-pretty-length url-http-content-length)
763 (url-percentage (- nd url-http-end-of-headers)
764 url-http-content-length))
765 (url-display-percentage
766 "Reading... %s of %s (%d%%)"
767 (url-percentage (- nd url-http-end-of-headers)
768 url-http-content-length)
769 (url-pretty-length (- nd url-http-end-of-headers))
770 (url-pretty-length url-http-content-length)
771 (url-percentage (- nd url-http-end-of-headers)
772 url-http-content-length)))
773
774 (if (> (- nd url-http-end-of-headers) url-http-content-length)
775 (progn
776 ;; Found the end of the document! Wheee!
777 (url-display-percentage nil nil)
778 (message "Reading... done.")
779 (if (url-http-parse-headers)
780 (url-http-activate-callback)))))
781
782(defun url-http-chunked-encoding-after-change-function (st nd length)
783 "Function used when dealing with 'chunked' encoding.
784Cannot give a sophisticated percentage, but we need a different
785function to look for the special 0-length chunk that signifies
786the end of the document."
787 (declare (special url-current-object
788 url-http-end-of-headers
789 url-http-content-type
790 url-http-chunked-length
791 url-http-chunked-counter
792 url-http-process url-http-chunked-start))
793 (save-excursion
794 (goto-char st)
795 (let ((read-next-chunk t)
796 (case-fold-search t)
797 (regexp nil)
798 (no-initial-crlf nil))
799 ;; We need to loop thru looking for more chunks even within
800 ;; one after-change-function call.
801 (while read-next-chunk
802 (setq no-initial-crlf (= 0 url-http-chunked-counter))
803 (if url-http-content-type
804 (url-display-percentage nil
805 "Reading [%s]... chunk #%d"
806 url-http-content-type url-http-chunked-counter)
807 (url-display-percentage nil
808 "Reading... chunk #%d"
809 url-http-chunked-counter))
810 (url-http-debug "Reading chunk %d (%d %d %d)"
811 url-http-chunked-counter st nd length)
812 (setq regexp (if no-initial-crlf
813 "\\([0-9a-z]+\\).*\r?\n"
814 "\r?\n\\([0-9a-z]+\\).*\r?\n"))
815
816 (if url-http-chunked-start
817 ;; We know how long the chunk is supposed to be, skip over
818 ;; leading crap if possible.
819 (if (> nd (+ url-http-chunked-start url-http-chunked-length))
820 (progn
821 (url-http-debug "Got to the end of chunk #%d!"
822 url-http-chunked-counter)
823 (goto-char (+ url-http-chunked-start
824 url-http-chunked-length)))
825 (url-http-debug "Still need %d bytes to hit end of chunk"
826 (- (+ url-http-chunked-start
827 url-http-chunked-length)
828 nd))
829 (setq read-next-chunk nil)))
830 (if (not read-next-chunk)
831 (url-http-debug "Still spinning for next chunk...")
832 (if no-initial-crlf (skip-chars-forward "\r\n"))
833 (if (not (looking-at regexp))
834 (progn
835 ;; Must not have received the entirety of the chunk header,
836 ;; need to spin some more.
837 (url-http-debug "Did not see start of chunk @ %d!" (point))
838 (setq read-next-chunk nil))
839 (add-text-properties (match-beginning 0) (match-end 0)
840 (list 'start-open t
841 'end-open t
842 'chunked-encoding t
843 'face (if (featurep 'xemacs)
844 'text-cursor
845 'cursor)
846 'invisible t))
216d3806
JB
847 (setq url-http-chunked-length (string-to-number (buffer-substring
848 (match-beginning 1)
849 (match-end 1))
850 16)
8c8b8430
SM
851 url-http-chunked-counter (1+ url-http-chunked-counter)
852 url-http-chunked-start (set-marker
853 (or url-http-chunked-start
854 (make-marker))
855 (match-end 0)))
856; (if (not url-http-debug)
857 (delete-region (match-beginning 0) (match-end 0));)
858 (url-http-debug "Saw start of chunk %d (length=%d, start=%d"
859 url-http-chunked-counter url-http-chunked-length
860 (marker-position url-http-chunked-start))
861 (if (= 0 url-http-chunked-length)
862 (progn
863 ;; Found the end of the document! Wheee!
864 (url-http-debug "Saw end of stream chunk!")
865 (setq read-next-chunk nil)
866 (url-display-percentage nil nil)
867 (goto-char (match-end 1))
868 (if (re-search-forward "^\r*$" nil t)
63db9644 869 (url-http-debug "Saw end of trailers..."))
8c8b8430
SM
870 (if (url-http-parse-headers)
871 (url-http-activate-callback))))))))))
872
873(defun url-http-wait-for-headers-change-function (st nd length)
874 ;; This will wait for the headers to arrive and then splice in the
875 ;; next appropriate after-change-function, etc.
876 (declare (special url-current-object
877 url-http-end-of-headers
878 url-http-content-type
879 url-http-content-length
880 url-http-transfer-encoding
881 url-callback-function
882 url-callback-arguments
883 url-http-process
884 url-http-method
885 url-http-after-change-function
886 url-http-response-status))
887 (url-http-debug "url-http-wait-for-headers-change-function (%s)"
888 (buffer-name))
889 (if (not (bobp))
890 (let ((end-of-headers nil)
891 (old-http nil)
892 (content-length nil))
893 (goto-char (point-min))
894 (if (not (looking-at "^HTTP/[1-9]\\.[0-9]"))
895 ;; Not HTTP/x.y data, must be 0.9
896 ;; God, I wish this could die.
897 (setq end-of-headers t
898 url-http-end-of-headers 0
899 old-http t)
900 (if (re-search-forward "^\r*$" nil t)
901 ;; Saw the end of the headers
902 (progn
903 (url-http-debug "Saw end of headers... (%s)" (buffer-name))
904 (setq url-http-end-of-headers (set-marker (make-marker)
905 (point))
906 end-of-headers t)
907 (url-http-clean-headers))))
908
909 (if (not end-of-headers)
910 ;; Haven't seen the end of the headers yet, need to wait
911 ;; for more data to arrive.
912 nil
913 (if old-http
914 (message "HTTP/0.9 How I hate thee!")
915 (progn
916 (url-http-parse-response)
917 (mail-narrow-to-head)
918 ;;(narrow-to-region (point-min) url-http-end-of-headers)
919 (setq url-http-transfer-encoding (mail-fetch-field
920 "transfer-encoding")
921 url-http-content-type (mail-fetch-field "content-type"))
922 (if (mail-fetch-field "content-length")
923 (setq url-http-content-length
216d3806 924 (string-to-number (mail-fetch-field "content-length"))))
8c8b8430
SM
925 (widen)))
926 (if url-http-transfer-encoding
927 (setq url-http-transfer-encoding
928 (downcase url-http-transfer-encoding)))
929
930 (cond
931 ((or (= url-http-response-status 204)
932 (= url-http-response-status 205))
933 (url-http-debug "%d response must have headers only (%s)."
934 url-http-response-status (buffer-name))
935 (if (url-http-parse-headers)
936 (url-http-activate-callback)))
937 ((string= "HEAD" url-http-method)
938 ;; A HEAD request is _ALWAYS_ terminated by the header
939 ;; information, regardless of any entity headers,
940 ;; according to section 4.4 of the HTTP/1.1 draft.
941 (url-http-debug "HEAD request must have headers only (%s)."
942 (buffer-name))
943 (if (url-http-parse-headers)
944 (url-http-activate-callback)))
945 ((string= "CONNECT" url-http-method)
946 ;; A CONNECT request is finished, but we cannot stick this
947 ;; back on the free connectin list
948 (url-http-debug "CONNECT request must have headers only.")
949 (if (url-http-parse-headers)
950 (url-http-activate-callback)))
951 ((equal url-http-response-status 304)
952 ;; Only allowed to have a header section. We have to handle
953 ;; this here instead of in url-http-parse-headers because if
954 ;; you have a cached copy of something without a known
955 ;; content-length, and try to retrieve it from the cache, we'd
956 ;; fall into the 'being dumb' section and wait for the
957 ;; connection to terminate, which means we'd wait for 10
958 ;; seconds for the keep-alives to time out on some servers.
959 (if (url-http-parse-headers)
960 (url-http-activate-callback)))
961 (old-http
962 ;; HTTP/0.9 always signaled end-of-connection by closing the
963 ;; connection.
964 (url-http-debug
965 "Saw HTTP/0.9 response, connection closed means end of document.")
966 (setq url-http-after-change-function
967 'url-http-simple-after-change-function))
968 ((equal url-http-transfer-encoding "chunked")
969 (url-http-debug "Saw chunked encoding.")
970 (setq url-http-after-change-function
971 'url-http-chunked-encoding-after-change-function)
972 (if (> nd url-http-end-of-headers)
973 (progn
974 (url-http-debug
975 "Calling initial chunked-encoding for extra data at end of headers")
976 (url-http-chunked-encoding-after-change-function
977 (marker-position url-http-end-of-headers) nd
978 (- nd url-http-end-of-headers)))))
979 ((integerp url-http-content-length)
980 (url-http-debug
981 "Got a content-length, being smart about document end.")
982 (setq url-http-after-change-function
983 'url-http-content-length-after-change-function)
984 (cond
985 ((= 0 url-http-content-length)
986 ;; We got a NULL body! Activate the callback
987 ;; immediately!
988 (url-http-debug
989 "Got 0-length content-length, activating callback immediately.")
990 (if (url-http-parse-headers)
991 (url-http-activate-callback)))
992 ((> nd url-http-end-of-headers)
993 ;; Have some leftover data
994 (url-http-debug "Calling initial content-length for extra data at end of headers")
995 (url-http-content-length-after-change-function
996 (marker-position url-http-end-of-headers)
997 nd
998 (- nd url-http-end-of-headers)))
999 (t
1000 nil)))
1001 (t
1002 (url-http-debug "No content-length, being dumb.")
1003 (setq url-http-after-change-function
1004 'url-http-simple-after-change-function)))))
1005 ;; We are still at the beginning of the buffer... must just be
1006 ;; waiting for a response.
1007 (url-http-debug "Spinning waiting for headers..."))
1008 (goto-char (point-max)))
1009
1010;;;###autoload
1011(defun url-http (url callback cbargs)
1012 "Retrieve URL via HTTP asynchronously.
1013URL must be a parsed URL. See `url-generic-parse-url' for details.
1014When retrieval is completed, the function CALLBACK is executed with
1015CBARGS as the arguments."
1016 (check-type url vector "Need a pre-parsed URL.")
1017 (declare (special url-current-object
1018 url-http-end-of-headers
1019 url-http-content-type
1020 url-http-content-length
1021 url-http-transfer-encoding
1022 url-http-after-change-function
1023 url-callback-function
1024 url-callback-arguments
1025 url-http-method
1026 url-http-extra-headers
1027 url-http-data
1028 url-http-chunked-length
1029 url-http-chunked-start
1030 url-http-chunked-counter
1031 url-http-process))
1032 (let ((connection (url-http-find-free-connection (url-host url)
1033 (url-port url)))
1034 (buffer (generate-new-buffer (format " *http %s:%d*"
1035 (url-host url)
1036 (url-port url)))))
1037 (if (not connection)
1038 ;; Failed to open the connection for some reason
1039 (progn
1040 (kill-buffer buffer)
1041 (setq buffer nil)
1042 (error "Could not create connection to %s:%d" (url-host url)
1043 (url-port url)))
1044 (save-excursion
1045 (set-buffer buffer)
1046 (mm-disable-multibyte)
1047 (setq url-current-object url
1048 mode-line-format "%b [%s]")
1049
1050 (dolist (var '(url-http-end-of-headers
1051 url-http-content-type
1052 url-http-content-length
1053 url-http-transfer-encoding
1054 url-http-after-change-function
1055 url-http-response-status
1056 url-http-chunked-length
1057 url-http-chunked-counter
1058 url-http-chunked-start
1059 url-callback-function
1060 url-callback-arguments
1061 url-http-process
1062 url-http-method
1063 url-http-extra-headers
cacfe88b
AS
1064 url-http-data
1065 url-http-cookies-sources))
8c8b8430
SM
1066 (set (make-local-variable var) nil))
1067
1068 (setq url-http-method (or url-request-method "GET")
1069 url-http-extra-headers url-request-extra-headers
1070 url-http-data url-request-data
1071 url-http-process connection
1072 url-http-chunked-length nil
1073 url-http-chunked-start nil
1074 url-http-chunked-counter 0
1075 url-callback-function callback
1076 url-callback-arguments cbargs
cacfe88b
AS
1077 url-http-after-change-function 'url-http-wait-for-headers-change-function
1078 url-http-cookies-sources (if (boundp 'proxy-object)
1079 proxy-object
1080 url-current-object))
8c8b8430
SM
1081
1082 (set-process-buffer connection buffer)
1083 (set-process-sentinel connection 'url-http-end-of-document-sentinel)
1084 (set-process-filter connection 'url-http-generic-filter)
1085 (process-send-string connection (url-http-create-request url))))
1086 buffer))
1087
1088;; Since Emacs 19/20 does not allow you to change the
1089;; `after-change-functions' hook in the midst of running them, we fake
1090;; an after change by hooking into the process filter and inserting
1091;; the data ourselves. This is slightly less efficient, but there
1092;; were tons of weird ways the after-change code was biting us in the
1093;; shorts.
1094(defun url-http-generic-filter (proc data)
1095 ;; Sometimes we get a zero-length data chunk after the process has
1096 ;; been changed to 'free', which means it has no buffer associated
1097 ;; with it. Do nothing if there is no buffer, or 0 length data.
1098 (declare (special url-http-after-change-function))
1099 (and (process-buffer proc)
1100 (/= (length data) 0)
1101 (save-excursion
1102 (set-buffer (process-buffer proc))
1103 (url-http-debug "Calling after change function `%s' for `%S'" url-http-after-change-function proc)
1104 (funcall url-http-after-change-function
1105 (point-max)
1106 (progn
1107 (goto-char (point-max))
1108 (insert data)
1109 (point-max))
1110 (length data)))))
1111
1112;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1113;;; file-name-handler stuff from here on out
1114;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1115(if (not (fboundp 'symbol-value-in-buffer))
1116 (defun url-http-symbol-value-in-buffer (symbol buffer
1117 &optional unbound-value)
1118 "Return the value of SYMBOL in BUFFER, or UNBOUND-VALUE if it is unbound."
1119 (save-excursion
1120 (set-buffer buffer)
1121 (if (not (boundp symbol))
1122 unbound-value
1123 (symbol-value symbol))))
1124 (defalias 'url-http-symbol-value-in-buffer 'symbol-value-in-buffer))
1125
1126(defun url-http-head (url)
1127 (let ((url-request-method "HEAD")
1128 (url-request-data nil))
1129 (url-retrieve-synchronously url)))
1130
1131;;;###autoload
1132(defun url-http-file-exists-p (url)
b43eb9ab 1133 (let ((status nil)
8c8b8430
SM
1134 (exists nil)
1135 (buffer (url-http-head url)))
1136 (if (not buffer)
1137 (setq exists nil)
1138 (setq status (url-http-symbol-value-in-buffer 'url-http-response-status
1139 buffer 500)
1140 exists (and (>= status 200) (< status 300)))
1141 (kill-buffer buffer))
1142 exists))
1143
1144;;;###autoload
1145(defalias 'url-http-file-readable-p 'url-http-file-exists-p)
1146
3f19601e 1147(defun url-http-head-file-attributes (url &optional id-format)
8c8b8430
SM
1148 (let ((buffer (url-http-head url))
1149 (attributes nil))
1150 (when buffer
1151 (setq attributes (make-list 11 nil))
1152 (setf (nth 1 attributes) 1) ; Number of links to file
1153 (setf (nth 2 attributes) 0) ; file uid
1154 (setf (nth 3 attributes) 0) ; file gid
1155 (setf (nth 7 attributes) ; file size
1156 (url-http-symbol-value-in-buffer 'url-http-content-length
1157 buffer -1))
1158 (setf (nth 8 attributes) (eval-when-compile (make-string 10 ?-)))
1159 (kill-buffer buffer))
1160 attributes))
1161
1162;;;###autoload
3f19601e 1163(defun url-http-file-attributes (url &optional id-format)
8c8b8430 1164 (if (url-dav-supported-p url)
3f19601e
SM
1165 (url-dav-file-attributes url id-format)
1166 (url-http-head-file-attributes url id-format)))
8c8b8430
SM
1167
1168;;;###autoload
1169(defun url-http-options (url)
1170 "Returns a property list describing options available for URL.
1171This list is retrieved using the `OPTIONS' HTTP method.
1172
1173Property list members:
1174
1175methods
1176 A list of symbols specifying what HTTP methods the resource
1177 supports.
1178
1179dav
1180 A list of numbers specifying what DAV protocol/schema versions are
1181 supported.
1182
1183dasl
1184 A list of supported DASL search types supported (string form)
1185
1186ranges
1187 A list of the units available for use in partial document fetches.
1188
1189p3p
1190 The `Platform For Privacy Protection' description for the resource.
1191 Currently this is just the raw header contents. This is likely to
1192 change once P3P is formally supported by the URL package or
1193 Emacs/W3.
1194"
1195 (let* ((url-request-method "OPTIONS")
1196 (url-request-data nil)
1197 (buffer (url-retrieve-synchronously url))
1198 (header nil)
1199 (options nil))
1200 (when (and buffer (= 2 (/ (url-http-symbol-value-in-buffer
1201 'url-http-response-status buffer 0) 100)))
1202 ;; Only parse the options if we got a 2xx response code!
1203 (save-excursion
1204 (save-restriction
1205 (save-match-data
1206 (set-buffer buffer)
1207 (mail-narrow-to-head)
1208
1209 ;; Figure out what methods are supported.
1210 (when (setq header (mail-fetch-field "allow"))
1211 (setq options (plist-put
1212 options 'methods
1213 (mapcar 'intern (split-string header "[ ,]+")))))
1214
1215 ;; Check for DAV
1216 (when (setq header (mail-fetch-field "dav"))
1217 (setq options (plist-put
1218 options 'dav
1219 (delq 0
1220 (mapcar 'string-to-number
1221 (split-string header "[, ]+"))))))
1222
1223 ;; Now for DASL
1224 (when (setq header (mail-fetch-field "dasl"))
1225 (setq options (plist-put
1226 options 'dasl
1227 (split-string header "[, ]+"))))
1228
1229 ;; P3P - should get more detailed here. FIXME
1230 (when (setq header (mail-fetch-field "p3p"))
1231 (setq options (plist-put options 'p3p header)))
1232
1233 ;; Check for whether they accept byte-range requests.
1234 (when (setq header (mail-fetch-field "accept-ranges"))
1235 (setq options (plist-put
1236 options 'ranges
1237 (delq 'none
1238 (mapcar 'intern
1239 (split-string header "[, ]+"))))))
1240 ))))
1241 (if buffer (kill-buffer buffer))
1242 options))
1243
1244(provide 'url-http)
1245
b43eb9ab 1246;; arch-tag: ba7c59ae-c0f4-4a31-9617-d85f221732ee
8c8b8430 1247;;; url-http.el ends here