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