;;; NNTP (RFC977) Interface for GNU Emacs ;; Copyright (C) 1987, 1988 Fujitsu Laboratoris LTD. ;; Copyrigth (C) 1987, 1988 Masanobu UMEDA (umerin@flab.flab.Fujitsu.JUNET) ;; $Header: nntp.el,v 2.0 88/02/02 10:01:52 umerin Locked $ ;; This file is part of GNU Emacs. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY. No author or distributor ;; accepts responsibility to anyone for the consequences of using it ;; or for whether it serves any particular purpose or works at all, ;; unless he says so in writing. Refer to the GNU Emacs General Public ;; License for full details. ;; Everyone is granted permission to copy, modify and redistribute ;; GNU Emacs, but only under the conditions described in the ;; GNU Emacs General Public License. A copy of this license is ;; supposed to have been given to you along with GNU Emacs so you ;; can know your rights and responsibilities. It should be in a ;; file named COPYING. Among other things, the copyright notice ;; and this notice must be preserved on all copies. ;; This implementation depends on 1.2a NNTP software bundled with ;; 4.3BSD. (provide 'nntp) (defvar nntp-server-process nil "NNTP news server process.") (defvar nntp-server-hook nil "*Hooks for NNTP news server. If Kanji code of news server is different from local kanji code, you have to put the following code in your .emacs file: (setq nntp-server-hook '(lambda () ;; Server's Kanji code is EUC (NEmacs hack). (make-local-variable 'kanji-fileio-code) (setq kanji-fileio-code 0)))") (defconst nntp-magic-tick 10 "Number of time waiting for server response using `accept-process-output'. The value strongly depends on your machine and news server performance. It is recommended to re-define it in site-init.el or your .emacs file. Optimal values for well-known machines are as follows: SUN3/260: 10 S-3500 UTS: 1") ;; Retrieving lots of headers by sending command asynchronously. ;; Access functions to headers are defined as macro. (defmacro nntp-headers-number (headers) "Return article number in HEADERS." (` (car (, headers)))) (defmacro nntp-headers-subject (headers) "Return subject string in HEADERS." (` (nth 1 (, headers)))) (defmacro nntp-headers-from (headers) "Return author string in HEADERS." (` (nth 2 (, headers)))) (defmacro nntp-headers-xref (headers) "Return xref string in HEADERS." (` (nth 3 (, headers)))) (defun nntp-retrieve-headers (sequence) "Return list of article headers specified by SEQUENCE of article id. The format of list is `((NUMBER SUBJECT FROM XREF) ...)'. News group must be selected before calling me." (save-excursion (let ((number (length sequence)) (headers nil) ;Hold result list (article 0) (subject nil) (xref nil) (from nil) (count 0)) (set-buffer (process-buffer nntp-server-process)) (erase-buffer) ;; Send HEAD command. (while sequence (nntp-send-strings-to-server "HEAD" (car sequence)) (setq sequence (cdr sequence))) ;; Wait for completion of reply. (sleep-for 1) ;;(accept-process-output) (goto-char (point-min)) (while (< (nntp-count-reply "^[0-9]") number) ;;(message "Reading...: %d" count) ;; I'm not sure which is the fastest way to wait for ;; completion of request, sleep-for or accept-process-output. (if (or (> count nntp-magic-tick) (> number 10)) (progn ;; Fujitsu UTS requires the next code. I don't know why? ;; Usg-unix-v which supports TCP/IP stream is assumed as ;; Fujitsu UTS system. (if (eq system-type 'usg-unix-v) (message "Reading...")) (sleep-for 1) (if (eq system-type 'usg-unix-v) (message ""))) (setq count (1+ count)) (accept-process-output)) (goto-char (point-min)) ) ;; Wait for text of last command. (goto-char (point-max)) (re-search-backward "^[0-9]") (if (looking-at "^[23]") (while (progn (goto-char (- (point-max) 3)) (not (looking-at "^\\.\r$"))) ;;(sleep-for 1) (accept-process-output) )) ;; Now all of replies are recieved. ;; First, delete unnecessary lines. (goto-char (point-min)) (delete-non-matching-lines "^Subject:[ \t]\\|^Xref:[ \t]\\|^From:[ \t]\\|^[23]") ;; Then examines replies. (while (not (eobp)) (cond ((looking-at "^[23].*[ \t]+\\([0-9]+\\)[ \t]+") ;Article exists. (setq article (string-to-int (buffer-substring (match-beginning 1) (match-end 1)))) (forward-line 1) (setq subject nil) (setq xref nil) (setq from nil) ;; It is better to extract From:, Subject: and Xref: ;; field values in this order. (while (looking-at "^[^23]") (if (looking-at "^From:[ \t]\\(.*\\)\r$") (progn ;; Extract From: field. (setq from (buffer-substring (match-beginning 1) (match-end 1))) (forward-line 1))) (if (looking-at "^Subject:[ \t]\\(.*\\)\r$") (progn ;; Extract Subject: field. (setq subject (buffer-substring (match-beginning 1) (match-end 1))) (forward-line 1))) (if (looking-at "^Xref:[ \t]\\(.*\\)\r$") (progn ;; Extract Xref: field. (setq xref (buffer-substring (match-beginning 1) (match-end 1))) (forward-line 1))) ) (if (and subject from) (setq headers (cons (list article subject from xref) headers)) ;; Subject: and From: field must be specified. (error "NNTP: recieve error(1) on line: %s" (buffer-substring (point) (save-excursion (end-of-line) (point))))) ) (t ;No matching lines (error "NNTP: recieve error(2) on line: %s" (buffer-substring (point) (save-excursion (end-of-line) (point)))) ))) (nreverse headers) ))) (defun nntp-count-reply (regexp) "Count matches for REGEXP following point." (let ((count 0)) (save-excursion (while (and (not (eobp)) (re-search-forward regexp nil t)) (setq count (1+ count)) )) ;; Return count count )) ;;; ;;; Raw Interface to Network News Transfer Protocol (RFC977) ;;; (defun nntp-open-server (host &optional service) "Open news server on HOST. If HOST is nil, use value of environment variable `NNTPSERVER'. If optional argument SERVICE is non-nil, open by the service name." (let ((host (or host (getenv "NNTPSERVER") (error "NNTP: no server host is specified.")))) (if (nntp-open-server-internal host service) (nntp-wait-for-response "^[23].*\r$")) )) (defun nntp-close-server () "Close news server." (unwind-protect ;; We cannot send QUIT command unless the process is running. (if (memq (process-status nntp-server-process) '(run open)) (nntp-send-command nil "QUIT")) (nntp-close-server-internal) )) (fset 'nntp-request-quit (symbol-function 'nntp-close-server)) (defun nntp-request-article (id) "Select article by message ID (or number)." (prog1 (nntp-send-command "^\\.\r$" "ARTICLE" id) (nntp-decode-text) )) (defun nntp-request-body (id) "Select article body by message ID (or number)." (prog1 (nntp-send-command "^\\.\r$" "BODY" id) (nntp-decode-text) )) (defun nntp-request-head (id) "Select article head by message ID (or number)." (prog1 (nntp-send-command "^\\.\r$" "HEAD" id) (nntp-decode-text) )) (defun nntp-request-stat (id) "Select article by message ID (or number)." (nntp-send-command "^[23].*\r$" "STAT" id)) (defun nntp-request-group (group) "Select news GROUP." ;; 1.2a NNTP's group command is buggy. "^M" (\r) is not appended to ;; end of the status message. (nntp-send-command "^[23].*$" "GROUP" group)) (defun nntp-request-list () "List valid newsgoups." (prog1 (nntp-send-command "^\\.\r$" "LIST") (nntp-decode-text) )) (defun nntp-request-last () "Set current article pointer to the previous article in the current news group." (nntp-send-command "^[23].*\r$" "LAST")) (defun nntp-request-next () "Advance current article pointer." (nntp-send-command "^[23].*\r$" "NEXT")) (defun nntp-request-post () "Post a new news in current buffer." (if (nntp-send-command "^[23].*\r$" "POST") (progn (nntp-encode-text) (nntp-send-region-to-server (point-min) (point-max)) ;; 1.2a NNTP's post command is buggy. "^M" (\r) is not ;; appended to end of the status message. (nntp-wait-for-response "^[23].*$") ))) ;; Encoding and decoding of NNTP text. (defun nntp-decode-text () "Decode text transmitted by NNTP. 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 (process-buffer nntp-server-process)) ;; Insert newline at end of buffer. (goto-char (point-max)) (if (not (bolp)) (insert "\n")) ;; Delete `^M' at end of line. (goto-char (point-min)) ;; (replace-regexp "\r$" "") (while (not (eobp)) (end-of-line) (forward-char -1) (if (looking-at "\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 "^\\.$") (progn (kill-line) (kill-line))) ;; Replace `..' at beginning of line with `.'. (goto-char (point-min)) ;; (replace-regexp "^\\.\\." ".") (while (not (eobp)) (if (looking-at "^\\.\\.") (delete-char 1)) (forward-line 1) (beginning-of-line)) )) (defun nntp-encode-text () "Encode text in current buffer for NNTP transmission. 1. Insert `.' at beginning of line. 2. Insert `.' at end of buffer (end of text mark)." (save-excursion ;; Insert newline at end of buffer. (goto-char (point-max)) (if (not (bolp)) (insert "\n")) ;; Replace `.' ad beginning of line with `..'. (goto-char (point-min)) ;; (replace-regexp "^\\." "..") (while (not (eobp)) (if (looking-at "^\\.") (insert ".")) (forward-line 1) (beginning-of-line)) ;; Insert `.' at end of buffer (end of text mark). (goto-char (point-max)) (insert ".\n") )) ;;; ;;; Synchronous Communication with NNTP Server ;;; (defun nntp-send-command (response cmd &rest args) "Wailt for server RESPONSE after sending CMD and optional ARGS to news server." (save-excursion ;; Clear communication buffer. (set-buffer (process-buffer nntp-server-process)) (erase-buffer) (apply 'nntp-send-strings-to-server cmd args) (if response (nntp-wait-for-response response) t) )) (defun nntp-wait-for-response (regexp) "Wait for server response which matches REGEXP." (save-excursion (let ((status t) (wait t) (count 0)) (set-buffer (process-buffer nntp-server-process)) ;; Wait for status response (RFC977). ;; 1xx - Informative message. ;; 2xx - Command ok. ;; 3xx - Command ok so far, send the rest of it. ;; 4xx - Command was correct, but couldn't be performed for some ;; reason. ;; 5xx - Command unimplemented, or incorrect, or a serious ;; program error occurred. ;; I'm not sure which is better method for waiting for ;; completion of NNTP command. At least communication between ;; photon and flab works fine by `accept-process-output'. ;;(sleep-for 1) (accept-process-output) (while wait (goto-char (point-min)) (cond ((looking-at "[23]") (setq wait nil)) ((looking-at "[45]") (setq status nil) (setq wait nil)) (t ;;(message "Reading...: %d" count) ;; I'm not sure `accept-process-output' causes infinite ;; loop. (if (> count nntp-magic-tick) (sleep-for 1) (setq count (1+ count)) (accept-process-output)) )) ) (if status (progn (setq wait t) (setq count 0) ;Reset counter. (while wait (goto-char (point-max)) (forward-line -1) (beginning-of-line) ;;(message (buffer-substring ;; (point) ;; (save-excursion (end-of-line) (point)))) (if (looking-at regexp) (setq wait nil) ;;(message "Reading...: %d" count) ;; I'm not sure `accept-process-output' causes ;; infinite loop. (if (> count nntp-magic-tick) (progn ;; Fujitsu UTS requires the next code. I don't ;; know why? (UMERIN) (message "Reading...") (sleep-for 1) (message "")) (setq count (1+ count)) (accept-process-output)) )) ;; Successfully recieved server response. t )) ))) ;;; ;;; Low-Level Interface to NNTP Server ;;; (defun nntp-send-strings-to-server (&rest strings) "Send list of STRINGS to news server as command and its arguments." (let ((cmd (car strings)) (strings (cdr strings))) ;; Command and each argument must be separeted by one or more spaces. (while strings (setq cmd (concat cmd " " (car strings))) (setq strings (cdr strings))) ;; Command line must be terminated by a CR-LF. (process-send-string nntp-server-process (concat cmd "\n")) )) (defun nntp-send-region-to-server (begin end) "Send current buffer region (from BEGIN to END) to news server." (save-excursion (save-excursion ;; Clear communication buffer. (set-buffer (process-buffer nntp-server-process)) (erase-buffer)) (copy-to-buffer (process-buffer nntp-server-process) begin end) ;; We have to work on the buffer associated with NNTP server ;; process because of NEmacs hack. (set-buffer (process-buffer nntp-server-process)) (setq begin (point-min)) (setq end (point-max)) ;; `process-send-region' does not work if text to be sent is very ;; large. I don't know maximum size of text sent correctly. (let ((last nil) (size 100)) ;Size of text sent at once. (save-restriction (narrow-to-region begin end) (goto-char begin) (while (not (eobp)) (setq last (min end (+ (point) size))) (process-send-region nntp-server-process (point) last) ;; I don't know whether the next codes solve the known ;; problem of communication error of GNU Emacs. (accept-process-output) ;;(sit-for 0) (goto-char last) ))) ;; We cannot erase buffer, because reply may be received. (delete-region begin end) )) (defun nntp-open-server-internal (host &optional service) "Open connection to news server on HOST by SERVICE (default is nntp)." (save-excursion ;; Initialize communication buffer. (set-buffer (get-buffer-create " *nntpd*")) (kill-all-local-variables) (erase-buffer) (prog1 (setq nntp-server-process (open-network-stream "nntpd" (current-buffer) host (or service "nntp"))) ;; You can change kanji-fileio-code in hooks. (run-hooks 'nntp-server-hook)) )) (defun nntp-close-server-internal () "Close connection to news server." (delete-process nntp-server-process) (setq nntp-server-process nil))