;;; w3m-cookie.el --- Functions for cookie processing ;; Copyright (C) 2002, 2003 TSUCHIYA Masatoshi ;; Authors: Teranishi Yuuichi ;; Keywords: w3m, WWW, hypermedia ;; This file is a part of emacs-w3m. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, you can either send email to this ;; program's maintainer or write to: The Free Software Foundation, ;; Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA. ;;; Commentary: ;; This file contains the functions for cookies. For more detail ;; about emacs-w3m, see: ;; ;; http://emacs-w3m.namazu.org/ ;; Reference for version 0 cookie: ;; http://www.netscape.com/newsref/std/cookie_spec.html ;; Reference for version 1 cookie: ;; http://www.ietf.org/rfc/rfc2965.txt ;; ;;; Code: (eval-when-compile (require 'cl)) (require 'w3m-util) (require 'w3m) (defvar w3m-cookies nil "A list of cookie elements. Currently only browser local cookies are stored.") (defconst w3m-cookie-two-dot-domains-regexp (concat "\\.\\(" (mapconcat 'identity (list "com" "edu" "net" "org" "gov" "mil" "int") "\\|") "\\)$") "A regular expression of top-level domains that only require two matching '.'s in the domain name in order to set a cookie.") (defcustom w3m-cookie-accept-domains nil "A list of trusted domain name string." :group 'w3m :type '(repeat (string :format "Domain name: %v\n" :size 0))) (defcustom w3m-cookie-reject-domains nil "A list of untrusted domain name string." :group 'w3m :type '(repeat (string :format "Domain name: %v\n" :size 0))) (defcustom w3m-cookie-accept-bad-cookies nil "If nil, don't accept bad cookies. If t, accept bad cookies. If ask, ask user whether accept bad cookies or not." :group 'w3m :type '(radio (const :tag "Don't accept bad cookies" nil) (const :tag "Ask accepting bad cookies" ask) (const :tag "Always accept bad cookies" t))) (defcustom w3m-cookie-file (expand-file-name ".cookie" w3m-profile-directory) "File in which cookies are kept." :group 'w3m :type '(file :size 0)) ;;; Cookie accessor. (defmacro w3m-cookie-url (cookie) `(aref ,cookie 0)) (defmacro w3m-cookie-domain (cookie) `(aref ,cookie 1)) (defmacro w3m-cookie-secure (cookie) `(aref ,cookie 2)) (defmacro w3m-cookie-name (cookie) `(aref ,cookie 3)) (defmacro w3m-cookie-value (cookie) `(aref ,cookie 4)) (defmacro w3m-cookie-path (cookie) `(aref ,cookie 5)) (defmacro w3m-cookie-version (cookie) `(aref ,cookie 6)) (defmacro w3m-cookie-expires (cookie) `(aref ,cookie 7)) (defmacro w3m-cookie-ignore (cookie) `(aref ,cookie 8)) (defsubst w3m-cookie-create (&rest args) (let ((cookie (make-vector 9 nil))) (setf (w3m-cookie-url cookie) (plist-get args :url)) (setf (w3m-cookie-domain cookie) (plist-get args :domain)) (setf (w3m-cookie-secure cookie) (plist-get args :secure)) (setf (w3m-cookie-name cookie) (plist-get args :name)) (setf (w3m-cookie-value cookie) (plist-get args :value)) (setf (w3m-cookie-path cookie) (plist-get args :path)) (setf (w3m-cookie-version cookie) (or (plist-get args :version) 0)) (setf (w3m-cookie-expires cookie) (plist-get args :expires)) (setf (w3m-cookie-ignore cookie) (plist-get args :ignore)) cookie)) (defun w3m-cookie-store (cookie) "Store COOKIE." (let (ignored) (catch 'found (dolist (c w3m-cookies) (when (and (string= (w3m-cookie-domain c) (w3m-cookie-domain cookie)) (string= (w3m-cookie-path c) (w3m-cookie-path cookie)) (string= (w3m-cookie-name c) (w3m-cookie-name cookie))) (if (w3m-cookie-ignore c) (setq ignored t) (setq w3m-cookies (delq c w3m-cookies))) (throw 'found t)))) (unless ignored (push cookie w3m-cookies)))) (defun w3m-cookie-remove (domain path name) "Remove COOKIE if stored." (dolist (c w3m-cookies) (when (and (string= (w3m-cookie-domain c) domain) (string= (w3m-cookie-path c) path) (string= (w3m-cookie-name c) name)) (setq w3m-cookies (delq c w3m-cookies))))) (defun w3m-cookie-retrieve (host path &optional secure) "Retrieve cookies for DOMAIN and PATH." (let ((case-fold-search t) expires cookies) (dolist (c w3m-cookies) (if (and (w3m-cookie-expires c) (w3m-time-newer-p (current-time) (w3m-time-parse-string (w3m-cookie-expires c)))) (push c expires) (when (and (not (w3m-cookie-ignore c)) (string-match (concat (regexp-quote (w3m-cookie-domain c)) "$") host) (string-match (concat "^" (regexp-quote (w3m-cookie-path c))) path)) (if (w3m-cookie-secure c) (if secure (push c cookies)) (push c cookies))))) ;; Delete expired cookies. (dolist (expire expires) (setq w3m-cookies (delq expire w3m-cookies))) cookies)) ;; HTTP URL parser. (defun w3m-parse-http-url (url) "Parse an absolute HTTP URL." (let (secure split) (when (and (string-match w3m-url-components-regexp url) (or (string= (match-string 2 url) "http") (setq secure (string= (match-string 2 url) "https"))) (match-beginning 4) (match-end 4)) (setq split (save-match-data (split-string (match-string 4 url) ":"))) (vector secure (nth 0 split) (string-to-number (or (nth 1 split) "80")) (if (eq (length (match-string 5 url)) 0) "/" (match-string 5 url)))))) (defsubst w3m-http-url-secure (http-url) "Secure flag of the HTTP-URL." (aref http-url 0)) (defsubst w3m-http-url-host (http-url) "Host name of the HTTP-URL." (aref http-url 1)) (defsubst w3m-http-url-port (http-url) "Port number of the HTTP-URL." (aref http-url 2)) (defsubst w3m-http-url-path (http-url) "Path of the HTTP-URL." (aref http-url 3)) ;;; Cookie parser. (defvar w3m-cookie-parse-args-syntax-table (copy-syntax-table emacs-lisp-mode-syntax-table) "A syntax table for parsing sgml attributes.") (modify-syntax-entry ?' "\"" w3m-cookie-parse-args-syntax-table) (modify-syntax-entry ?` "\"" w3m-cookie-parse-args-syntax-table) (modify-syntax-entry ?{ "(" w3m-cookie-parse-args-syntax-table) (modify-syntax-entry ?} ")" w3m-cookie-parse-args-syntax-table) (defun w3m-cookie-parse-args (str &optional nodowncase) (let (name value results name-pos val-pos st nd) (save-excursion (save-restriction (set-buffer (get-buffer-create " *w3m-cookie-parse-temp*")) (set-syntax-table w3m-cookie-parse-args-syntax-table) (erase-buffer) (insert str) (setq st (point-min) nd (point-max)) (set-syntax-table w3m-cookie-parse-args-syntax-table) (narrow-to-region st nd) (goto-char (point-min)) (while (not (eobp)) (skip-chars-forward "; \n\t") (setq name-pos (point)) (skip-chars-forward "^ \n\t=;") (if (not nodowncase) (downcase-region name-pos (point))) (setq name (buffer-substring name-pos (point))) (skip-chars-forward " \t\n") (if (/= (or (char-after (point)) 0) ?=) ; There is no value (setq value nil) (skip-chars-forward " \t\n=") (setq val-pos (point) value (cond ((or (= (or (char-after val-pos) 0) ?\") (= (or (char-after val-pos) 0) ?')) (buffer-substring (1+ val-pos) (condition-case () (prog2 (forward-sexp 1) (1- (point)) (skip-chars-forward "\"")) (error (skip-chars-forward "^ \t\n") (point))))) (t (buffer-substring val-pos (progn (skip-chars-forward "^;") (skip-chars-backward " \t") (point))))))) (setq results (cons (cons name value) results)) (skip-chars-forward "; \n\t")) results)))) (defun w3m-cookie-trusted-host-p (host) "Returns non-nil when the HOST is specified as trusted by user." (let ((accept w3m-cookie-accept-domains) (reject w3m-cookie-reject-domains) (trusted t) regexp tlen rlen) (while accept (cond ((string= (car accept) ".") (setq regexp ".*")) ((string= (car accept) ".local") (setq regexp "^[^\\.]+$")) ((eq (string-to-char (car accept)) ?.) (setq regexp (concat (regexp-quote (car accept)) "$"))) (t (setq regexp (concat "^" (regexp-quote (car accept)) "$")))) (when (string-match regexp host) (setq tlen (length (car accept)) accept nil)) (pop accept)) (while reject (cond ((string= (car reject) ".") (setq regexp ".*")) ((string= (car reject) ".local") (setq regexp "^[^\\.]+$")) ((eq (string-to-char (car reject)) ?.) (setq regexp (concat (regexp-quote (car reject)) "$"))) (t (setq regexp (concat "^" (regexp-quote (car reject)) "$")))) (when (string-match (concat regexp "$") host) (setq rlen (length (car reject)) reject nil)) (pop reject)) (if tlen (if rlen (if (<= tlen rlen) (setq trusted nil))) (if rlen (setq trusted nil))) trusted)) ;;; Version 0 cookie. (defun w3m-cookie-1-acceptable-p (host domain) (let ((numdots 0) (last nil) (case-fold-search t) (mindots 3)) (while (setq last (string-match "\\." domain last)) (setq numdots (1+ numdots) last (1+ last))) (if (string-match w3m-cookie-two-dot-domains-regexp domain) (setq mindots 2)) (cond ((string= host domain) ; Apparently netscape lets you do this t) ((>= numdots mindots) ; We have enough dots in domain name ;; Need to check and make sure the host is actually _in_ the ;; domain it wants to set a cookie for though. (string-match (concat (regexp-quote domain) "$") host)) (t nil)))) (defun w3m-cookie-1-set (url &rest args) ;; Set-Cookie:, version 0 cookie. (let ((http-url (w3m-parse-http-url url)) (case-fold-search t) secure domain expires path rest) (when http-url (setq secure (and (w3m-assoc-ignore-case "secure" args) t) domain (or (cdr-safe (w3m-assoc-ignore-case "domain" args)) (w3m-http-url-host http-url)) expires (cdr-safe (w3m-assoc-ignore-case "expires" args)) path (or (cdr-safe (w3m-assoc-ignore-case "path" args)) (file-name-directory (w3m-http-url-path http-url)))) (while args (if (not (member (downcase (car (car args))) '("secure" "domain" "expires" "path"))) (setq rest (cons (car args) rest))) (setq args (cdr args))) (cond ((not (w3m-cookie-trusted-host-p (w3m-http-url-host http-url))) ;; The site was explicity marked as untrusted by the user nil) ((or (w3m-cookie-1-acceptable-p (w3m-http-url-host http-url) domain) (eq w3m-cookie-accept-bad-cookies t) (and (eq w3m-cookie-accept-bad-cookies 'ask) (y-or-n-p (format "Accept bad cookie from %s for %s? " (w3m-http-url-host http-url) domain)))) ;; Cookie is accepted by the user, and passes our security checks (dolist (elem rest) ;; If a CGI script wishes to delete a cookie, it can do so by ;; returning a cookie with the same name, and an expires time ;; which is in the past. (when (and expires (w3m-time-newer-p (current-time) (w3m-time-parse-string expires))) (w3m-cookie-remove domain path (car elem))) (w3m-cookie-store (w3m-cookie-create :url url :domain domain :name (car elem) :value (cdr elem) :path path :expires expires :secure secure)))) (t (message "%s tried to set a cookie for domain %s - rejected." (w3m-http-url-host http-url) domain)))))) ;;; Version 1 cookie. (defun w3m-cookie-2-acceptable-p (http-url domain) ;; A user agent rejects (SHALL NOT store its information) if the Version ;; attribute is missing. Moreover, a user agent rejects (SHALL NOT ;; store its information) if any of the following is true of the ;; attributes explicitly present in the Set-Cookie2 response header: ;; * The value for the Path attribute is not a prefix of the ;; request-URI. ;; * The value for the Domain attribute contains no embedded dots, ;; and the value is not .local. ;; * The effective host name that derives from the request-host does ;; not domain-match the Domain attribute. ;; * The request-host is a HDN (not IP address) and has the form HD, ;; where D is the value of the Domain attribute, and H is a string ;; that contains one or more dots. ;; * The Port attribute has a "port-list", and the request-port was ;; not in the list. ) (defun w3m-cookie-2-set (url &rest args) ;; Set-Cookie2:, version 1 cookie. ;; Not implemented yet. ) ;;; Save & Load (defvar w3m-cookie-init nil) (defun w3m-cookie-clear () "Clear cookie list." (setq w3m-cookies nil)) (defun w3m-cookie-save () "Save cookies." (let (cookies) (dolist (cookie w3m-cookies) (when (and (w3m-cookie-expires cookie) (w3m-time-newer-p (w3m-time-parse-string (w3m-cookie-expires cookie)) (current-time))) (push cookie cookies))) (w3m-save-list w3m-cookie-file cookies))) (defun w3m-cookie-load () "Load cookies." (when (null w3m-cookies) (setq w3m-cookies (w3m-load-list w3m-cookie-file)))) (defun w3m-cookie-setup () "Setup cookies. Returns immediataly if already initialized." (interactive) (unless w3m-cookie-init (w3m-cookie-load) (setq w3m-cookie-init t))) ;;;###autoload (defun w3m-cookie-shutdown () "Save cookies." (interactive) (w3m-cookie-save) (setq w3m-cookie-init nil) (w3m-cookie-clear) (if (get-buffer " *w3m-cookie-parse-temp*") (kill-buffer (get-buffer " *w3m-cookie-parse-temp*")))) ;;;###autoload (defun w3m-cookie-set (url beg end) "Register cookies which correspond to URL. BEG and END should be an HTTP response header region on current buffer." (w3m-cookie-setup) (when (and url beg end) (save-excursion (let ((case-fold-search t) (version 0) data) (goto-char beg) (while (re-search-forward "^\\(Set-Cookie\\(2\\)?:\\) *\\(.*\\(\n[ \t].*\\)*\\)\n" end t) (setq data (match-string 3)) (if (match-beginning 2) (setq version 1)) (apply (case version (0 'w3m-cookie-1-set) (1 'w3m-cookie-2-set)) url (w3m-cookie-parse-args data 'nodowncase))))))) ;;;###autoload (defun w3m-cookie-get (url) "Get a cookie field string which corresponds to the URL." (w3m-cookie-setup) (let* ((http-url (w3m-parse-http-url url)) (cookies (and http-url (w3m-cookie-retrieve (w3m-http-url-host http-url) (w3m-http-url-path http-url) (w3m-http-url-secure http-url))))) ;; When sending cookies to a server, all cookies with a more specific path ;; mapping should be sent before cookies with less specific path mappings. (setq cookies (sort cookies (lambda (x y) (< (length (w3m-cookie-path x)) (length (w3m-cookie-path y)))))) (when cookies (mapconcat (lambda (cookie) (concat (w3m-cookie-name cookie) "=" (w3m-cookie-value cookie))) cookies "; ")))) ;;;###autoload (defun w3m-cookie (&optional no-cache) "Display cookies and enable you to manage them." (interactive "P") (w3m-goto-url "about://cookie/" no-cache)) ;;;###autoload (defun w3m-about-cookie (url &optional no-decode no-cache post-data &rest args) "Make the html contents to display and to enable you to manage cookies." (unless w3m-use-cookies (error "You must enable emacs-w3m to use cookies.")) (w3m-cookie-setup) (let ((pos 0)) (when post-data (dolist (pair (split-string post-data "&")) (setq pair (split-string pair "=")) (setf (w3m-cookie-ignore (nth (string-to-number (car pair)) w3m-cookies)) (eq (string-to-number (cadr pair)) 0)))) (insert (concat "\ Cookies
Cookies

    ")) (dolist (cookie w3m-cookies) (insert (concat "
  1. " (w3m-cookie-url cookie) "

    " "" "" (when (w3m-cookie-expires cookie) (concat "")) "" (when (w3m-cookie-domain cookie) (concat "")) (when (w3m-cookie-path cookie) (concat "")) "
    Cookie:" (w3m-cookie-name cookie) "=" (w3m-cookie-value cookie) "
    Expires:" (w3m-cookie-expires cookie) "
    Version:" (number-to-string (w3m-cookie-version cookie)) "
    Domain:" (w3m-cookie-domain cookie) "
    Path:" (w3m-cookie-path cookie) "
    Secure:" (if (w3m-cookie-secure cookie) "Yes" "No") "
    " "
    Use:" (format "Yes" pos (if (w3m-cookie-ignore cookie) "" " checked")) "  " (format "No" pos (if (w3m-cookie-ignore cookie) " checked" "")) "

    ")) (setq pos (1+ pos))) (insert "

") "text/html")) (provide 'w3m-cookie) ;;; w3m-cookie.el ends here