*** empty log message ***
[emacs.git] / lisp / url / url-http.el
blob39db321c080d6f7cb3ee64154b4a73c87a1ffd17
1 ;;; url-http.el --- HTTP retrieval routines
3 ;; Copyright (C) 1999, 2001, 2004, 2005 Free Software Foundation, Inc.
5 ;; Author: Bill Perry <wmperry@gnu.org>
6 ;; Keywords: comm, data, processes
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.
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.
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
22 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
25 ;;; Commentary:
27 ;;; Code:
29 (eval-when-compile
30 (require 'cl)
31 (defvar url-http-extra-headers)
32 (defvar url-http-cookies-sources))
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")
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)
48 (defvar url-http-real-basic-auth-storage nil)
49 (defvar url-http-proxy-basic-auth-storage nil)
51 (defvar url-http-open-connections (make-hash-table :test 'equal
52 :size 17)
53 "A hash table of all open network connections.")
55 (defvar url-http-version "1.1"
56 "What version of HTTP we advertise, as a string.
57 Valid values are 1.1 and 1.0.
58 This is only useful when debugging the HTTP subsystem.
60 Setting this to 1.0 will tell servers not to send chunked encoding,
61 and other HTTP/1.1 specific features.
64 (defvar url-http-attempt-keepalives t
65 "Whether to use a single TCP connection multiple times in HTTP.
66 This is only useful when debugging the HTTP subsystem. Setting to
67 `nil' will explicitly close the connection to the server after every
68 request.
71 ;(eval-when-compile
72 ;; These are all macros so that they are hidden from external sight
73 ;; when the file is byte-compiled.
75 ;; This allows us to expose just the entry points we want.
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))
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)
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)
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)))))
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)))
134 (format "User-Agent: %sURL/%s%s\r\n"
135 (if url-package-name
136 (concat url-package-name "/" url-package-version " ")
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 "")))))
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))
157 (let ((url-basic-auth-storage
158 'url-http-proxy-basic-auth-storage))
159 (url-get-authentication url nil 'any nil))))
160 (real-fname (url-filename (or proxy-obj url)))
161 (host (url-host (or proxy-obj url)))
162 (auth (if (cdr-safe (assoc "Authorization" url-request-extra-headers))
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")))
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))
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))
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")))
196 ;; This was done with a call to `format'. Concatting parts has
197 ;; the advantage of keeping the parts of each header together and
198 ;; allows us to elide null lines directly, at the cost of making
199 ;; the layout less clear.
200 (setq request
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 ""))
283 (url-http-debug "Request is: \n%s" request)
284 request))
286 ;; Parsing routines
287 (defun url-http-clean-headers ()
288 "Remove trailing \r from header lines.
289 This 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 "")))
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)
306 ;; Cheating, but who cares? :)
307 (if proxy
308 (setq url-basic-auth-storage 'url-http-proxy-basic-auth-storage))
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)))
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))
340 (url-retrieve url url-callback-function
341 url-callback-arguments)))))))
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))))
355 (defun url-http-handle-cookies ()
356 "Handle all set-cookie / set-cookie2 headers in an HTTP response.
357 The buffer must already be narrowed to the headers, so mail-fetch-field will
358 work correctly."
359 (let ((cookies (mail-fetch-field "Set-Cookie" nil nil t))
360 (cookies2 (mail-fetch-field "Set-Cookie2" nil nil t))
361 (url-current-object url-http-cookies-sources))
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)))
371 (defun url-http-parse-headers ()
372 "Parse and handle HTTP specific headers.
373 Return t if and only if the current buffer is still active and
374 should 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))
381 (url-http-mark-connection-as-free (url-host url-current-object)
382 (url-port url-current-object)
383 url-http-process)
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)
393 (let ((class nil)
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)
399 (case class
400 ;; Classes of response codes
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)
431 (if (and url-automatic-caching (equal url-http-method "GET"))
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
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"
477 url-http-data nil)))
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)))
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
518 (cons :redirect
519 (cons redirect-uri
520 url-callback-arguments)))
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.
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))))
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))
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))
714 ;; )
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))
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)))))
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)))
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.
747 More sophisticated percentage downloaded, etc.
748 Also does minimal parsing of HTTP headers and will actually cause
749 the 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)))
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)))))
782 (defun url-http-chunked-encoding-after-change-function (st nd length)
783 "Function used when dealing with 'chunked' encoding.
784 Cannot give a sophisticated percentage, but we need a different
785 function to look for the special 0-length chunk that signifies
786 the 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"))
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 'cursor
844 'invisible t))
845 (setq url-http-chunked-length (string-to-number (buffer-substring
846 (match-beginning 1)
847 (match-end 1))
849 url-http-chunked-counter (1+ url-http-chunked-counter)
850 url-http-chunked-start (set-marker
851 (or url-http-chunked-start
852 (make-marker))
853 (match-end 0)))
854 ; (if (not url-http-debug)
855 (delete-region (match-beginning 0) (match-end 0));)
856 (url-http-debug "Saw start of chunk %d (length=%d, start=%d"
857 url-http-chunked-counter url-http-chunked-length
858 (marker-position url-http-chunked-start))
859 (if (= 0 url-http-chunked-length)
860 (progn
861 ;; Found the end of the document! Wheee!
862 (url-http-debug "Saw end of stream chunk!")
863 (setq read-next-chunk nil)
864 (url-display-percentage nil nil)
865 (goto-char (match-end 1))
866 (if (re-search-forward "^\r*$" nil t)
867 (url-http-debug "Saw end of trailers..."))
868 (if (url-http-parse-headers)
869 (url-http-activate-callback))))))))))
871 (defun url-http-wait-for-headers-change-function (st nd length)
872 ;; This will wait for the headers to arrive and then splice in the
873 ;; next appropriate after-change-function, etc.
874 (declare (special url-current-object
875 url-http-end-of-headers
876 url-http-content-type
877 url-http-content-length
878 url-http-transfer-encoding
879 url-callback-function
880 url-callback-arguments
881 url-http-process
882 url-http-method
883 url-http-after-change-function
884 url-http-response-status))
885 (url-http-debug "url-http-wait-for-headers-change-function (%s)"
886 (buffer-name))
887 (if (not (bobp))
888 (let ((end-of-headers nil)
889 (old-http nil)
890 (content-length nil))
891 (goto-char (point-min))
892 (if (not (looking-at "^HTTP/[1-9]\\.[0-9]"))
893 ;; Not HTTP/x.y data, must be 0.9
894 ;; God, I wish this could die.
895 (setq end-of-headers t
896 url-http-end-of-headers 0
897 old-http t)
898 (if (re-search-forward "^\r*$" nil t)
899 ;; Saw the end of the headers
900 (progn
901 (url-http-debug "Saw end of headers... (%s)" (buffer-name))
902 (setq url-http-end-of-headers (set-marker (make-marker)
903 (point))
904 end-of-headers t)
905 (url-http-clean-headers))))
907 (if (not end-of-headers)
908 ;; Haven't seen the end of the headers yet, need to wait
909 ;; for more data to arrive.
911 (if old-http
912 (message "HTTP/0.9 How I hate thee!")
913 (progn
914 (url-http-parse-response)
915 (mail-narrow-to-head)
916 ;;(narrow-to-region (point-min) url-http-end-of-headers)
917 (setq url-http-transfer-encoding (mail-fetch-field
918 "transfer-encoding")
919 url-http-content-type (mail-fetch-field "content-type"))
920 (if (mail-fetch-field "content-length")
921 (setq url-http-content-length
922 (string-to-number (mail-fetch-field "content-length"))))
923 (widen)))
924 (if url-http-transfer-encoding
925 (setq url-http-transfer-encoding
926 (downcase url-http-transfer-encoding)))
928 (cond
929 ((or (= url-http-response-status 204)
930 (= url-http-response-status 205))
931 (url-http-debug "%d response must have headers only (%s)."
932 url-http-response-status (buffer-name))
933 (if (url-http-parse-headers)
934 (url-http-activate-callback)))
935 ((string= "HEAD" url-http-method)
936 ;; A HEAD request is _ALWAYS_ terminated by the header
937 ;; information, regardless of any entity headers,
938 ;; according to section 4.4 of the HTTP/1.1 draft.
939 (url-http-debug "HEAD request must have headers only (%s)."
940 (buffer-name))
941 (if (url-http-parse-headers)
942 (url-http-activate-callback)))
943 ((string= "CONNECT" url-http-method)
944 ;; A CONNECT request is finished, but we cannot stick this
945 ;; back on the free connectin list
946 (url-http-debug "CONNECT request must have headers only.")
947 (if (url-http-parse-headers)
948 (url-http-activate-callback)))
949 ((equal url-http-response-status 304)
950 ;; Only allowed to have a header section. We have to handle
951 ;; this here instead of in url-http-parse-headers because if
952 ;; you have a cached copy of something without a known
953 ;; content-length, and try to retrieve it from the cache, we'd
954 ;; fall into the 'being dumb' section and wait for the
955 ;; connection to terminate, which means we'd wait for 10
956 ;; seconds for the keep-alives to time out on some servers.
957 (if (url-http-parse-headers)
958 (url-http-activate-callback)))
959 (old-http
960 ;; HTTP/0.9 always signaled end-of-connection by closing the
961 ;; connection.
962 (url-http-debug
963 "Saw HTTP/0.9 response, connection closed means end of document.")
964 (setq url-http-after-change-function
965 'url-http-simple-after-change-function))
966 ((equal url-http-transfer-encoding "chunked")
967 (url-http-debug "Saw chunked encoding.")
968 (setq url-http-after-change-function
969 'url-http-chunked-encoding-after-change-function)
970 (if (> nd url-http-end-of-headers)
971 (progn
972 (url-http-debug
973 "Calling initial chunked-encoding for extra data at end of headers")
974 (url-http-chunked-encoding-after-change-function
975 (marker-position url-http-end-of-headers) nd
976 (- nd url-http-end-of-headers)))))
977 ((integerp url-http-content-length)
978 (url-http-debug
979 "Got a content-length, being smart about document end.")
980 (setq url-http-after-change-function
981 'url-http-content-length-after-change-function)
982 (cond
983 ((= 0 url-http-content-length)
984 ;; We got a NULL body! Activate the callback
985 ;; immediately!
986 (url-http-debug
987 "Got 0-length content-length, activating callback immediately.")
988 (if (url-http-parse-headers)
989 (url-http-activate-callback)))
990 ((> nd url-http-end-of-headers)
991 ;; Have some leftover data
992 (url-http-debug "Calling initial content-length for extra data at end of headers")
993 (url-http-content-length-after-change-function
994 (marker-position url-http-end-of-headers)
996 (- nd url-http-end-of-headers)))
998 nil)))
1000 (url-http-debug "No content-length, being dumb.")
1001 (setq url-http-after-change-function
1002 'url-http-simple-after-change-function)))))
1003 ;; We are still at the beginning of the buffer... must just be
1004 ;; waiting for a response.
1005 (url-http-debug "Spinning waiting for headers..."))
1006 (goto-char (point-max)))
1008 ;;;###autoload
1009 (defun url-http (url callback cbargs)
1010 "Retrieve URL via HTTP asynchronously.
1011 URL must be a parsed URL. See `url-generic-parse-url' for details.
1012 When retrieval is completed, the function CALLBACK is executed with
1013 CBARGS as the arguments."
1014 (check-type url vector "Need a pre-parsed URL.")
1015 (declare (special url-current-object
1016 url-http-end-of-headers
1017 url-http-content-type
1018 url-http-content-length
1019 url-http-transfer-encoding
1020 url-http-after-change-function
1021 url-callback-function
1022 url-callback-arguments
1023 url-http-method
1024 url-http-extra-headers
1025 url-http-data
1026 url-http-chunked-length
1027 url-http-chunked-start
1028 url-http-chunked-counter
1029 url-http-process))
1030 (let ((connection (url-http-find-free-connection (url-host url)
1031 (url-port url)))
1032 (buffer (generate-new-buffer (format " *http %s:%d*"
1033 (url-host url)
1034 (url-port url)))))
1035 (if (not connection)
1036 ;; Failed to open the connection for some reason
1037 (progn
1038 (kill-buffer buffer)
1039 (setq buffer nil)
1040 (error "Could not create connection to %s:%d" (url-host url)
1041 (url-port url)))
1042 (save-excursion
1043 (set-buffer buffer)
1044 (mm-disable-multibyte)
1045 (setq url-current-object url
1046 mode-line-format "%b [%s]")
1048 (dolist (var '(url-http-end-of-headers
1049 url-http-content-type
1050 url-http-content-length
1051 url-http-transfer-encoding
1052 url-http-after-change-function
1053 url-http-response-status
1054 url-http-chunked-length
1055 url-http-chunked-counter
1056 url-http-chunked-start
1057 url-callback-function
1058 url-callback-arguments
1059 url-http-process
1060 url-http-method
1061 url-http-extra-headers
1062 url-http-data
1063 url-http-cookies-sources))
1064 (set (make-local-variable var) nil))
1066 (setq url-http-method (or url-request-method "GET")
1067 url-http-extra-headers url-request-extra-headers
1068 url-http-data url-request-data
1069 url-http-process connection
1070 url-http-chunked-length nil
1071 url-http-chunked-start nil
1072 url-http-chunked-counter 0
1073 url-callback-function callback
1074 url-callback-arguments cbargs
1075 url-http-after-change-function 'url-http-wait-for-headers-change-function
1076 url-http-cookies-sources (if (boundp 'proxy-object)
1077 proxy-object
1078 url-current-object))
1080 (set-process-buffer connection buffer)
1081 (set-process-sentinel connection 'url-http-end-of-document-sentinel)
1082 (set-process-filter connection 'url-http-generic-filter)
1083 (process-send-string connection (url-http-create-request url))))
1084 buffer))
1086 ;; Since Emacs 19/20 does not allow you to change the
1087 ;; `after-change-functions' hook in the midst of running them, we fake
1088 ;; an after change by hooking into the process filter and inserting
1089 ;; the data ourselves. This is slightly less efficient, but there
1090 ;; were tons of weird ways the after-change code was biting us in the
1091 ;; shorts.
1092 (defun url-http-generic-filter (proc data)
1093 ;; Sometimes we get a zero-length data chunk after the process has
1094 ;; been changed to 'free', which means it has no buffer associated
1095 ;; with it. Do nothing if there is no buffer, or 0 length data.
1096 (declare (special url-http-after-change-function))
1097 (and (process-buffer proc)
1098 (/= (length data) 0)
1099 (save-excursion
1100 (set-buffer (process-buffer proc))
1101 (url-http-debug "Calling after change function `%s' for `%S'" url-http-after-change-function proc)
1102 (funcall url-http-after-change-function
1103 (point-max)
1104 (progn
1105 (goto-char (point-max))
1106 (insert data)
1107 (point-max))
1108 (length data)))))
1110 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1111 ;;; file-name-handler stuff from here on out
1112 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1113 (if (not (fboundp 'symbol-value-in-buffer))
1114 (defun url-http-symbol-value-in-buffer (symbol buffer
1115 &optional unbound-value)
1116 "Return the value of SYMBOL in BUFFER, or UNBOUND-VALUE if it is unbound."
1117 (save-excursion
1118 (set-buffer buffer)
1119 (if (not (boundp symbol))
1120 unbound-value
1121 (symbol-value symbol))))
1122 (defalias 'url-http-symbol-value-in-buffer 'symbol-value-in-buffer))
1124 (defun url-http-head (url)
1125 (let ((url-request-method "HEAD")
1126 (url-request-data nil))
1127 (url-retrieve-synchronously url)))
1129 ;;;###autoload
1130 (defun url-http-file-exists-p (url)
1131 (let ((status nil)
1132 (exists nil)
1133 (buffer (url-http-head url)))
1134 (if (not buffer)
1135 (setq exists nil)
1136 (setq status (url-http-symbol-value-in-buffer 'url-http-response-status
1137 buffer 500)
1138 exists (and (>= status 200) (< status 300)))
1139 (kill-buffer buffer))
1140 exists))
1142 ;;;###autoload
1143 (defalias 'url-http-file-readable-p 'url-http-file-exists-p)
1145 (defun url-http-head-file-attributes (url &optional id-format)
1146 (let ((buffer (url-http-head url))
1147 (attributes nil))
1148 (when buffer
1149 (setq attributes (make-list 11 nil))
1150 (setf (nth 1 attributes) 1) ; Number of links to file
1151 (setf (nth 2 attributes) 0) ; file uid
1152 (setf (nth 3 attributes) 0) ; file gid
1153 (setf (nth 7 attributes) ; file size
1154 (url-http-symbol-value-in-buffer 'url-http-content-length
1155 buffer -1))
1156 (setf (nth 8 attributes) (eval-when-compile (make-string 10 ?-)))
1157 (kill-buffer buffer))
1158 attributes))
1160 ;;;###autoload
1161 (defun url-http-file-attributes (url &optional id-format)
1162 (if (url-dav-supported-p url)
1163 (url-dav-file-attributes url id-format)
1164 (url-http-head-file-attributes url id-format)))
1166 ;;;###autoload
1167 (defun url-http-options (url)
1168 "Returns a property list describing options available for URL.
1169 This list is retrieved using the `OPTIONS' HTTP method.
1171 Property list members:
1173 methods
1174 A list of symbols specifying what HTTP methods the resource
1175 supports.
1178 A list of numbers specifying what DAV protocol/schema versions are
1179 supported.
1181 dasl
1182 A list of supported DASL search types supported (string form)
1184 ranges
1185 A list of the units available for use in partial document fetches.
1188 The `Platform For Privacy Protection' description for the resource.
1189 Currently this is just the raw header contents. This is likely to
1190 change once P3P is formally supported by the URL package or
1191 Emacs/W3.
1193 (let* ((url-request-method "OPTIONS")
1194 (url-request-data nil)
1195 (buffer (url-retrieve-synchronously url))
1196 (header nil)
1197 (options nil))
1198 (when (and buffer (= 2 (/ (url-http-symbol-value-in-buffer
1199 'url-http-response-status buffer 0) 100)))
1200 ;; Only parse the options if we got a 2xx response code!
1201 (save-excursion
1202 (save-restriction
1203 (save-match-data
1204 (set-buffer buffer)
1205 (mail-narrow-to-head)
1207 ;; Figure out what methods are supported.
1208 (when (setq header (mail-fetch-field "allow"))
1209 (setq options (plist-put
1210 options 'methods
1211 (mapcar 'intern (split-string header "[ ,]+")))))
1213 ;; Check for DAV
1214 (when (setq header (mail-fetch-field "dav"))
1215 (setq options (plist-put
1216 options 'dav
1217 (delq 0
1218 (mapcar 'string-to-number
1219 (split-string header "[, ]+"))))))
1221 ;; Now for DASL
1222 (when (setq header (mail-fetch-field "dasl"))
1223 (setq options (plist-put
1224 options 'dasl
1225 (split-string header "[, ]+"))))
1227 ;; P3P - should get more detailed here. FIXME
1228 (when (setq header (mail-fetch-field "p3p"))
1229 (setq options (plist-put options 'p3p header)))
1231 ;; Check for whether they accept byte-range requests.
1232 (when (setq header (mail-fetch-field "accept-ranges"))
1233 (setq options (plist-put
1234 options 'ranges
1235 (delq 'none
1236 (mapcar 'intern
1237 (split-string header "[, ]+"))))))
1238 ))))
1239 (if buffer (kill-buffer buffer))
1240 options))
1242 (provide 'url-http)
1244 ;; arch-tag: ba7c59ae-c0f4-4a31-9617-d85f221732ee
1245 ;;; url-http.el ends here