]> code.delx.au - gnu-emacs/blob - lisp/url/url-cookie.el
(unhandled-file-name-directory): Add handler.
[gnu-emacs] / lisp / url / url-cookie.el
1 ;;; url-cookie.el --- Netscape Cookie support
2
3 ;; Copyright (C) 1996, 1997, 1998, 1999, 2004,
4 ;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
5
6 ;; Keywords: comm, data, processes, hypermedia
7
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 3, 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
22 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (require 'timezone)
30 (require 'url-util)
31 (require 'url-parse)
32 (eval-when-compile (require 'cl))
33
34 ;; See http://home.netscape.com/newsref/std/cookie_spec.html for the
35 ;; 'open standard' defining this crap.
36 ;;
37 ;; A cookie is stored internally as a vector of 7 slots
38 ;; [ cookie NAME VALUE EXPIRES LOCALPART DOMAIN SECURE ]
39
40 (defsubst url-cookie-name (cookie) (aref cookie 1))
41 (defsubst url-cookie-value (cookie) (aref cookie 2))
42 (defsubst url-cookie-expires (cookie) (aref cookie 3))
43 (defsubst url-cookie-localpart (cookie) (aref cookie 4))
44 (defsubst url-cookie-domain (cookie) (aref cookie 5))
45 (defsubst url-cookie-secure (cookie) (aref cookie 6))
46
47 (defsubst url-cookie-set-name (cookie val) (aset cookie 1 val))
48 (defsubst url-cookie-set-value (cookie val) (aset cookie 2 val))
49 (defsubst url-cookie-set-expires (cookie val) (aset cookie 3 val))
50 (defsubst url-cookie-set-localpart (cookie val) (aset cookie 4 val))
51 (defsubst url-cookie-set-domain (cookie val) (aset cookie 5 val))
52 (defsubst url-cookie-set-secure (cookie val) (aset cookie 6 val))
53 (defsubst url-cookie-retrieve-arg (key args) (nth 1 (memq key args)))
54
55 (defsubst url-cookie-create (&rest args)
56 "Create a cookie vector object from keyword-value pairs ARGS.
57 The keywords allowed are
58 :name NAME
59 :value VALUE
60 :expires TIME
61 :localpart LOCALPAR
62 :domain DOMAIN
63 :secure ???
64 Could someone fill in more information?"
65 (let ((retval (make-vector 7 nil)))
66 (aset retval 0 'cookie)
67 (url-cookie-set-name retval (url-cookie-retrieve-arg :name args))
68 (url-cookie-set-value retval (url-cookie-retrieve-arg :value args))
69 (url-cookie-set-expires retval (url-cookie-retrieve-arg :expires args))
70 (url-cookie-set-localpart retval (url-cookie-retrieve-arg :localpart args))
71 (url-cookie-set-domain retval (url-cookie-retrieve-arg :domain args))
72 (url-cookie-set-secure retval (url-cookie-retrieve-arg :secure args))
73 retval))
74
75 (defun url-cookie-p (obj)
76 "Return non-nil if OBJ is a cookie vector object.
77 These objects represent cookies in the URL package.
78 A cookie vector object is a vector of 7 slots:
79 [cookie NAME VALUE EXPIRES LOCALPART DOMAIN SECURE]."
80 (and (vectorp obj) (= (length obj) 7) (eq (aref obj 0) 'cookie)))
81
82 (defgroup url-cookie nil
83 "URL cookies."
84 :prefix "url-"
85 :prefix "url-cookie-"
86 :group 'url)
87
88 (defvar url-cookie-storage nil "Where cookies are stored.")
89 (defvar url-cookie-secure-storage nil "Where secure cookies are stored.")
90 (defcustom url-cookie-file nil
91 "File where cookies are stored on disk."
92 :type '(choice (const :tag "Default" :value nil) file)
93 :group 'url-file
94 :group 'url-cookie)
95
96 (defcustom url-cookie-confirmation nil
97 "If non-nil, confirmation by the user is required to accept HTTP cookies."
98 :type 'boolean
99 :group 'url-cookie)
100
101 (defcustom url-cookie-multiple-line nil
102 "If nil, HTTP requests put all cookies for the server on one line.
103 Some web servers, such as http://www.hotmail.com/, only accept cookies
104 when they are on one line. This is broken behavior, but just try
105 telling Microsoft that."
106 :type 'boolean
107 :group 'url-cookie)
108
109 (defvar url-cookies-changed-since-last-save nil
110 "Whether the cookies list has changed since the last save operation.")
111
112 (defun url-cookie-parse-file (&optional fname)
113 (setq fname (or fname url-cookie-file))
114 (condition-case ()
115 (load fname nil t)
116 (error
117 ;; It's completely normal for the cookies file not to exist yet.
118 ;; (message "Could not load cookie file %s" fname)
119 )))
120
121 (defun url-cookie-clean-up (&optional secure)
122 (let* (
123 (var (if secure 'url-cookie-secure-storage 'url-cookie-storage))
124 (val (symbol-value var))
125 (cur nil)
126 (new nil)
127 (cookies nil)
128 (cur-cookie nil)
129 (new-cookies nil)
130 )
131 (while val
132 (setq cur (car val)
133 val (cdr val)
134 new-cookies nil
135 cookies (cdr cur))
136 (while cookies
137 (setq cur-cookie (car cookies)
138 cookies (cdr cookies))
139 (if (or (not (url-cookie-p cur-cookie))
140 (url-cookie-expired-p cur-cookie)
141 (null (url-cookie-expires cur-cookie)))
142 nil
143 (setq new-cookies (cons cur-cookie new-cookies))))
144 (if (not new-cookies)
145 nil
146 (setcdr cur new-cookies)
147 (setq new (cons cur new))))
148 (set var new)))
149
150 (defun url-cookie-write-file (&optional fname)
151 (when url-cookies-changed-since-last-save
152 (or fname (setq fname (expand-file-name url-cookie-file)))
153 (if (condition-case nil
154 (progn
155 (url-make-private-file fname)
156 nil)
157 (error t))
158 (message "Error accessing cookie file `%s'" fname)
159 (url-cookie-clean-up)
160 (url-cookie-clean-up t)
161 (with-temp-buffer
162 (insert ";; Emacs-W3 HTTP cookies file\n"
163 ";; Automatically generated file!!! DO NOT EDIT!!!\n\n"
164 "(setq url-cookie-storage\n '")
165 (pp url-cookie-storage (current-buffer))
166 (insert ")\n(setq url-cookie-secure-storage\n '")
167 (pp url-cookie-secure-storage (current-buffer))
168 (insert ")\n")
169 (insert "\f\n;; Local Variables:\n"
170 ";; version-control: never\n"
171 ";; no-byte-compile: t\n"
172 ";; End:\n")
173 (set (make-local-variable 'version-control) 'never)
174 (write-file fname))
175 (setq url-cookies-changed-since-last-save nil))))
176
177 (defun url-cookie-store (name value &optional expires domain localpart secure)
178 "Store a netscape-style cookie."
179 (let* ((storage (if secure url-cookie-secure-storage url-cookie-storage))
180 (tmp storage)
181 (cur nil)
182 (found-domain nil))
183
184 ;; First, look for a matching domain
185 (setq found-domain (assoc domain storage))
186
187 (if found-domain
188 ;; Need to either stick the new cookie in existing domain storage
189 ;; or possibly replace an existing cookie if the names match.
190 (progn
191 (setq storage (cdr found-domain)
192 tmp nil)
193 (while storage
194 (setq cur (car storage)
195 storage (cdr storage))
196 (if (and (equal localpart (url-cookie-localpart cur))
197 (equal name (url-cookie-name cur)))
198 (progn
199 (url-cookie-set-expires cur expires)
200 (url-cookie-set-value cur value)
201 (setq tmp t))))
202 (if (not tmp)
203 ;; New cookie
204 (setcdr found-domain (cons
205 (url-cookie-create :name name
206 :value value
207 :expires expires
208 :domain domain
209 :localpart localpart
210 :secure secure)
211 (cdr found-domain)))))
212 ;; Need to add a new top-level domain
213 (setq tmp (url-cookie-create :name name
214 :value value
215 :expires expires
216 :domain domain
217 :localpart localpart
218 :secure secure))
219 (cond
220 (storage
221 (setcdr storage (cons (list domain tmp) (cdr storage))))
222 (secure
223 (setq url-cookie-secure-storage (list (list domain tmp))))
224 (t
225 (setq url-cookie-storage (list (list domain tmp))))))))
226
227 (defun url-cookie-expired-p (cookie)
228 (let* (
229 (exp (url-cookie-expires cookie))
230 (cur-date (and exp (timezone-parse-date (current-time-string))))
231 (exp-date (and exp (timezone-parse-date exp)))
232 (cur-greg (and cur-date (timezone-absolute-from-gregorian
233 (string-to-number (aref cur-date 1))
234 (string-to-number (aref cur-date 2))
235 (string-to-number (aref cur-date 0)))))
236 (exp-greg (and exp (timezone-absolute-from-gregorian
237 (string-to-number (aref exp-date 1))
238 (string-to-number (aref exp-date 2))
239 (string-to-number (aref exp-date 0)))))
240 (diff-in-days (and exp (- cur-greg exp-greg)))
241 )
242 (cond
243 ((not exp) nil) ; No expiry == expires at browser quit
244 ((< diff-in-days 0) nil) ; Expires sometime after today
245 ((> diff-in-days 0) t) ; Expired before today
246 (t ; Expires sometime today, check times
247 (let* ((cur-time (timezone-parse-time (aref cur-date 3)))
248 (exp-time (timezone-parse-time (aref exp-date 3)))
249 (cur-norm (+ (* 360 (string-to-number (aref cur-time 2)))
250 (* 60 (string-to-number (aref cur-time 1)))
251 (* 1 (string-to-number (aref cur-time 0)))))
252 (exp-norm (+ (* 360 (string-to-number (aref exp-time 2)))
253 (* 60 (string-to-number (aref exp-time 1)))
254 (* 1 (string-to-number (aref exp-time 0))))))
255 (> (- cur-norm exp-norm) 1))))))
256
257 (defun url-cookie-retrieve (host localpart &optional secure)
258 "Retrieve all the netscape-style cookies for a specified HOST and LOCALPART."
259 (let ((storage (if secure
260 (append url-cookie-secure-storage url-cookie-storage)
261 url-cookie-storage))
262 (case-fold-search t)
263 (cookies nil)
264 (cur nil)
265 (retval nil)
266 (localpart-regexp nil))
267 (while storage
268 (setq cur (car storage)
269 storage (cdr storage)
270 cookies (cdr cur))
271 (if (and (car cur)
272 (string-match
273 (concat "^.*"
274 (regexp-quote
275 ;; Remove the dot from wildcard domains
276 ;; before matching.
277 (if (eq ?. (aref (car cur) 0))
278 (substring (car cur) 1)
279 (car cur)))
280 "$") host))
281 ;; The domains match - a possible hit!
282 (while cookies
283 (setq cur (car cookies)
284 cookies (cdr cookies)
285 localpart-regexp (concat "^" (regexp-quote
286 (url-cookie-localpart cur))))
287 (if (and (string-match localpart-regexp localpart)
288 (not (url-cookie-expired-p cur)))
289 (setq retval (cons cur retval))))))
290 retval))
291
292 (defun url-cookie-generate-header-lines (host localpart secure)
293 (let* ((cookies (url-cookie-retrieve host localpart secure))
294 (retval nil)
295 (cur nil)
296 (chunk nil))
297 ;; Have to sort this for sending most specific cookies first
298 (setq cookies (and cookies
299 (sort cookies
300 (function
301 (lambda (x y)
302 (> (length (url-cookie-localpart x))
303 (length (url-cookie-localpart y))))))))
304 (while cookies
305 (setq cur (car cookies)
306 cookies (cdr cookies)
307 chunk (format "%s=%s" (url-cookie-name cur) (url-cookie-value cur))
308 retval (if (and url-cookie-multiple-line
309 (< 80 (+ (length retval) (length chunk) 4)))
310 (concat retval "\r\nCookie: " chunk)
311 (if retval
312 (concat retval "; " chunk)
313 (concat "Cookie: " chunk)))))
314 (if retval
315 (concat retval "\r\n")
316 "")))
317
318 (defvar url-cookie-two-dot-domains
319 (concat "\\.\\("
320 (mapconcat 'identity (list "com" "edu" "net" "org" "gov" "mil" "int")
321 "\\|")
322 "\\)$")
323 "A regexp of top level domains that only require two matching
324 '.'s in the domain name in order to set a cookie.")
325
326 (defcustom url-cookie-trusted-urls nil
327 "A list of regular expressions matching URLs to always accept cookies from."
328 :type '(repeat regexp)
329 :group 'url-cookie)
330
331 (defcustom url-cookie-untrusted-urls nil
332 "A list of regular expressions matching URLs to never accept cookies from."
333 :type '(repeat regexp)
334 :group 'url-cookie)
335
336 (defun url-cookie-host-can-set-p (host domain)
337 (let ((numdots 0)
338 (last nil)
339 (case-fold-search t)
340 (mindots 3))
341 (while (setq last (string-match "\\." domain last))
342 (setq numdots (1+ numdots)
343 last (1+ last)))
344 (if (string-match url-cookie-two-dot-domains domain)
345 (setq mindots 2))
346 (cond
347 ((string= host domain) ; Apparently netscape lets you do this
348 t)
349 ((>= numdots mindots) ; We have enough dots in domain name
350 ;; Need to check and make sure the host is actually _in_ the
351 ;; domain it wants to set a cookie for though.
352 (string-match (concat (regexp-quote
353 ;; Remove the dot from wildcard domains
354 ;; before matching.
355 (if (eq ?. (aref domain 0))
356 (substring domain 1)
357 domain))
358 "$") host))
359 (t
360 nil))))
361
362 (defun url-cookie-handle-set-cookie (str)
363 (setq url-cookies-changed-since-last-save t)
364 (let* ((args (url-parse-args str t))
365 (case-fold-search t)
366 (secure (and (assoc-string "secure" args t) t))
367 (domain (or (cdr-safe (assoc-string "domain" args t))
368 (url-host url-current-object)))
369 (current-url (url-view-url t))
370 (trusted url-cookie-trusted-urls)
371 (untrusted url-cookie-untrusted-urls)
372 (expires (cdr-safe (assoc-string "expires" args t)))
373 (localpart (or (cdr-safe (assoc-string "path" args t))
374 (file-name-directory
375 (url-filename url-current-object))))
376 (rest nil))
377 (while args
378 (if (not (member (downcase (car (car args)))
379 '("secure" "domain" "expires" "path")))
380 (setq rest (cons (car args) rest)))
381 (setq args (cdr args)))
382
383 ;; Sometimes we get dates that the timezone package cannot handle very
384 ;; gracefully - take care of this here, instead of in url-cookie-expired-p
385 ;; to speed things up.
386 (if (and expires
387 (string-match
388 (concat "^[^,]+, +\\(..\\)-\\(...\\)-\\(..\\) +"
389 "\\(..:..:..\\) +\\[*\\([^\]]+\\)\\]*$")
390 expires))
391 (setq expires (concat (match-string 1 expires) " "
392 (match-string 2 expires) " "
393 (match-string 3 expires) " "
394 (match-string 4 expires) " ["
395 (match-string 5 expires) "]")))
396
397 ;; This one is for older Emacs/XEmacs variants that don't
398 ;; understand this format without tenths of a second in it.
399 ;; Wednesday, 30-Dec-2037 16:00:00 GMT
400 ;; - vs -
401 ;; Wednesday, 30-Dec-2037 16:00:00.00 GMT
402 (if (and expires
403 (string-match
404 "\\([0-9]+\\)-\\([A-Za-z]+\\)-\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9]+:[0-9]+\\)\\(\\.[0-9]+\\)*[ \t]+\\([-+a-zA-Z0-9]+\\)"
405 expires))
406 (setq expires (concat (match-string 1 expires) "-" ; day
407 (match-string 2 expires) "-" ; month
408 (match-string 3 expires) " " ; year
409 (match-string 4 expires) ".00 " ; hour:minutes:seconds
410 (match-string 6 expires)))) ":" ; timezone
411
412 (while (consp trusted)
413 (if (string-match (car trusted) current-url)
414 (setq trusted (- (match-end 0) (match-beginning 0)))
415 (pop trusted)))
416 (while (consp untrusted)
417 (if (string-match (car untrusted) current-url)
418 (setq untrusted (- (match-end 0) (match-beginning 0)))
419 (pop untrusted)))
420 (if (and trusted untrusted)
421 ;; Choose the more specific match
422 (if (> trusted untrusted)
423 (setq untrusted nil)
424 (setq trusted nil)))
425 (cond
426 (untrusted
427 ;; The site was explicity marked as untrusted by the user
428 nil)
429 ((or (eq url-privacy-level 'paranoid)
430 (and (listp url-privacy-level) (memq 'cookies url-privacy-level)))
431 ;; user never wants cookies
432 nil)
433 ((and url-cookie-confirmation
434 (not trusted)
435 (save-window-excursion
436 (with-output-to-temp-buffer "*Cookie Warning*"
437 (mapcar
438 (function
439 (lambda (x)
440 (princ (format "%s - %s" (car x) (cdr x))))) rest))
441 (prog1
442 (not (funcall url-confirmation-func
443 (format "Allow %s to set these cookies? "
444 (url-host url-current-object))))
445 (if (get-buffer "*Cookie Warning*")
446 (kill-buffer "*Cookie Warning*")))))
447 ;; user wants to be asked, and declined.
448 nil)
449 ((url-cookie-host-can-set-p (url-host url-current-object) domain)
450 ;; Cookie is accepted by the user, and passes our security checks
451 (let ((cur nil))
452 (while rest
453 (setq cur (pop rest))
454 (url-cookie-store (car cur) (cdr cur)
455 expires domain localpart secure))))
456 (t
457 (message "%s tried to set a cookie for domain %s - rejected."
458 (url-host url-current-object) domain)))))
459
460 (defvar url-cookie-timer nil)
461
462 (defcustom url-cookie-save-interval 3600
463 "The number of seconds between automatic saves of cookies.
464 Default is 1 hour. Note that if you change this variable outside of
465 the `customize' interface after `url-do-setup' has been run, you need
466 to run the `url-cookie-setup-save-timer' function manually."
467 :set #'(lambda (var val)
468 (set-default var val)
469 (if (bound-and-true-p url-setup-done)
470 (url-cookie-setup-save-timer)))
471 :type 'integer
472 :group 'url-cookie)
473
474 (defun url-cookie-setup-save-timer ()
475 "Reset the cookie saver timer."
476 (interactive)
477 (ignore-errors (cancel-timer url-cookie-timer))
478 (setq url-cookie-timer nil)
479 (if url-cookie-save-interval
480 (setq url-cookie-timer (run-at-time url-cookie-save-interval
481 url-cookie-save-interval
482 #'url-cookie-write-file))))
483
484 (provide 'url-cookie)
485
486 ;; arch-tag: 2568751b-6452-4398-aa2d-303edadb54d7
487 ;;; url-cookie.el ends here