Mercurial > hg > xemacs-beta
diff lisp/url/url.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ac2d302a0011 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/url/url.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,2394 @@ +;;; url.el,v --- Uniform Resource Locator retrieval tool +;; Author: wmperry +;; Created: 1996/05/30 13:25:47 +;; Version: 1.52 +;; Keywords: comm, data, processes, hypermedia + +;;; LCD Archive Entry: +;;; url|William M. Perry|wmperry@spry.com| +;;; Major mode for manipulating URLs| +;;; 1996/05/30 13:25:47|1.52|Location Undetermined +;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Copyright (c) 1993, 1994, 1995 by William M. Perry (wmperry@spry.com) +;;; +;;; This file is not part of GNU Emacs, but the same permissions apply. +;;; +;;; GNU Emacs 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. +;;; +;;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to +;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Copyright (c) 1993, 1994, 1995 by William M. Perry (wmperry@spry.com) ;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(require 'url-vars) +(require 'url-parse) +(require 'urlauth) +(require 'url-cookie) +(require 'mm) +(require 'md5) +(require 'base64) +(require 'url-hash) +(or (featurep 'efs) + (featurep 'efs-auto) + (condition-case () + (require 'ange-ftp) + (error nil))) + +(load-library "url-sysdp") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Functions that might not exist in old versions of emacs +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun url-save-error (errobj) + (save-excursion + (set-buffer (get-buffer-create " *url-error*")) + (erase-buffer)) + (display-error errobj (get-buffer-create " *url-error*"))) + +(cond + ((fboundp 'display-warning) + (fset 'url-warn 'display-warning)) + ((fboundp 'w3-warn) + (fset 'url-warn 'w3-warn)) + ((fboundp 'warn) + (defun url-warn (class message &optional level) + (warn "(%s/%s) %s" class (or level 'warning) message))) + (t + (defun url-warn (class message &optional level) + (save-excursion + (set-buffer (get-buffer-create "*W3-WARNINGS*")) + (goto-char (point-max)) + (save-excursion + (insert (format "(%s/%s) %s\n" class (or level 'warning) message))) + (display-buffer (current-buffer)))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Autoload all the URL loaders +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(autoload 'url-file "url-file") +(autoload 'url-ftp "url-file") +(autoload 'url-gopher "url-gopher") +(autoload 'url-irc "url-irc") +(autoload 'url-http "url-http") +(autoload 'url-mailserver "url-mail") +(autoload 'url-mailto "url-mail") +(autoload 'url-info "url-misc") +(autoload 'url-shttp "url-http") +(autoload 'url-https "url-http") +(autoload 'url-finger "url-misc") +(autoload 'url-rlogin "url-misc") +(autoload 'url-telnet "url-misc") +(autoload 'url-tn3270 "url-misc") +(autoload 'url-proxy "url-misc") +(autoload 'url-x-exec "url-misc") +(autoload 'url-news "url-news") +(autoload 'url-nntp "url-news") +(autoload 'url-decode-pgp/pem "url-pgp") +(autoload 'url-wais "url-wais") + +(autoload 'url-save-newsrc "url-news") +(autoload 'url-news-generate-reply-form "url-news") +(autoload 'url-parse-newsrc "url-news") +(autoload 'url-mime-response-p "url-http") +(autoload 'url-parse-mime-headers "url-http") +(autoload 'url-handle-refresh-header "url-http") +(autoload 'url-create-mime-request "url-http") +(autoload 'url-create-message-id "url-http") +(autoload 'url-create-multipart-request "url-http") +(autoload 'url-parse-viewer-types "url-http") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; File-name-handler-alist functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun url-setup-file-name-handlers () + ;; Setup file-name handlers. + '(cond + ((not (boundp 'file-name-handler-alist)) + nil) ; Don't load if no alist + ((rassq 'url-file-handler file-name-handler-alist) + nil) ; Don't load twice + ((and (string-match "XEmacs\\|Lucid" emacs-version) + (< url-emacs-minor-version 11)) ; Don't load in lemacs 19.10 + nil) + (t + (setq file-name-handler-alist + (let ((new-handler (cons + (concat "^/*" + (substring url-nonrelative-link1 nil)) + 'url-file-handler))) + (if file-name-handler-alist + (append (list new-handler) file-name-handler-alist) + (list new-handler))))))) + +(defun url-file-handler (operation &rest args) + ;; Function called from the file-name-handler-alist routines. OPERATION + ;; is what needs to be done ('file-exists-p, etc). args are the arguments + ;; that would have been passed to OPERATION." + (let ((fn (get operation 'url-file-handlers)) + (url (car args)) + (myargs (cdr args))) + (if (= (string-to-char url) ?/) + (setq url (substring url 1 nil))) + (if fn (apply fn url myargs) + (let (file-name-handler-alist) + (apply operation url myargs))))) + +(defun url-file-handler-identity (&rest args) + (car args)) + +(defun url-file-handler-null (&rest args) + nil) + +(put 'file-directory-p 'url-file-handlers 'url-file-handler-null) +(put 'substitute-in-file-name 'url-file-handlers 'url-file-handler-identity) +(put 'file-writable-p 'url-file-handlers 'url-file-handler-null) +(put 'file-truename 'url-file-handlers 'url-file-handler-identity) +(put 'insert-file-contents 'url-file-handlers 'url-insert-file-contents) +(put 'expand-file-name 'url-file-handlers 'url-expand-file-name) +(put 'directory-files 'url-file-handlers 'url-directory-files) +(put 'file-directory-p 'url-file-handlers 'url-file-directory-p) +(put 'file-writable-p 'url-file-handlers 'url-file-writable-p) +(put 'file-readable-p 'url-file-handlers 'url-file-exists) +(put 'file-executable-p 'url-file-handlers 'null) +(put 'file-symlink-p 'url-file-handlers 'null) +(put 'file-exists-p 'url-file-handlers 'url-file-exists) +(put 'copy-file 'url-file-handlers 'url-copy-file) +(put 'file-attributes 'url-file-handlers 'url-file-attributes) +(put 'file-name-all-completions 'url-file-handlers + 'url-file-name-all-completions) +(put 'file-name-completion 'url-file-handlers 'url-file-name-completion) +(put 'file-local-copy 'url-file-handlers 'url-file-local-copy) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Utility functions +;;; ----------------- +;;; Various functions used around the url code. +;;; Some of these qualify as hacks, but hey, this is elisp. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(if (fboundp 'mm-string-to-tokens) + (fset 'url-string-to-tokens 'mm-string-to-tokens) + (defun url-string-to-tokens (str &optional delim) + "Return a list of words from the string STR" + (setq delim (or delim ? )) + (let (results y) + (mapcar + (function + (lambda (x) + (cond + ((and (= x delim) y) (setq results (cons y results) y nil)) + ((/= x delim) (setq y (concat y (char-to-string x)))) + (t nil)))) str) + (nreverse (cons y results))))) + +(defun url-days-between (date1 date2) + ;; Return the number of days between date1 and date2. + (- (url-day-number date1) (url-day-number date2))) + +(defun url-day-number (date) + (let ((dat (mapcar (function (lambda (s) (and s (string-to-int s)) )) + (timezone-parse-date date)))) + (timezone-absolute-from-gregorian + (nth 1 dat) (nth 2 dat) (car dat)))) + +(defun url-seconds-since-epoch (date) + ;; Returns a number that says how many seconds have + ;; lapsed between Jan 1 12:00:00 1970 and DATE." + (let* ((tdate (mapcar (function (lambda (ti) (and ti (string-to-int ti)))) + (timezone-parse-date date))) + (ttime (mapcar (function (lambda (ti) (and ti (string-to-int ti)))) + (timezone-parse-time + (aref (timezone-parse-date date) 3)))) + (edate (mapcar (function (lambda (ti) (and ti (string-to-int ti)))) + (timezone-parse-date "Jan 1 12:00:00 1970"))) + (tday (- (timezone-absolute-from-gregorian + (nth 1 tdate) (nth 2 tdate) (nth 0 tdate)) + (timezone-absolute-from-gregorian + (nth 1 edate) (nth 2 edate) (nth 0 edate))))) + (+ (nth 2 ttime) + (* (nth 1 ttime) 60) + (* (nth 0 ttime) 60 60) + (* tday 60 60 24)))) + +(defun url-match (s x) + ;; Return regexp match x in s. + (substring s (match-beginning x) (match-end x))) + +(defun url-split (str del) + ;; Split the string STR, with DEL (a regular expression) as the delimiter. + ;; Returns an assoc list that you can use with completing-read." + (let (x y) + (while (string-match del str) + (setq y (substring str 0 (match-beginning 0)) + str (substring str (match-end 0) nil)) + (if (not (string-match "^[ \t]+$" y)) + (setq x (cons (list y y) x)))) + (if (not (equal str "")) + (setq x (cons (list str str) x))) + x)) + +(defun url-replace-regexp (regexp to-string) + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (replace-match to-string t nil))) + +(defun url-clear-tmp-buffer () + (set-buffer (get-buffer-create url-working-buffer)) + (if buffer-read-only (toggle-read-only)) + (erase-buffer)) + +(defun url-maybe-relative (url) + (url-retrieve (url-expand-file-name url))) + +(defun url-buffer-is-hypertext (&optional buff) + "Return t if a buffer contains HTML, as near as we can guess." + (setq buff (or buff (current-buffer))) + (save-excursion + (set-buffer buff) + (let ((case-fold-search t)) + (goto-char (point-min)) + (re-search-forward + "<\\(TITLE\\|HEAD\\|BASE\\|H[0-9]\\|ISINDEX\\|P\\)>" nil t)))) + +(defun nntp-after-change-function (&rest args) + (save-excursion + (set-buffer nntp-server-buffer) + (message "Read %d bytes" (point-max)))) + +(defun url-percentage (x y) + (if (fboundp 'float) + (round (* 100 (/ x (float y)))) + (/ (* x 100) y))) + +(defun url-after-change-function (&rest args) + ;; The nitty gritty details of messaging the HTTP/1.0 status messages + ;; in the minibuffer." + (save-excursion + (set-buffer url-working-buffer) + (let (status-message) + (if url-current-content-length + nil + (goto-char (point-min)) + (skip-chars-forward " \t\n") + (if (not (looking-at "HTTP/[0-9]\.[0-9]")) + (setq url-current-content-length 0) + (setq url-current-isindex + (and (re-search-forward "$\r*$" nil t) (point))) + (if (re-search-forward + "^content-type:[ \t]*\\([^\r\n]+\\)\r*$" + url-current-isindex t) + (setq url-current-mime-type (downcase + (url-eat-trailing-space + (buffer-substring + (match-beginning 1) + (match-end 1)))))) + (if (re-search-forward "^content-length:\\([^\r\n]+\\)\r*$" + url-current-isindex t) + (setq url-current-content-length + (string-to-int (buffer-substring (match-beginning 1) + (match-end 1)))) + (setq url-current-content-length nil)))) + (goto-char (point-min)) + (if (re-search-forward "^status:\\([^\r]*\\)" url-current-isindex t) + (progn + (setq status-message (buffer-substring (match-beginning 1) + (match-end 1))) + (replace-match (concat "btatus:" status-message)))) + (goto-char (point-max)) + (cond + (status-message (url-lazy-message "%s" status-message)) + ((and url-current-content-length (> url-current-content-length 1) + url-current-mime-type) + (url-lazy-message "Read %d of %d bytes (%d%%) [%s]" + (point-max) url-current-content-length + (url-percentage (point-max) url-current-content-length) + url-current-mime-type)) + ((and url-current-content-length (> url-current-content-length 1)) + (url-lazy-message "Read %d of %d bytes (%d%%)" + (point-max) url-current-content-length + (url-percentage (point-max) + url-current-content-length))) + ((and (/= 1 (point-max)) url-current-mime-type) + (url-lazy-message "Read %d bytes. [%s]" (point-max) + url-current-mime-type)) + ((/= 1 (point-max)) + (url-lazy-message "Read %d bytes." (point-max))) + (t (url-lazy-message "Waiting for response.")))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Information information +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar url-process-lookup-table nil) + +(defun url-setup-process-get () + (let ((x nil) + (nativep t)) + (condition-case () + (progn + (setq x (start-process "Test" nil "/bin/sh")) + (get x 'command)) + (error (setq nativep nil))) + (cond + ((fboundp 'process-get) ; Emacs 19.31 w/my hacks + (defun url-process-get (proc prop &optional default) + (or (process-get proc prop) default))) + (nativep ; XEmacs 19.14 w/my hacks + (fset 'url-process-get 'get)) + (t + (defun url-process-get (proc prop &optional default) + (or (plist-get (cdr-safe (assq proc url-process-lookup-table)) prop) + default)))) + (cond + ((fboundp 'process-put) ; Emacs 19.31 w/my hacks + (fset 'url-process-put 'process-put)) + (nativep + (fset 'url-process-put 'put)) + (t + (defun url-process-put (proc prop val) + (let ((node (assq proc url-process-lookup-table))) + (if (not node) + (setq url-process-lookup-table (cons (cons proc (list prop val)) + url-process-lookup-table)) + (setcdr node (plist-put (cdr node) prop val))))))) + (and (processp x) (delete-process x)))) + +(defun url-gc-process-lookup-table () + (let (new) + (while url-process-lookup-table + (if (not (memq (process-status (caar url-process-lookup-table)) + '(stop closed nil))) + (setq new (cons (car url-process-lookup-table) new))) + (setq url-process-lookup-table (cdr url-process-lookup-table))) + (setq url-process-lookup-table new))) + +(defun url-list-processes () + (interactive) + (url-gc-process-lookup-table) + (let ((processes (process-list)) + proc len type) + (set-buffer (get-buffer-create "URL Status Display")) + (display-buffer (current-buffer)) + (erase-buffer) + (insert + (eval-when-compile (format "%-40s%-10s%-25s" "URL" "Size" "Type")) "\n" + (eval-when-compile (make-string 75 ?-)) "\n") + (while processes + (setq proc (car processes) + processes (cdr processes)) + (if (url-process-get proc 'url) + (progn + (save-excursion + (set-buffer (process-buffer proc)) + (setq len url-current-content-length + type url-current-mime-type)) + (insert + (format "%-40s%-10d%-25s" (url-process-get proc 'url) + len type))))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; file-name-handler stuff calls this +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun url-have-visited-url (url &rest args) + "Return non-nil iff the user has visited URL before. +The return value is a cons of the url and the date last accessed as a string" + (url-gethash url url-global-history-hash-table)) + +(defun url-directory-files (url &rest args) + "Return a list of files on a server." + nil) + +(defun url-file-writable-p (url &rest args) + "Return t iff a url is writable by this user" + nil) + +(defun url-copy-file (url &rest args) + "Copy a url to the specified filename." + nil) + +(defun url-file-directly-accessible-p (url) + "Returns t iff the specified URL is directly accessible +on your filesystem. (nfs, local file, etc)." + (let* ((urlobj (if (vectorp url) url (url-generic-parse-url url))) + (type (url-type urlobj))) + (and (member type '("file" "ftp")) + (not (url-host urlobj))))) + +;;;###autoload +(defun url-file-attributes (url &rest args) + "Return a list of attributes of URL. +Value is nil if specified file cannot be opened. +Otherwise, list elements are: + 0. t for directory, string (name linked to) for symbolic link, or nil. + 1. Number of links to file. + 2. File uid. + 3. File gid. + 4. Last access time, as a list of two integers. + First integer has high-order 16 bits of time, second has low 16 bits. + 5. Last modification time, likewise. + 6. Last status change time, likewise. + 7. Size in bytes. (-1, if number is out of range). + 8. File modes, as a string of ten letters or dashes as in ls -l. + If URL is on an http server, this will return the content-type if possible. + 9. t iff file's gid would change if file were deleted and recreated. +10. inode number. +11. Device number. + +If file does not exist, returns nil." + (and url + (let* ((urlobj (url-generic-parse-url url)) + (type (url-type urlobj)) + (url-automatic-caching nil) + (data nil) + (exists nil)) + (cond + ((equal type "http") + (cond + ((not url-be-anal-about-file-attributes) + (setq data (list + (url-file-directory-p url) ; Directory + 1 ; number of links to it + 0 ; UID + 0 ; GID + (cons 0 0) ; Last access time + (cons 0 0) ; Last mod. time + (cons 0 0) ; Last status time + -1 ; file size + (mm-extension-to-mime + (url-file-extension (url-filename urlobj))) + nil ; gid would change + 0 ; inode number + 0 ; device number + ))) + (t ; HTTP/1.0, use HEAD + (let ((url-request-method "HEAD") + (url-request-data nil) + (url-working-buffer " *url-temp*")) + (save-excursion + (condition-case () + (progn + (url-retrieve url) + (setq data (and + (setq exists + (cdr + (assoc "status" + url-current-mime-headers))) + (>= exists 200) + (< exists 300) + (list + (url-file-directory-p url) ; Directory + 1 ; links to + 0 ; UID + 0 ; GID + (cons 0 0) ; Last access time + (cons 0 0) ; Last mod. time + (cons 0 0) ; Last status time + (or ; Size in bytes + (cdr (assoc "content-length" + url-current-mime-headers)) + -1) + (or + (cdr (assoc "content-type" + url-current-mime-headers)) + (mm-extension-to-mime + (url-file-extension + (url-filename urlobj)))) ; content-type + nil ; gid would change + 0 ; inode number + 0 ; device number + )))) + (error nil)) + (and (not data) + (setq data (list (url-file-directory-p url) + 1 0 0 (cons 0 0) (cons 0 0) (cons 0 0) + -1 (mm-extension-to-mime + (url-file-extension + url-current-file)) + nil 0 0))) + (kill-buffer " *url-temp*")))))) + ((member type '("ftp" "file")) + (let ((fname (if (url-host urlobj) + (concat "/" + (if (url-user urlobj) + (concat (url-user urlobj) "@") + "") + (url-host urlobj) ":" + (url-filename urlobj)) + (url-filename urlobj)))) + (setq data (or (file-attributes fname) (make-list 12 nil))) + (setcar (cdr (cdr (cdr (cdr (cdr (cdr (cdr (cdr data)))))))) + (mm-extension-to-mime (url-file-extension fname))))) + (t nil)) + data))) + +(defun url-file-name-all-completions (file dirname &rest args) + "Return a list of all completions of file name FILE in directory DIR. +These are all file names in directory DIR which begin with FILE." + ;; need to rewrite + ) + +(defun url-file-name-completion (file dirname &rest args) + "Complete file name FILE in directory DIR. +Returns the longest string +common to all filenames in DIR that start with FILE. +If there is only one and FILE matches it exactly, returns t. +Returns nil if DIR contains no name starting with FILE." + (apply 'url-file-name-all-completions file dirname args)) + +(defun url-file-local-copy (file &rest args) + "Copy the file FILE into a temporary file on this machine. +Returns the name of the local copy, or nil, if FILE is directly +accessible." + nil) + +(defun url-insert-file-contents (url &rest args) + "Insert the contents of the URL in this buffer." + (save-excursion + (let ((old-asynch url-be-asynchronous)) + (setq-default url-be-asynchronous nil) + (url-retrieve url) + (setq-default url-be-asynchronous old-asynch))) + (insert-buffer url-working-buffer) + (setq buffer-file-name url) + (kill-buffer url-working-buffer)) + +(defun url-file-directory-p (url &rest args) + "Return t iff a url points to a directory" + (equal (substring url -1 nil) "/")) + +(defun url-file-exists (url &rest args) + "Return t iff a file exists." + (let* ((urlobj (url-generic-parse-url url)) + (type (url-type urlobj)) + (exists nil)) + (cond + ((equal type "http") ; use head + (let ((url-request-method "HEAD") + (url-request-data nil) + (url-working-buffer " *url-temp*")) + (save-excursion + (url-retrieve url) + (setq exists (or (cdr + (assoc "status" url-current-mime-headers)) 500)) + (kill-buffer " *url-temp*") + (setq exists (and (>= exists 200) (< exists 300)))))) + ((member type '("ftp" "file")) ; file-attributes + (let ((fname (if (url-host urlobj) + (concat "/" + (if (url-user urlobj) + (concat (url-user urlobj) "@") + "") + (url-host urlobj) ":" + (url-filename urlobj)) + (url-filename urlobj)))) + (setq exists (file-exists-p fname)))) + (t nil)) + exists)) + +;;;###autoload +(defun url-normalize-url (url) + "Return a 'normalized' version of URL. This strips out default port +numbers, etc." + (let (type data grok retval) + (setq data (url-generic-parse-url url) + type (url-type data)) + (if (member type '("www" "about" "mailto" "mailserver" "info")) + (setq retval url) + (setq retval (url-recreate-url data))) + retval)) + +;;;###autoload +(defun url-buffer-visiting (url) + "Return the name of a buffer (if any) that is visiting URL." + (setq url (url-normalize-url url)) + (let ((bufs (buffer-list)) + (found nil)) + (if (condition-case () + (string-match "\\(.*\\)#" url) + (error nil)) + (setq url (url-match url 1))) + (while (and bufs (not found)) + (save-excursion + (set-buffer (car bufs)) + (setq found (if (and + (not (equal (buffer-name (car bufs)) + url-working-buffer)) + (memq major-mode '(url-mode w3-mode)) + (equal (url-view-url t) url)) (car bufs) nil) + bufs (cdr bufs)))) + found)) + +(defun url-file-size (url &rest args) + "Return the size of a file in bytes, or -1 if can't be determined." + (let* ((urlobj (url-generic-parse-url url)) + (type (url-type urlobj)) + (size -1) + (data nil)) + (cond + ((equal type "http") ; use head + (let ((url-request-method "HEAD") + (url-request-data nil) + (url-working-buffer " *url-temp*")) + (save-excursion + (url-retrieve url) + (setq size (or (cdr + (assoc "content-length" url-current-mime-headers)) + -1)) + (kill-buffer " *url-temp*")))) + ((member type '("ftp" "file")) ; file-attributes + (let ((fname (if (url-host urlobj) + (concat "/" + (if (url-user urlobj) + (concat (url-user urlobj) "@") + "") + (url-host urlobj) ":" + (url-filename urlobj)) + (url-filename urlobj)))) + (setq data (file-attributes fname) + size (nth 7 data)))) + (t nil)) + (cond + ((stringp size) (string-to-int size)) + ((integerp size) size) + ((null size) -1) + (t -1)))) + +(defun url-generate-new-buffer-name (start) + "Create a new buffer name based on START." + (let ((x 1) + name) + (if (not (get-buffer start)) + start + (progn + (setq name (format "%s<%d>" start x)) + (while (get-buffer name) + (setq x (1+ x) + name (format "%s<%d>" start x))) + name)))) + +(defun url-generate-unique-filename (&optional fmt) + "Generate a unique filename in url-temporary-directory" + (if (not fmt) + (let ((base (format "url-tmp.%d" (user-real-uid))) + (fname "") + (x 0)) + (setq fname (format "%s%d" base x)) + (while (file-exists-p (expand-file-name fname url-temporary-directory)) + (setq x (1+ x) + fname (concat base (int-to-string x)))) + (expand-file-name fname url-temporary-directory)) + (let ((base (concat "url" (int-to-string (user-real-uid)))) + (fname "") + (x 0)) + (setq fname (format fmt (concat base (int-to-string x)))) + (while (file-exists-p (expand-file-name fname url-temporary-directory)) + (setq x (1+ x) + fname (format fmt (concat base (int-to-string x))))) + (expand-file-name fname url-temporary-directory)))) + +(defun url-lazy-message (&rest args) + "Just like `message', but is a no-op if called more than once a second. +Will not do anything if url-show-status is nil." + (if (or (null url-show-status) + (= url-lazy-message-time + (setq url-lazy-message-time (nth 1 (current-time))))) + nil + (apply 'message args))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Gateway Support +;;; --------------- +;;; Fairly good/complete gateway support +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun url-kill-process (proc) + "Kill the process PROC - knows about all the various gateway types, +and acts accordingly." + (cond + ((eq url-gateway-method 'native) (delete-process proc)) + ((eq url-gateway-method 'program) (kill-process proc)) + (t (error "Unknown url-gateway-method %s" url-gateway-method)))) + +(defun url-accept-process-output (proc) + "Allow any pending output from subprocesses to be read by Emacs. +It is read into the process' buffers or given to their filter functions. +Where possible, this will not exit until some output is received from PROC, +or 1 second has elapsed." + (accept-process-output proc 1)) + +(defun url-process-status (proc) + "Return the process status of a url buffer" + (cond + ((memq url-gateway-method '(native ssl program)) (process-status proc)) + (t (error "Unkown url-gateway-method %s" url-gateway-method)))) + +(defun url-open-stream (name buffer host service) + "Open a stream to a host" + (let ((tmp-gateway-method (if (and url-gateway-local-host-regexp + (not (eq 'ssl url-gateway-method)) + (string-match + url-gateway-local-host-regexp + host)) + 'native + url-gateway-method)) + (tcp-binary-process-output-services (if (stringp service) + (list service) + (list service + (int-to-string service))))) + (and (eq url-gateway-method 'tcp) + (require 'tcp) + (setq url-gateway-method 'native + tmp-gateway-method 'native)) + (cond + ((eq tmp-gateway-method 'ssl) + (open-ssl-stream name buffer host service)) + ((eq tmp-gateway-method 'native) + (if url-broken-resolution + (setq host + (cond + ((featurep 'ange-ftp) (ange-ftp-nslookup-host host)) + ((featurep 'efs) (efs-nslookup-host host)) + ((featurep 'efs-auto) (efs-nslookup-host host)) + (t host)))) + (let ((max-retries url-connection-retries) + (cur-retries 0) + (retry t) + (errobj nil) + (conn nil)) + (while (and (not conn) retry) + (condition-case errobj + (setq conn (open-network-stream name buffer host service)) + (error + (url-save-error errobj) + (save-window-excursion + (save-excursion + (switch-to-buffer-other-window " *url-error*") + (shrink-window-if-larger-than-buffer) + (goto-char (point-min)) + (if (and (re-search-forward "in use" nil t) + (< cur-retries max-retries)) + (progn + (setq retry t + cur-retries (1+ cur-retries)) + (sleep-for 0.5)) + (setq cur-retries 0 + retry (funcall url-confirmation-func + (concat "Connection to " host + " failed, retry? ")))) + (kill-buffer (current-buffer))))))) + (if conn + (progn + (if (featurep 'mule) + (save-excursion + (set-buffer (get-buffer-create buffer)) + (setq mc-flag nil) + (set-process-coding-system conn *noconv* *noconv*))) + conn) + (error "Unable to connect to %s:%s" host service)))) + ((eq tmp-gateway-method 'program) + (let ((proc (start-process name buffer url-gateway-telnet-program host + (int-to-string service))) + (tmp nil)) + (save-excursion + (set-buffer buffer) + (setq tmp (point)) + (while (not (progn + (goto-char (point-min)) + (re-search-forward + url-gateway-telnet-ready-regexp nil t))) + (url-accept-process-output proc)) + (delete-region tmp (point)) + (goto-char (point-min)) + (if (re-search-forward "connect:" nil t) + (progn + (condition-case () + (delete-process proc) + (error nil)) + (url-replace-regexp ".*connect:.*" "") + nil) + proc)))) + (t (error "Unknown url-gateway-method %s" url-gateway-method))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Miscellaneous functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun url-setup-privacy-info () + (interactive) + (setq url-system-type + (cond + ((or (eq url-privacy-level 'paranoid) + (and (listp url-privacy-level) + (memq 'os url-privacy-level))) + nil) + ((eq system-type 'Apple-Macintosh) "Macintosh") + ((eq system-type 'next-mach) "NeXT") + ((eq system-type 'windows-nt) "Windows-NT; 32bit") + ((eq system-type 'ms-windows) "Windows; 16bit") + ((eq system-type 'ms-dos) "MS-DOS; 32bit") + ((and (eq system-type 'vax-vms) (device-type)) + "VMS; X11") + ((eq system-type 'vax-vms) "VMS; TTY") + ((eq (device-type) 'x) "X11") + ((eq (device-type) 'ns) "NeXTStep") + ((eq (device-type) 'pm) "OS/2") + ((eq (device-type) 'win32) "Windows; 32bit") + ((eq (device-type) 'tty) "(Unix?); TTY") + (t "UnkownPlatform"))) + + ;; Set up the entity definition for PGP and PEM authentication + (setq url-pgp/pem-entity (or url-pgp/pem-entity + user-mail-address + (format "%s@%s" (user-real-login-name) + (system-name)))) + + (setq url-personal-mail-address (or url-personal-mail-address + url-pgp/pem-entity + user-mail-address)) + + (if (or (memq url-privacy-level '(paranoid high)) + (and (listp url-privacy-level) + (memq 'email url-privacy-level))) + (setq url-personal-mail-address nil)) + + (if (or (eq url-privacy-level 'paranoid) + (and (listp url-privacy-level) + (memq 'os url-privacy-level))) + (setq url-os-type nil) + (let ((vers (emacs-version))) + (if (string-match "(\\([^, )]+\\))$" vers) + (setq url-os-type (url-match vers 1)) + (setq url-os-type (symbol-name system-type)))))) + +(defun url-handle-no-scheme (url) + (let ((temp url-registered-protocols) + (found nil)) + (while (and temp (not found)) + (if (and (not (member (car (car temp)) '("auto" "www"))) + (string-match (concat "^" (car (car temp)) "\\.") + url)) + (setq found t) + (setq temp (cdr temp)))) + (cond + (found ; Found something like ftp.spry.com + (url-retrieve (concat (car (car temp)) "://" url))) + ((string-match "^www\\." url) + (url-retrieve (concat "http://" url))) + ((string-match "\\(\\.[^\\.]+\\)\\(\\.[^\\.]+\\)" url) + ;; Ok, we have at least two dots in the filename, just stick http on it + (url-retrieve (concat "http://" url))) + (t + (url-retrieve (concat "http://www." url ".com")))))) + +(defun url-setup-save-timer () + "Reset the history list timer." + (interactive) + (cond + ((featurep 'itimer) + (if (get-itimer "url-history-saver") + (delete-itimer (get-itimer "url-history-saver"))) + (start-itimer "url-history-saver" 'url-write-global-history + url-global-history-save-interval + url-global-history-save-interval)) + ((fboundp 'run-at-time) + (run-at-time url-global-history-save-interval + url-global-history-save-interval + 'url-write-global-history)) + (t nil))) + +(defvar url-download-minor-mode nil) + +(defun url-download-minor-mode (on) + (setq url-download-minor-mode (if on + (1+ (or url-download-minor-mode 0)) + (1- (or url-download-minor-mode 1)))) + (if (<= url-download-minor-mode 0) + (setq url-download-minor-mode nil))) + +(defun url-do-setup () + "Do setup - this is to avoid conflict with user settings when URL is +dumped with emacs." + (if url-setup-done + nil + + (add-minor-mode 'url-download-minor-mode " Webbing" nil) + ;; Decide what type of process-get to use + ;(url-setup-process-get) + + ;; Make OS/2 happy + (setq tcp-binary-process-input-services + (append '("http" "80") + tcp-binary-process-input-services)) + + ;; Register all the protocols we can handle + (url-register-protocol 'file) + (url-register-protocol 'ftp nil nil "21") + (url-register-protocol 'gopher nil nil "70") + (url-register-protocol 'http nil nil "80") + (url-register-protocol 'https nil nil "443") + (url-register-protocol 'nfs nil nil "2049") + (url-register-protocol 'info nil 'url-identity-expander) + (url-register-protocol 'mailserver nil 'url-identity-expander) + (url-register-protocol 'finger nil 'url-identity-expander "79") + (url-register-protocol 'mailto nil 'url-identity-expander) + (url-register-protocol 'news nil 'url-identity-expander "119") + (url-register-protocol 'nntp nil 'url-identity-expander "119") + (url-register-protocol 'irc nil 'url-identity-expander "6667") + (url-register-protocol 'rlogin) + (url-register-protocol 'shttp nil nil "80") + (url-register-protocol 'telnet) + (url-register-protocol 'tn3270) + (url-register-protocol 'wais) + (url-register-protocol 'x-exec) + (url-register-protocol 'proxy) + (url-register-protocol 'auto 'url-handle-no-scheme) + + ;; Register all the authentication schemes we can handle + (url-register-auth-scheme "basic" nil 4) + (url-register-auth-scheme "digest" nil 7) + + ;; Filename handler stuff for emacsen that support it + (url-setup-file-name-handlers) + + (setq url-cookie-file + (or url-cookie-file + (expand-file-name "~/.w3cookies"))) + + (setq url-global-history-file + (or url-global-history-file + (and (memq system-type '(ms-dos ms-windows)) + (expand-file-name "~/mosaic.hst")) + (and (memq system-type '(axp-vms vax-vms)) + (expand-file-name "~/mosaic.global-history")) + (condition-case () + (expand-file-name "~/.mosaic-global-history") + (error nil)))) + + ;; Parse the global history file if it exists, so that it can be used + ;; for URL completion, etc. + (if (and url-global-history-file + (file-exists-p url-global-history-file)) + (url-parse-global-history)) + + ;; Setup save timer + (and url-global-history-save-interval (url-setup-save-timer)) + + (if (and url-cookie-file + (file-exists-p url-cookie-file)) + (url-cookie-parse-file url-cookie-file)) + + ;; Read in proxy gateways + (let ((noproxy (and (not (assoc "no_proxy" url-proxy-services)) + (or (getenv "NO_PROXY") + (getenv "no_PROXY") + (getenv "no_proxy"))))) + (if noproxy + (setq url-proxy-services + (cons (cons "no_proxy" + (concat "\\(" + (mapconcat + (function + (lambda (x) + (cond + ((= x ?,) "\\|") + ((= x ? ) "") + ((= x ?.) (regexp-quote ".")) + ((= x ?*) ".*") + ((= x ??) ".") + (t (char-to-string x))))) + noproxy "") "\\)")) + url-proxy-services)))) + + ;; Set the url-use-transparent with decent defaults + (if (not (eq (device-type) 'tty)) + (setq url-use-transparent nil)) + (and url-use-transparent (require 'transparent)) + + ;; Set the password entry funtion based on user defaults or guess + ;; based on which remote-file-access package they are using. + (cond + (url-passwd-entry-func nil) ; Already been set + ((boundp 'read-passwd) ; Use secure password if available + (setq url-passwd-entry-func 'read-passwd)) + ((or (featurep 'efs) ; Using EFS + (featurep 'efs-auto)) ; or autoloading efs + (if (not (fboundp 'read-passwd)) + (autoload 'read-passwd "passwd" "Read in a password" nil)) + (setq url-passwd-entry-func 'read-passwd)) + ((or (featurep 'ange-ftp) ; Using ange-ftp + (and (boundp 'file-name-handler-alist) + (not (string-match "Lucid" (emacs-version))))) + (setq url-passwd-entry-func 'ange-ftp-read-passwd)) + (t + (url-warn 'security + "Can't determine how to read passwords, winging it."))) + + ;; Set up the news service if they haven't done so + (setq url-news-server + (cond + (url-news-server url-news-server) + ((and (boundp 'gnus-default-nntp-server) + (not (equal "" gnus-default-nntp-server))) + gnus-default-nntp-server) + ((and (boundp 'gnus-nntp-server) + (not (null gnus-nntp-server)) + (not (equal "" gnus-nntp-server))) + gnus-nntp-server) + ((and (boundp 'nntp-server-name) + (not (null nntp-server-name)) + (not (equal "" nntp-server-name))) + nntp-server-name) + ((getenv "NNTPSERVER") (getenv "NNTPSERVER")) + (t "news"))) + + ;; Set up the MIME accept string if they haven't got it hardcoded yet + (or url-mime-accept-string + (setq url-mime-accept-string (url-parse-viewer-types))) + (or url-mime-encoding-string + (setq url-mime-encoding-string + (mapconcat 'car + mm-content-transfer-encodings + ", "))) + + (url-setup-privacy-info) + (run-hooks 'url-load-hook) + (setq url-setup-done t))) + +(defun url-cache-file-writable-p (file) + "Follows the documentation of file-writable-p, unlike file-writable-p." + (and (file-writable-p file) + (if (file-exists-p file) + (not (file-directory-p file)) + (file-directory-p (file-name-directory file))))) + +(defun url-prepare-cache-for-file (file) + "Makes it possible to cache data in FILE. +Creates any necessary parent directories, deleting any non-directory files +that would stop this. Returns nil if parent directories can not be +created. If FILE already exists as a non-directory, it changes +permissions of FILE or deletes FILE to make it possible to write a new +version of FILE. Returns nil if this can not be done. Returns nil if +FILE already exists as a directory. Otherwise, returns t, indicating that +FILE can be created or overwritten." + + ;; COMMENT: We don't delete directories because that requires + ;; recursively deleting the directories's contents, which might + ;; eliminate a substantial portion of the cache. + + (cond + ((url-cache-file-writable-p file) + t) + ((file-directory-p file) + nil) + (t + (catch 'upcff-tag + (let ((dir (file-name-directory file)) + dir-parent dir-last-component) + (if (string-equal dir file) + ;; *** Should I have a warning here? + ;; FILE must match a pattern like /foo/bar/, indicating it is a + ;; name only suitable for a directory. So presume we won't be + ;; able to overwrite FILE and return nil. + (throw 'upcff-tag nil)) + + ;; Make sure the containing directory exists, or throw a failure + ;; if we can't create it. + (if (file-directory-p dir) + nil + (or (fboundp 'make-directory) + (throw 'upcff-tag nil)) + (make-directory dir t) + ;; make-directory silently fails if there is an obstacle, so + ;; we must verify its results. + (if (file-directory-p dir) + nil + ;; Look at prefixes of the path to find the obstacle that is + ;; stopping us from making the directory. Unfortunately, there + ;; is no portable function in Emacs to find the parent directory + ;; of a *directory*. So this code may not work on VMS. + (while (progn + (if (eq ?/ (aref dir (1- (length dir)))) + (setq dir (substring dir 0 -1)) + ;; Maybe we're on VMS where the syntax is different. + (throw 'upcff-tag nil)) + (setq dir-parent (file-name-directory dir)) + (not (file-directory-p dir-parent))) + (setq dir dir-parent)) + ;; We have found the longest path prefix that exists as a + ;; directory. Deal with any obstacles in this directory. + (if (file-exists-p dir) + (condition-case nil + (delete-file dir) + (error (throw 'upcff-tag nil)))) + (if (file-exists-p dir) + (throw 'upcff-tag nil)) + ;; Try making the directory again. + (setq dir (file-name-directory file)) + (make-directory dir t) + (or (file-directory-p dir) + (throw 'upcff-tag nil)))) + + ;; The containing directory exists. Let's see if there is + ;; something in the way in this directory. + (if (url-cache-file-writable-p file) + (throw 'upcff-tag t) + (condition-case nil + (delete-file file) + (error (throw 'upcff-tag nil)))) + + ;; The return value, if we get this far. + (url-cache-file-writable-p file)))))) + +(defun url-store-in-cache (&optional buff) + "Store buffer BUFF in the cache" + (if (or (not (get-buffer buff)) + (member url-current-type '("www" "about" "https" "shttp" + "news" "mailto")) + (and (member url-current-type '("file" "ftp" nil)) + (not url-current-server)) + ) + nil + (save-excursion + (and buff (set-buffer buff)) + (let* ((fname (url-create-cached-filename (url-view-url t))) + (fname-hdr (concat (if (memq system-type '(ms-windows ms-dos os2)) + (url-file-extension fname t) + fname) ".hdr")) + (info (mapcar (function (lambda (var) + (cons (symbol-name var) + (symbol-value var)))) + '( url-current-content-length + url-current-file + url-current-isindex + url-current-mime-encoding + url-current-mime-headers + url-current-mime-type + url-current-port + url-current-server + url-current-type + url-current-user + )))) + (cond ((and (url-prepare-cache-for-file fname) + (url-prepare-cache-for-file fname-hdr)) + (write-region (point-min) (point-max) fname nil 5) + (set-buffer (get-buffer-create " *cache-tmp*")) + (erase-buffer) + (insert "(setq ") + (mapcar + (function + (lambda (x) + (insert (car x) " " + (cond ((null (setq x (cdr x))) "nil") + ((stringp x) (prin1-to-string x)) + ((listp x) (concat "'" (prin1-to-string x))) + ((numberp x) (int-to-string x)) + (t "'???")) "\n"))) + info) + (insert ")\n") + (write-region (point-min) (point-max) fname-hdr nil 5))))))) + + +(defun url-is-cached (url) + "Return non-nil if the URL is cached." + (let* ((fname (url-create-cached-filename url)) + (attribs (file-attributes fname))) + (and fname ; got a filename + (file-exists-p fname) ; file exists + (not (eq (nth 0 attribs) t)) ; Its not a directory + (nth 5 attribs)))) ; Can get last mod-time + +(defun url-create-cached-filename-using-md5 (url) + (if url + (expand-file-name (md5 url) + (concat url-temporary-directory "/" + (user-real-login-name))))) + +(defun url-create-cached-filename (url) + "Return a filename in the local cache for URL" + (if url + (let* ((url url) + (urlobj (if (vectorp url) + url + (url-generic-parse-url url))) + (protocol (url-type urlobj)) + (hostname (url-host urlobj)) + (host-components + (cons + (user-real-login-name) + (cons (or protocol "file") + (nreverse + (delq nil + (mm-string-to-tokens + (or hostname "localhost") ?.)))))) + (fname (url-filename urlobj))) + (if (and fname (/= (length fname) 0) (= (aref fname 0) ?/)) + (setq fname (substring fname 1 nil))) + (if fname + (let ((slash nil)) + (setq fname + (mapconcat + (function + (lambda (x) + (cond + ((and (= ?/ x) slash) + (setq slash nil) + "%2F") + ((= ?/ x) + (setq slash t) + "/") + (t + (setq slash nil) + (char-to-string x))))) fname "")))) + + (if (and fname (memq system-type '(ms-windows ms-dos windows-nt)) + (string-match "\\([A-Za-z]\\):[/\\]" fname)) + (setq fname (concat (url-match fname 1) "/" + (substring fname (match-end 0))))) + + (setq fname (and fname + (mapconcat + (function (lambda (x) + (if (= x ?~) "" (char-to-string x)))) + fname "")) + fname (cond + ((null fname) nil) + ((or (string= "" fname) (string= "/" fname)) + url-directory-index-file) + ((= (string-to-char fname) ?/) + (if (string= (substring fname -1 nil) "/") + (concat fname url-directory-index-file) + (substring fname 1 nil))) + (t + (if (string= (substring fname -1 nil) "/") + (concat fname url-directory-index-file) + fname)))) + + ;; Honor hideous 8.3 filename limitations on dos and windows + ;; we don't have to worry about this in Windows NT/95 (or OS/2?) + (if (and fname (memq system-type '(ms-windows ms-dos))) + (let ((base (url-file-extension fname t)) + (ext (url-file-extension fname nil))) + (setq fname (concat (substring base 0 (min 8 (length base))) + (substring ext 0 (min 4 (length ext))))) + (setq host-components + (mapcar + (function + (lambda (x) + (if (> (length x) 8) + (concat + (substring x 0 8) "." + (substring x 8 (min (length x) 11))) + x))) + host-components)))) + + (and fname + (expand-file-name fname + (expand-file-name + (mapconcat 'identity host-components "/") + url-temporary-directory)))))) + +(defun url-extract-from-cache (fnam) + "Extract FNAM from the local disk cache" + (set-buffer (get-buffer-create url-working-buffer)) + (erase-buffer) + (setq url-current-mime-viewer nil) + (insert-file-contents-literally fnam) + (load (concat (if (memq system-type '(ms-windows ms-dos os2)) + (url-file-extension fnam t) + fnam) ".hdr") t t)) + +;;;###autoload +(defun url-get-url-at-point (&optional pt) + "Get the URL closest to point, but don't change your +position. Has a preference for looking backward when not +directly on a symbol." + ;; Not at all perfect - point must be right in the name. + (save-excursion + (if pt (goto-char pt)) + (let ((filename-chars "%.?@a-zA-Z0-9---()_/:~=&") start url) + (save-excursion + ;; first see if you're just past a filename + (if (not (eobp)) + (if (looking-at "[] \t\n[{}()]") ; whitespace or some parens + (progn + (skip-chars-backward " \n\t\r({[]})") + (if (not (bobp)) + (backward-char 1))))) + (if (string-match (concat "[" filename-chars "]") + (char-to-string (following-char))) + (progn + (skip-chars-backward filename-chars) + (setq start (point)) + (skip-chars-forward filename-chars)) + (setq start (point))) + (setq url (if (fboundp 'buffer-substring-no-properties) + (buffer-substring-no-properties start (point)) + (buffer-substring start (point))))) + (if (string-match "^URL:" url) + (setq url (substring url 4 nil))) + (if (string-match "\\.$" url) + (setq url (substring url 0 -1))) + (if (not (string-match url-nonrelative-link url)) + (setq url nil)) + url))) + +(defun url-eat-trailing-space (x) + ;; Remove spaces/tabs at the end of a string + (let ((y (1- (length x))) + (skip-chars (list ? ?\t ?\n))) + (while (and (>= y 0) (memq (aref x y) skip-chars)) + (setq y (1- y))) + (substring x 0 (1+ y)))) + +(defun url-strip-leading-spaces (x) + ;; Remove spaces at the front of a string + (let ((y (1- (length x))) + (z 0) + (skip-chars (list ? ?\t ?\n))) + (while (and (<= z y) (memq (aref x z) skip-chars)) + (setq z (1+ z))) + (substring x z nil))) + +(defun url-convert-newlines-to-spaces (x) + "Convert newlines and carriage returns embedded in a string into spaces, +and swallow following whitespace. +The argument is not side-effected, but may be returned by this function." + (if (string-match "[\n\r]+\\s-*" x) ; [\\n\\r\\t ] + (concat (substring x 0 (match-beginning 0)) " " + (url-convert-newlines-to-spaces + (substring x (match-end 0)))) + x)) + +;; Test cases +;; (url-convert-newlines-to-spaces "foo bar") ; nothing happens +;; (url-convert-newlines-to-spaces "foo\n \t bar") ; whitespace converted +;; +;; This implementation doesn't mangle the match-data, is fast, and doesn't +;; create garbage, but it leaves whitespace. +;; (defun url-convert-newlines-to-spaces (x) +;; "Convert newlines and carriage returns embedded in a string into spaces. +;; The string is side-effected, then returned." +;; (let ((i 0) +;; (limit (length x))) +;; (while (< i limit) +;; (if (or (= ?\n (aref x i)) +;; (= ?\r (aref x i))) +;; (aset x i ? )) +;; (setq i (1+ i))) +;; x)) + +(defun url-expand-file-name (url &optional default) + "Convert URL to a fully specified URL, and canonicalize it. +Second arg DEFAULT is a URL to start with if URL is relative. +If DEFAULT is nil or missing, the current buffer's URL is used. +Path components that are `.' are removed, and +path components followed by `..' are removed, along with the `..' itself." + (if url + (setq url (mapconcat (function (lambda (x) + (if (= x ?\n) "" (char-to-string x)))) + (url-strip-leading-spaces + (url-eat-trailing-space url)) ""))) + (cond + ((null url) nil) ; Something hosed! Be graceful + ((string-match "^#" url) ; Offset link, use it raw + url) + (t + (let* ((urlobj (url-generic-parse-url url)) + (inhibit-file-name-handlers t) + (defobj (cond + ((vectorp default) default) + (default (url-generic-parse-url default)) + ((and (null default) url-current-object) + url-current-object) + (t (url-generic-parse-url (url-view-url t))))) + (expander (cdr-safe + (cdr-safe + (assoc (or (url-type urlobj) + (url-type defobj)) + url-registered-protocols))))) + (if (string-match "^//" url) + (setq urlobj (url-generic-parse-url (concat (url-type defobj) ":" + url)))) + (if (fboundp expander) + (funcall expander urlobj defobj) + (message "Unknown URL scheme: %s" (or (url-type urlobj) + (url-type defobj))) + (url-identity-expander urlobj defobj)) + (url-recreate-url urlobj))))) + +(defun url-default-expander (urlobj defobj) + ;; The default expansion routine - urlobj is modified by side effect! + (url-set-type urlobj (or (url-type urlobj) (url-type defobj))) + (url-set-port urlobj (or (url-port urlobj) + (and (string= (url-type urlobj) + (url-type defobj)) + (url-port defobj)))) + (if (not (string= "file" (url-type urlobj))) + (url-set-host urlobj (or (url-host urlobj) (url-host defobj)))) + (if (string= "ftp" (url-type urlobj)) + (url-set-user urlobj (or (url-user urlobj) (url-user defobj)))) + (if (string= (url-filename urlobj) "") + (url-set-filename urlobj "/")) + (if (string-match "^/" (url-filename urlobj)) + nil + (url-set-filename urlobj + (url-remove-relative-links + (concat (url-basepath (url-filename defobj)) + (url-filename urlobj)))))) + +(defun url-identity-expander (urlobj defobj) + (url-set-type urlobj (or (url-type urlobj) (url-type defobj)))) + +(defun url-hexify-string (str) + "Escape characters in a string" + (if (and (featurep 'mule) str) + (setq str (code-convert-string + str *internal* url-mule-retrieval-coding-system))) + (setq str (mapconcat + (function + (lambda (char) + (if (or (> char ?z) + (< char ?-) + (and (< char ?a) + (> char ?Z)) + (and (< char ?@) + (> char ?:))) + (if (< char 16) + (upcase (format "%%0%x" char)) + (upcase (format "%%%x" char))) + (char-to-string char)))) str ""))) + +(defun url-make-sequence (start end) + "Make a sequence (list) of numbers from START to END" + (cond + ((= start end) '()) + ((> start end) '()) + (t + (let ((sqnc '())) + (while (<= start end) + (setq sqnc (cons end sqnc) + end (1- end))) + sqnc)))) + +(defun url-file-extension (fname &optional x) + "Return the filename extension of FNAME. If optional variable X is t, +then return the basename of the file with the extension stripped off." + (if (and fname (string-match "\\.[^./]+$" fname)) + (if x (substring fname 0 (match-beginning 0)) + (substring fname (match-beginning 0) nil)) + ;; + ;; If fname has no extension, and x then return fname itself instead of + ;; nothing. When caching it allows the correct .hdr file to be produced + ;; for filenames without extension. + ;; + (if x + fname + ""))) + +(defun url-basepath (file &optional x) + "Return the base pathname of FILE, or the actual filename if X is true" + (cond + ((null file) "") + (x (file-name-nondirectory file)) + (t (file-name-directory file)))) + +(defun url-unhex (x) + (if (> x ?9) + (if (>= x ?a) + (+ 10 (- x ?a)) + (+ 10 (- x ?A))) + (- x ?0))) + +(defun url-unhex-string (str) + "Remove %XXX embedded spaces, etc in a url" + (setq str (or str "")) + (let ((tmp "")) + (while (string-match "%[0-9a-f][0-9a-f]" str) + (let* ((start (match-beginning 0)) + (ch1 (url-unhex (elt str (+ start 1)))) + (code (+ (* 16 ch1) + (url-unhex (elt str (+ start 2)))))) + (setq tmp + (concat + tmp (substring str 0 start) + (if (or (= code ?\n) (= code ?\r)) " " (char-to-string code))) + str (substring str (match-end 0))))) + (setq tmp (concat tmp str)) + tmp)) + +(defun url-clean-text () + "Clean up a buffer, removing any excess garbage from a gateway mechanism, +and decoding any MIME content-transfer-encoding used." + (set-buffer url-working-buffer) + (goto-char (point-min)) + (url-replace-regexp "Connection closed by.*" "") + (goto-char (point-min)) + (url-replace-regexp "Process WWW.*" "")) + +(defun url-uncompress () + "Do any necessary uncompression on `url-working-buffer'" + (set-buffer url-working-buffer) + (if (not url-inhibit-uncompression) + (let* ((extn (url-file-extension url-current-file)) + (decoder nil) + (code-1 (cdr-safe + (assoc "content-transfer-encoding" + url-current-mime-headers))) + (code-2 (cdr-safe + (assoc "content-encoding" url-current-mime-headers))) + (code-3 (and (not code-1) (not code-2) + (cdr-safe (assoc extn url-uncompressor-alist)))) + (done nil) + (default-process-coding-system + (if (featurep 'mule) (cons *noconv* *noconv*)))) + (mapcar + (function + (lambda (code) + (setq decoder (and (not (member code done)) + (cdr-safe + (assoc code mm-content-transfer-encodings))) + done (cons code done)) + (cond + ((null decoder) nil) + ((stringp decoder) + (message "Decoding...") + (call-process-region (point-min) (point-max) decoder t t nil) + (message "Decoding... done.")) + ((listp decoder) + (apply 'call-process-region (point-min) (point-max) + (car decoder) t t nil (cdr decoder))) + ((and (symbolp decoder) (fboundp decoder)) + (message "Decoding...") + (funcall decoder (point-min) (point-max)) + (message "Decoding... done.")) + (t + (error "Bad entry for %s in `mm-content-transfer-encodings'" + code))))) + (list code-1 code-2 code-3)))) + (set-buffer-modified-p nil)) + +(defun url-filter (proc string) + (save-excursion + (set-buffer url-working-buffer) + (insert string) + (if (string-match "\nConnection closed by" string) + (progn (set-process-filter proc nil) + (url-sentinel proc string)))) + string) + +(defun url-default-callback (buf) + (url-download-minor-mode nil) + (cond + ((save-excursion (set-buffer buf) + (and url-current-callback-func + (fboundp url-current-callback-func))) + (save-excursion + (save-window-excursion + (set-buffer buf) + (cond + ((listp url-current-callback-data) + (apply url-current-callback-func + url-current-callback-data)) + (url-current-callback-data + (funcall url-current-callback-func + url-current-callback-data)) + (t + (funcall url-current-callback-func)))))) + ((fboundp 'w3-sentinel) + (set-variable 'w3-working-buffer buf) + (w3-sentinel)) + (t + (message "Retrieval for %s complete." buf)))) + +(defun url-sentinel (proc string) + (if (buffer-name (process-buffer proc)) + (save-excursion + (set-buffer (get-buffer (process-buffer proc))) + (remove-hook 'after-change-functions 'url-after-change-function) + (let ((status nil) + (url-working-buffer (current-buffer))) + (if url-be-asynchronous + (progn + (widen) + (url-clean-text) + (cond + ((and (null proc) (not (get-buffer url-working-buffer))) nil) + ((url-mime-response-p) + (setq status (url-parse-mime-headers)))) + (if (not url-current-mime-type) + (setq url-current-mime-type (mm-extension-to-mime + (url-file-extension + url-current-file)))))) + (if (member status '(401 301 302 303 204)) + nil + (funcall url-default-retrieval-proc (buffer-name))))) + (url-warn 'url (format "Process %s completed with no buffer!" proc)))) + +(defun url-remove-relative-links (name) + ;; Strip . and .. from pathnames + (let ((new (if (not (string-match "^/" name)) + (concat "/" name) + name))) + (while (string-match "/\\([^/]*/\\.\\./\\)" new) + (setq new (concat (substring new 0 (match-beginning 1)) + (substring new (match-end 1))))) + (while (string-match "/\\(\\./\\)" new) + (setq new (concat (substring new 0 (match-beginning 1)) + (substring new (match-end 1))))) + (while (string-match "^/\\.\\.\\(/\\)" new) + (setq new (substring new (match-beginning 1) nil))) + new)) + +(defun url-truncate-url-for-viewing (url &optional width) + "Return a shortened version of URL that is WIDTH characters or less wide. +WIDTH defaults to the current frame width." + (let* ((fr-width (or width (frame-width))) + (str-width (length url)) + (tail (file-name-nondirectory url)) + (fname nil) + (modified 0) + (urlobj nil)) + ;; The first thing that can go are the search strings + (if (and (>= str-width fr-width) + (string-match "?" url)) + (setq url (concat (substring url 0 (match-beginning 0)) "?...") + str-width (length url) + tail (file-name-nondirectory url))) + (if (< str-width fr-width) + nil ; Hey, we are done! + (setq urlobj (url-generic-parse-url url) + fname (url-filename urlobj) + fr-width (- fr-width 4)) + (while (and (>= str-width fr-width) + (string-match "/" fname)) + (setq fname (substring fname (match-end 0) nil) + modified (1+ modified)) + (url-set-filename urlobj fname) + (setq url (url-recreate-url urlobj) + str-width (length url))) + (if (> modified 1) + (setq fname (concat "/.../" fname)) + (setq fname (concat "/" fname))) + (url-set-filename urlobj fname) + (setq url (url-recreate-url urlobj))) + url)) + +(defun url-view-url (&optional no-show) + "View the current document's URL. Optional argument NO-SHOW means +just return the URL, don't show it in the minibuffer." + (interactive) + (let ((url "")) + (cond + ((equal url-current-type "gopher") + (setq url (format "%s://%s%s/%s" + url-current-type url-current-server + (if (or (null url-current-port) + (string= "70" url-current-port)) "" + (concat ":" url-current-port)) + url-current-file))) + ((equal url-current-type "news") + (setq url (concat "news:" + (if (not (equal url-current-server + url-news-server)) + (concat "//" url-current-server + (if (or (null url-current-port) + (string= "119" url-current-port)) + "" + (concat ":" url-current-port)) "/")) + url-current-file))) + ((equal url-current-type "about") + (setq url (concat "about:" url-current-file))) + ((member url-current-type '("http" "shttp" "https")) + (setq url (format "%s://%s%s/%s" url-current-type url-current-server + (if (or (null url-current-port) + (string= "80" url-current-port)) + "" + (concat ":" url-current-port)) + (if (and url-current-file + (= ?/ (string-to-char url-current-file))) + (substring url-current-file 1 nil) + url-current-file)))) + ((equal url-current-type "ftp") + (setq url (format "%s://%s%s/%s" url-current-type + (if (and url-current-user + (not (string= "anonymous" url-current-user))) + (concat url-current-user "@") "") + url-current-server + (if (and url-current-file + (= ?/ (string-to-char url-current-file))) + (substring url-current-file 1 nil) + url-current-file)))) + ((and (member url-current-type '("file" nil)) url-current-file) + (setq url (format "file:%s" url-current-file))) + ((equal url-current-type "www") + (setq url (format "www:/%s/%s" url-current-server url-current-file))) + (t + (setq url nil))) + (if (not no-show) (message "%s" url) url))) + +(defun url-parse-Netscape-history (fname) + ;; Parse a Netscape/X style global history list. + (let (pos ; Position holder + url ; The URL + time) ; Last time accessed + (goto-char (point-min)) + (skip-chars-forward "^\n") + (skip-chars-forward "\n \t") ; Skip past the tag line + (setq url-global-history-hash-table (url-make-hashtable 131)) + ;; Here we will go to the end of the line and + ;; skip back over a token, since we might run + ;; into spaces in URLs, depending on how much + ;; smarter netscape is than the old XMosaic :) + (while (not (eobp)) + (setq pos (point)) + (end-of-line) + (skip-chars-backward "^ \t") + (skip-chars-backward " \t") + (setq url (buffer-substring pos (point)) + pos (1+ (point))) + (skip-chars-forward "^\n") + (setq time (buffer-substring pos (point))) + (skip-chars-forward "\n") + (setq url-history-changed-since-last-save t) + (url-puthash url time url-global-history-hash-table)))) + +(defun url-parse-Mosaic-history-v1 (fname) + ;; Parse an NCSA Mosaic/X style global history list + (goto-char (point-min)) + (skip-chars-forward "^\n") + (skip-chars-forward "\n \t") ; Skip past the tag line + (skip-chars-forward "^\n") + (skip-chars-forward "\n \t") ; Skip past the second tag line + (setq url-global-history-hash-table (url-make-hashtable 131)) + (let (pos ; Temporary position holder + bol ; Beginning-of-line + url ; URL + time ; Time + last-end ; Last ending point + ) + (while (not (eobp)) + (setq bol (point)) + (end-of-line) + (setq pos (point) + last-end (point)) + (skip-chars-backward "^ \t" bol) ; Skip over year + (skip-chars-backward " \t" bol) + (skip-chars-backward "^ \t" bol) ; Skip over time + (skip-chars-backward " \t" bol) + (skip-chars-backward "^ \t" bol) ; Skip over day # + (skip-chars-backward " \t" bol) + (skip-chars-backward "^ \t" bol) ; Skip over month + (skip-chars-backward " \t" bol) + (skip-chars-backward "^ \t" bol) ; Skip over day abbrev. + (if (bolp) + nil ; Malformed entry!!! Ack! Bailout! + (setq time (buffer-substring pos (point))) + (skip-chars-backward " \t") + (setq pos (point))) + (beginning-of-line) + (setq url (buffer-substring (point) pos)) + (goto-char (min (1+ last-end) (point-max))) ; Goto next line + (if (/= (length url) 0) + (progn + (setq url-history-changed-since-last-save t) + (url-puthash url time url-global-history-hash-table)))))) + +(defun url-parse-Mosaic-history-v2 (fname) + ;; Parse an NCSA Mosaic/X style global history list (version 2) + (goto-char (point-min)) + (skip-chars-forward "^\n") + (skip-chars-forward "\n \t") ; Skip past the tag line + (skip-chars-forward "^\n") + (skip-chars-forward "\n \t") ; Skip past the second tag line + (setq url-global-history-hash-table (url-make-hashtable 131)) + (let (pos ; Temporary position holder + bol ; Beginning-of-line + url ; URL + time ; Time + last-end ; Last ending point + ) + (while (not (eobp)) + (setq bol (point)) + (end-of-line) + (setq pos (point) + last-end (point)) + (skip-chars-backward "^ \t" bol) ; Skip over time + (if (bolp) + nil ; Malformed entry!!! Ack! Bailout! + (setq time (buffer-substring pos (point))) + (skip-chars-backward " \t") + (setq pos (point))) + (beginning-of-line) + (setq url (buffer-substring (point) pos)) + (goto-char (min (1+ last-end) (point-max))) ; Goto next line + (if (/= (length url) 0) + (progn + (setq url-history-changed-since-last-save t) + (url-puthash url time url-global-history-hash-table)))))) + +(defun url-parse-Emacs-history (&optional fname) + ;; Parse out the Emacs-w3 global history file for completion, etc. + (or fname (setq fname (expand-file-name url-global-history-file))) + (cond + ((not (file-exists-p fname)) + (message "%s does not exist." fname)) + ((not (file-readable-p fname)) + (message "%s is unreadable." fname)) + (t + (condition-case () + (load fname nil t) + (error (message "Could not load %s" fname))) + (if (boundp 'url-global-history-completion-list) + ;; Hey! Automatic conversion of old format! + (progn + (setq url-global-history-hash-table (url-make-hashtable 131) + url-history-changed-since-last-save t) + (mapcar (function + (lambda (x) + (url-puthash (car x) (cdr x) + url-global-history-hash-table))) + (symbol-value 'url-global-history-completion-list))))))) + +(defun url-parse-global-history (&optional fname) + ;; Parse out the mosaic global history file for completions, etc. + (or fname (setq fname (expand-file-name url-global-history-file))) + (cond + ((not (file-exists-p fname)) + (message "%s does not exist." fname)) + ((not (file-readable-p fname)) + (message "%s is unreadable." fname)) + (t + (save-excursion + (set-buffer (get-buffer-create " *url-tmp*")) + (erase-buffer) + (insert-file-contents-literally fname) + (goto-char (point-min)) + (cond + ((looking-at "(setq") (url-parse-Emacs-history fname)) + ((looking-at "ncsa-mosaic-.*-1$") (url-parse-Mosaic-history-v1 fname)) + ((looking-at "ncsa-mosaic-.*-2$") (url-parse-Mosaic-history-v2 fname)) + ((or (looking-at "MCOM-") (looking-at "netscape")) + (url-parse-Netscape-history fname)) + (t + (url-warn 'url (format "Cannot deduce type of history file: %s" + fname)))))))) + +(defun url-write-Emacs-history (fname) + ;; Write an Emacs-w3 style global history list into FNAME + (erase-buffer) + (let ((count 0)) + (url-maphash (function + (lambda (key value) + (setq count (1+ count)) + (insert "(url-puthash " + (if (stringp key) + (prin1-to-string key) + (concat "\"" (symbol-name key) "\"")) + (if (not (stringp value)) " '" "") + (prin1-to-string value) + " url-global-history-hash-table)\n"))) + url-global-history-hash-table) + (goto-char (point-min)) + (insert (format + "(setq url-global-history-hash-table (url-make-hashtable %d))\n" + (/ count 4))) + (goto-char (point-max)) + (insert "\n") + (write-file fname))) + +(defun url-write-Netscape-history (fname) + ;; Write a Netscape-style global history list into FNAME + (erase-buffer) + (let ((last-valid-time "785305714")) ; Picked out of thin air, + ; in case first in assoc list + ; doesn't have a valid time + (goto-char (point-min)) + (insert "MCOM-Global-history-file-1\n") + (url-maphash (function + (lambda (url time) + (if (or (not (stringp time)) (string-match " \t" time)) + (setq time last-valid-time) + (setq last-valid-time time)) + (insert (concat (if (stringp url) + url + (symbol-name url)) + " " time "\n")))) + url-global-history-hash-table) + (write-file fname))) + +(defun url-write-Mosaic-history-v1 (fname) + ;; Write a Mosaic/X-style global history list into FNAME + (erase-buffer) + (goto-char (point-min)) + (insert "ncsa-mosaic-history-format-1\nGlobal\n") + (url-maphash (function + (lambda (url time) + (if (listp time) + (setq time (current-time-string time))) + (if (or (not (stringp time)) + (not (string-match " " time))) + (setq time (current-time-string))) + (insert (concat (if (stringp url) + url + (symbol-name url)) + " " time "\n")))) + url-global-history-hash-table) + (write-file fname)) + +(defun url-write-Mosaic-history-v2 (fname) + ;; Write a Mosaic/X-style global history list into FNAME + (let ((last-valid-time "827250806")) + (erase-buffer) + (goto-char (point-min)) + (insert "ncsa-mosaic-history-format-2\nGlobal\n") + (url-maphash (function + (lambda (url time) + (if (listp time) + (setq time last-valid-time) + (setq last-valid-time time)) + (if (not (stringp time)) + (setq time last-valid-time)) + (insert (concat (if (stringp url) + url + (symbol-name url)) + " " time "\n")))) + url-global-history-hash-table) + (write-file fname))) + +(defun url-write-global-history (&optional fname) + "Write the global history file into `url-global-history-file'. +The type of data written is determined by what is in the file to begin +with. If the type of storage cannot be determined, then prompt the +user for what type to save as." + (interactive) + (or fname (setq fname (expand-file-name url-global-history-file))) + (cond + ((not url-history-changed-since-last-save) nil) + ((not (file-writable-p fname)) + (message "%s is unwritable." fname)) + (t + (let ((make-backup-files nil) + (version-control nil) + (require-final-newline t)) + (save-excursion + (set-buffer (get-buffer-create " *url-tmp*")) + (erase-buffer) + (condition-case () + (insert-file-contents-literally fname) + (error nil)) + (goto-char (point-min)) + (cond + ((looking-at "ncsa-mosaic-.*-1$") (url-write-Mosaic-history-v1 fname)) + ((looking-at "ncsa-mosaic-.*-2$") (url-write-Mosaic-history-v2 fname)) + ((looking-at "MCOM-") (url-write-Netscape-history fname)) + ((looking-at "netscape") (url-write-Netscape-history fname)) + ((looking-at "(setq") (url-write-Emacs-history fname)) + (t (url-write-Emacs-history fname))) + (kill-buffer (current-buffer)))))) + (setq url-history-changed-since-last-save nil)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; The main URL fetching interface +;;; ------------------------------- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;###autoload +(defun url-popup-info (url) + "Retrieve the HTTP/1.0 headers and display them in a temp buffer." + (let* ((urlobj (url-generic-parse-url url)) + (type (url-type urlobj)) + data) + (cond + ((string= type "http") + (let ((url-request-method "HEAD") + (url-automatic-caching nil) + (url-inhibit-mime-parsing t) + (url-working-buffer " *popup*")) + (save-excursion + (set-buffer (get-buffer-create url-working-buffer)) + (erase-buffer) + (setq url-be-asynchronous nil) + (url-retrieve url) + (subst-char-in-region (point-min) (point-max) ?\r ? ) + (buffer-string)))) + ((or (string= type "file") (string= type "ftp")) + (setq data (url-file-attributes url)) + (set-buffer (get-buffer-create + (url-generate-new-buffer-name "*Header Info*"))) + (erase-buffer) + (if data + (concat (if (stringp (nth 0 data)) + (concat " Linked to: " (nth 0 data)) + (concat " Directory: " (if (nth 0 data) "Yes" "No"))) + "\n Links: " (int-to-string (nth 1 data)) + "\n File UID: " (int-to-string (nth 2 data)) + "\n File GID: " (int-to-string (nth 3 data)) + "\n Last Access: " (current-time-string (nth 4 data)) + "\nLast Modified: " (current-time-string (nth 5 data)) + "\n Last Changed: " (current-time-string (nth 6 data)) + "\n Size (bytes): " (int-to-string (nth 7 data)) + "\n File Type: " (or (nth 8 data) "text/plain")) + (concat "No info found for " url))) + ((and (string= type "news") (string-match "@" url)) + (let ((art (url-filename urlobj))) + (if (not (string= (substring art -1 nil) ">")) + (setq art (concat "<" art ">"))) + (url-get-headers-from-article-id art))) + (t (concat "Don't know how to find information on " url))))) + +(defun url-decode-text () + ;; Decode text transmitted by NNTP. + ;; 0. Delete status line. + ;; 1. Delete `^M' at end of line. + ;; 2. Delete `.' at end of buffer (end of text mark). + ;; 3. Delete `.' at beginning of line." + (save-excursion + (set-buffer nntp-server-buffer) + ;; Insert newline at end of buffer. + (goto-char (point-max)) + (if (not (bolp)) + (insert "\n")) + ;; Delete status line. + (goto-char (point-min)) + (delete-region (point) (progn (forward-line 1) (point))) + ;; Delete `^M' at end of line. + ;; (replace-regexp "\r$" "") + (while (not (eobp)) + (end-of-line) + (if (= (preceding-char) ?\r) + (delete-char -1)) + (forward-line 1) + ) + ;; Delete `.' at end of buffer (end of text mark). + (goto-char (point-max)) + (forward-line -1) ;(beginning-of-line) + (if (looking-at "^\\.$") + (delete-region (point) (progn (forward-line 1) (point)))) + ;; Replace `..' at beginning of line with `.'. + (goto-char (point-min)) + ;; (replace-regexp "^\\.\\." ".") + (while (search-forward "\n.." nil t) + (delete-char -1)) + )) + +(defun url-get-headers-from-article-id (art) + ;; Return the HEAD of ART (a usenet news article) + (cond + ((string-match "flee" nntp-version) + (nntp/command "HEAD" art) + (save-excursion + (set-buffer nntp-server-buffer) + (while (progn (goto-char (point-min)) + (not (re-search-forward "^.\r*$" nil t))) + (url-accept-process-output nntp/connection)))) + (t + (nntp-send-command "^\\.\r$" "HEAD" art) + (url-decode-text))) + (save-excursion + (set-buffer nntp-server-buffer) + (buffer-string))) + +(defvar url-external-retrieval-program "www" + "*Name of the external executable to run to retrieve URLs.") + +(defvar url-external-retrieval-args '("-source") + "*A list of arguments to pass to `url-external-retrieval-program' to +retrieve a URL by its HTML source.") + +(defun url-retrieve-externally (url &optional no-cache) + (if (get-buffer url-working-buffer) + (save-excursion + (set-buffer url-working-buffer) + (set-buffer-modified-p nil) + (kill-buffer url-working-buffer))) + (set-buffer (get-buffer-create url-working-buffer)) + (let* ((args (append url-external-retrieval-args (list url))) + (urlobj (url-generic-parse-url url)) + (type (url-type urlobj))) + (if (or (member type '("www" "about" "mailto" "mailserver")) + (url-file-directly-accessible-p urlobj)) + (url-retrieve-internally url) + (url-lazy-message "Retrieving %s..." url) + (apply 'call-process url-external-retrieval-program + nil t nil args) + (url-lazy-message "Retrieving %s... done" url) + (if (and type urlobj) + (setq url-current-server (url-host urlobj) + url-current-type (url-type urlobj) + url-current-port (url-port urlobj) + url-current-file (url-filename urlobj))) + (if (member url-current-file '("/" "")) + (setq url-current-mime-type "text/html"))))) + +(defun url-get-normalized-date (&optional specified-time) + ;; Return a 'real' date string that most HTTP servers can understand. + (require 'timezone) + (let* ((raw (if specified-time (current-time-string specified-time) + (current-time-string))) + (gmt (timezone-make-date-arpa-standard raw + (nth 1 (current-time-zone)) + "GMT")) + (parsed (timezone-parse-date gmt)) + (day (cdr-safe (assoc (substring raw 0 3) weekday-alist))) + (year nil) + (month (car + (rassoc + (string-to-int (aref parsed 1)) monthabbrev-alist))) + ) + (setq day (or (car-safe (rassoc day weekday-alist)) + (substring raw 0 3)) + year (aref parsed 0)) + ;; This is needed for plexus servers, or the server will hang trying to + ;; parse the if-modified-since header. Hopefully, I can take this out + ;; soon. + (if (and year (> (length year) 2)) + (setq year (substring year -2 nil))) + + (concat day ", " (aref parsed 2) "-" month "-" year " " + (aref parsed 3) " " (or (aref parsed 4) + (concat "[" (nth 1 (current-time-zone)) + "]"))))) + +;;;###autoload +(defun url-cache-expired (url mod) + "Return t iff a cached file has expired." + (if (not (string-match url-nonrelative-link url)) + t + (let* ((urlobj (url-generic-parse-url url)) + (type (url-type urlobj))) + (cond + (url-standalone-mode + (not (file-exists-p (url-create-cached-filename urlobj)))) + ((string= type "http") + (if (not url-standalone-mode) t + (not (file-exists-p (url-create-cached-filename urlobj))))) + ((not (fboundp 'current-time)) + t) + ((member type '("file" "ftp")) + (if (or (equal mod '(0 0)) (not mod)) + (return t) + (or (> (nth 0 mod) (nth 0 (current-time))) + (> (nth 1 mod) (nth 1 (current-time)))))) + (t nil))))) + +(defun url-retrieve-internally (url &optional no-cache) + (if (get-buffer url-working-buffer) + (save-excursion + (set-buffer url-working-buffer) + (erase-buffer) + (setq url-current-can-be-cached (not no-cache)) + (set-buffer-modified-p nil))) + (let* ((urlobj (url-generic-parse-url url)) + (type (url-type urlobj)) + (url-using-proxy (and + (if (assoc "no_proxy" url-proxy-services) + (not (string-match + (cdr + (assoc "no_proxy" url-proxy-services)) + url)) + t) + (not + (and + (string-match "file:" url) + (not (string-match "file://" url)))) + (cdr (assoc type url-proxy-services)))) + (handler nil) + (original-url url) + (cached nil) + (tmp url-current-file)) + (if url-using-proxy (setq type "proxy")) + (setq cached (url-is-cached url) + cached (and cached (not (url-cache-expired url cached))) + handler (if cached 'url-extract-from-cache + (car-safe + (cdr-safe (assoc (or type "auto") + url-registered-protocols)))) + url (if cached (url-create-cached-filename url) url)) + (save-excursion + (set-buffer (get-buffer-create url-working-buffer)) + (setq url-current-can-be-cached (not no-cache))) + (if url-be-asynchronous + (url-download-minor-mode t)) + (if (and handler (fboundp handler)) + (funcall handler url) + (set-buffer (get-buffer-create url-working-buffer)) + (setq url-current-file tmp) + (erase-buffer) + (insert "<title> Link Error! </title>\n" + "<h1> An error has occurred... </h1>\n" + (format "The link type `<code>%s</code>'" type) + " is unrecognized or unsupported at this time.<p>\n" + "If you feel this is an error, please " + "<a href=\"mailto://" url-bug-address "\">send me mail.</a>" + "<p><address>William Perry</address><br>" + "<address>" url-bug-address "</address>") + (setq url-current-file "error.html")) + (if (and + (not url-be-asynchronous) + (get-buffer url-working-buffer)) + (progn + (set-buffer url-working-buffer) + (if (not url-current-object) + (setq url-current-object urlobj)) + (url-clean-text))) + (cond + ((equal type "wais") nil) + ((and url-be-asynchronous (not cached) (member type '("http" "proxy"))) + nil) + (url-be-asynchronous + (funcall url-default-retrieval-proc (buffer-name))) + ((not (get-buffer url-working-buffer)) nil) + ((and (not url-inhibit-mime-parsing) + (or cached (url-mime-response-p t))) + (or cached (url-parse-mime-headers nil t)))) + (if (and (or (not url-be-asynchronous) + (not (equal type "http"))) + (not url-current-mime-type)) + (if (url-buffer-is-hypertext) + (setq url-current-mime-type "text/html") + (setq url-current-mime-type (mm-extension-to-mime + (url-file-extension + url-current-file))))) + (if (and url-automatic-caching url-current-can-be-cached + (not url-be-asynchronous)) + (save-excursion + (url-store-in-cache url-working-buffer))) + (if (not (url-hashtablep url-global-history-hash-table)) + (setq url-global-history-hash-table (url-make-hashtable 131))) + (if (not (string-match "^about:" original-url)) + (progn + (setq url-history-changed-since-last-save t) + (url-puthash original-url (current-time) + url-global-history-hash-table))) + cached)) + +;;;###autoload +(defun url-retrieve (url &optional no-cache expected-md5) + "Retrieve a document over the World Wide Web. +The document should be specified by its fully specified +Uniform Resource Locator. No parsing is done, just return the +document as the server sent it. The document is left in the +buffer specified by url-working-buffer. url-working-buffer is killed +immediately before starting the transfer, so that no buffer-local +variables interfere with the retrieval. HTTP/1.0 redirection will +be honored before this function exits." + (url-do-setup) + (if (and (fboundp 'set-text-properties) + (subrp (symbol-function 'set-text-properties))) + (set-text-properties 0 (length url) nil url)) + (if (and url (string-match "^url:" url)) + (setq url (substring url (match-end 0) nil))) + (let ((status (url-retrieve-internally url no-cache))) + (if (and expected-md5 url-check-md5s) + (let ((cur-md5 (md5 (current-buffer)))) + (if (not (string= cur-md5 expected-md5)) + (and (not (funcall url-confirmation-func + "MD5s do not match, use anyway? ")) + (error "MD5 error."))))) + status)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; How to register a protocol +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun url-register-protocol (protocol &optional retrieve expander defport) + "Register a protocol with the URL retrieval package. +PROTOCOL is the type of protocol being registers (http, nntp, etc), + and is the first chunk of the URL. ie: http:// URLs will be + handled by the protocol registered as 'http'. PROTOCOL can + be either a symbol or a string - it is converted to a string, + and lowercased before being registered. +RETRIEVE (optional) is the function to be called with a url as its + only argument. If this argument is omitted, then this looks + for a function called 'url-PROTOCOL'. A warning is shown if + the function is undefined, but the protocol is still + registered. +EXPANDER (optional) is the function to call to expand a relative link + of type PROTOCOL. If omitted, this defaults to + `url-default-expander' + +Any proxy information is read in from environment variables at this +time, so this function should only be called after dumping emacs." + (let* ((protocol (cond + ((stringp protocol) (downcase protocol)) + ((symbolp protocol) (downcase (symbol-name protocol))) + (t nil))) + + (retrieve (or retrieve (intern (concat "url-" protocol)))) + (expander (or expander 'url-default-expander)) + (cur-protocol (assoc protocol url-registered-protocols)) + (urlobj nil) + (cur-proxy (assoc protocol url-proxy-services)) + (env-proxy (or (getenv (concat protocol "_proxy")) + (getenv (concat protocol "_PROXY")) + (getenv (upcase (concat protocol "_PROXY")))))) + + (if (not protocol) + (error "Invalid data to url-register-protocol.")) + + (if (not (fboundp retrieve)) + (message "Warning: %s registered, but no function found." protocol)) + + ;; Store the default port, if none previously specified and + ;; defport given + (if (and defport (not (assoc protocol url-default-ports))) + (setq url-default-ports (cons (cons protocol defport) + url-default-ports))) + + ;; Store the appropriate information for later + (if cur-protocol + (setcdr cur-protocol (cons retrieve expander)) + (setq url-registered-protocols (cons (cons protocol + (cons retrieve expander)) + url-registered-protocols))) + + ;; Store any proxying information - this will not overwrite an old + ;; entry, so that people can still set this information in their + ;; .emacs file + (cond + (cur-proxy nil) ; Keep their old settings + ((null env-proxy) nil) ; No proxy setup + ;; First check if its something like hostname:port + ((string-match "^\\([^:]+\\):\\([0-9]+\\)$" env-proxy) + (setq urlobj (url-generic-parse-url nil)) ; Get a blank object + (url-set-type urlobj "http") + (url-set-host urlobj (url-match env-proxy 1)) + (url-set-port urlobj (url-match env-proxy 2))) + ;; Then check if its a fully specified URL + ((string-match url-nonrelative-link env-proxy) + (setq urlobj (url-generic-parse-url env-proxy)) + (url-set-type urlobj "http") + (url-set-target urlobj nil)) + ;; Finally, fall back on the assumption that its just a hostname + (t + (setq urlobj (url-generic-parse-url nil)) ; Get a blank object + (url-set-type urlobj "http") + (url-set-host urlobj env-proxy))) + + (if (and (not cur-proxy) urlobj) + (progn + (setq url-proxy-services + (cons (cons protocol (url-recreate-url urlobj)) + url-proxy-services)) + (message "Using a proxy for %s..." protocol))))) + +(provide 'url)