*** empty log message ***
[gnus] / lisp / nntp.el
1 ;;; nntp.el --- nntp access for Gnus
2 ;;; Copyright (C) 1987,88,89,90,92,93,94,95,96,97 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
5 ;; Keywords: news
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to
21 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
22
23 ;;; Commentary:
24
25 ;;; Code:
26
27 (require 'nnheader)
28 (require 'nnoo)
29 (require 'gnus-util)
30
31 (nnoo-declare nntp)
32
33 (eval-and-compile
34   (unless (fboundp 'open-network-stream)
35     (require 'tcp)))
36
37 (eval-when-compile (require 'cl))
38
39 (defvoo nntp-address nil
40   "Address of the physical nntp server.")
41
42 (defvoo nntp-port-number "nntp"
43   "Port number on the physical nntp server.")
44
45 (defvoo nntp-server-opened-hook '(nntp-send-mode-reader)
46   "*Hook used for sending commands to the server at startup.
47 The default value is `nntp-send-mode-reader', which makes an innd
48 server spawn an nnrpd server.  Another useful function to put in this
49 hook might be `nntp-send-authinfo', which will prompt for a password
50 to allow posting from the server.  Note that this is only necessary to
51 do on servers that use strict access control.")
52
53 (defvoo nntp-authinfo-function 'nntp-send-authinfo
54   "Function used to send AUTHINFO to the server.")
55
56 (defvoo nntp-server-action-alist
57   '(("nntpd 1\\.5\\.11t"
58      (remove-hook 'nntp-server-opened-hook 'nntp-send-mode-reader))
59     ("NNRP server Netscape"
60      (setq nntp-server-list-active-group nil)))
61   "Alist of regexps to match on server types and actions to be taken.
62 For instance, if you want Gnus to beep every time you connect
63 to innd, you could say something like:
64
65 \(setq nntp-server-action-alist
66        '((\"innd\" (ding))))
67
68 You probably don't want to do that, though.")
69
70 (defvoo nntp-open-connection-function 'nntp-open-network-stream
71   "*Function used for connecting to a remote system.
72 It will be called with the buffer to output in.
73
74 Two pre-made functions are `nntp-open-network-stream', which is the
75 default, and simply connects to some port or other on the remote
76 system (see nntp-port-number).  The other are `nntp-open-rlogin',
77 which does an rlogin on the remote system, and then does a telnet to
78 the NNTP server available there (see nntp-rlogin-parameters) and
79 `nntp-open-telnet' which telnets to a remote system, logs in and does
80 the same.")
81
82 (defvoo nntp-rlogin-parameters '("telnet" "-8" "${NNTPSERVER:=news}" "nntp")
83   "*Parameters to `nntp-open-login'.
84 That function may be used as `nntp-open-connection-function'.  In that
85 case, this list will be used as the parameter list given to rsh.")
86
87 (defvoo nntp-rlogin-user-name nil
88   "*User name on remote system when using the rlogin connect method.")
89
90 (defvoo nntp-telnet-parameters '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp")
91   "*Parameters to `nntp-open-telnet'.
92 That function may be used as `nntp-open-connection-function'.  In that
93 case, this list will be executed as a command after logging in
94 via telnet.")
95
96 (defvoo nntp-telnet-user-name nil
97   "User name to log in via telnet with.")
98
99 (defvoo nntp-telnet-passwd nil
100   "Password to use to log in via telnet with.")
101
102 (defvoo nntp-telnet-command "telnet"
103   "Command used to start telnet.")
104
105 (defvoo nntp-telnet-switches '("-8")
106   "Switches given to the telnet command.")
107
108 (defvoo nntp-end-of-line "\r\n"
109   "String to use on the end of lines when talking to the NNTP server.
110 This is \"\\r\\n\" by default, but should be \"\\n\" when
111 using rlogin or telnet to communicate with the server.")
112
113 (defvoo nntp-large-newsgroup 50
114   "*The number of the articles which indicates a large newsgroup.
115 If the number of the articles is greater than the value, verbose
116 messages will be shown to indicate the current status.")
117
118 (defvoo nntp-maximum-request 400
119   "*The maximum number of the requests sent to the NNTP server at one time.
120 If Emacs hangs up while retrieving headers, set the variable to a
121 lower value.")
122
123 (defvoo nntp-nov-is-evil nil
124   "*If non-nil, nntp will never attempt to use XOVER when talking to the server.")
125
126 (defvoo nntp-xover-commands '("XOVER" "XOVERVIEW")
127   "*List of strings that are used as commands to fetch NOV lines from a server.
128 The strings are tried in turn until a positive response is gotten.  If
129 none of the commands are successful, nntp will just grab headers one
130 by one.")
131
132 (defvoo nntp-nov-gap 5
133   "*Maximum allowed gap between two articles.
134 If the gap between two consecutive articles is bigger than this
135 variable, split the XOVER request into two requests.")
136
137 (defvoo nntp-connection-timeout nil
138   "*Number of seconds to wait before an nntp connection times out.
139 If this variable is nil, which is the default, no timers are set.")
140
141 (defvoo nntp-prepare-server-hook nil
142   "*Hook run before a server is opened.
143 If can be used to set up a server remotely, for instance.  Say you
144 have an account at the machine \"other.machine\".  This machine has
145 access to an NNTP server that you can't access locally.  You could
146 then use this hook to rsh to the remote machine and start a proxy NNTP
147 server there that you can connect to.  See also `nntp-open-connection-function'")
148
149 (defvoo nntp-warn-about-losing-connection t
150   "*If non-nil, beep when a server closes connection.")
151
152 ;; 1997/5/4 by MORIOKA Tomohiko <morioka@jaist.ac.jp>
153 (defvoo nntp-coding-system-for-read nil
154   "*coding-system for read from NNTP.")
155
156 \f
157
158 ;;; Internal variables.
159
160 (defvar nntp-have-messaged nil)
161
162 (defvar nntp-process-wait-for nil)
163 (defvar nntp-process-to-buffer nil)
164 (defvar nntp-process-callback nil)
165 (defvar nntp-process-decode nil)
166 (defvar nntp-process-start-point nil)
167 (defvar nntp-inside-change-function nil)
168
169 (defvar nntp-connection-list nil)
170
171 (defvoo nntp-server-type nil)
172 (defvoo nntp-connection-alist nil)
173 (defvoo nntp-status-string "")
174 (defconst nntp-version "nntp 5.0")
175 (defvoo nntp-inhibit-erase nil)
176 (defvoo nntp-inhibit-output nil)
177
178 (defvoo nntp-server-xover 'try)
179 (defvoo nntp-server-list-active-group 'try)
180
181 (eval-and-compile
182   (autoload 'nnmail-read-passwd "nnmail"))
183
184 \f
185
186 ;;; Internal functions.
187
188 (defsubst nntp-send-string (process string)
189   "Send STRING to PROCESS."
190   (process-send-string process (concat string nntp-end-of-line)))
191
192 (defsubst nntp-wait-for (process wait-for buffer &optional decode discard)
193   "Wait for WAIT-FOR to arrive from PROCESS."
194   (save-excursion
195     (set-buffer (process-buffer process))
196     (goto-char (point-min))
197     (while (or (not (memq (char-after (point)) '(?2 ?3 ?4 ?5)))
198                (looking-at "480"))
199       (when (looking-at "480")
200         (erase-buffer)
201         (funcall nntp-authinfo-function))
202       (nntp-accept-process-output process)
203       (goto-char (point-min)))
204     (prog1
205         (if (looking-at "[45]")
206             (progn
207               (nntp-snarf-error-message)
208               nil)
209           (goto-char (point-max))
210           (let ((limit (point-min)))
211             (while (not (re-search-backward wait-for limit t))
212               ;; We assume that whatever we wait for is less than 1000
213               ;; characters long.
214               (setq limit (max (- (point-max) 1000) (point-min)))
215               (nntp-accept-process-output process)
216               (goto-char (point-max))))
217           (nntp-decode-text (not decode))
218           (unless discard
219             (save-excursion
220               (set-buffer buffer)
221               (goto-char (point-max))
222               (insert-buffer-substring (process-buffer process))
223               ;; Nix out "nntp reading...." message.
224               (when nntp-have-messaged
225                 (setq nntp-have-messaged nil)
226                 (message ""))
227               t)))
228       (unless discard
229         (erase-buffer)))))
230
231 (defsubst nntp-find-connection (buffer)
232   "Find the connection delivering to BUFFER."
233   (let ((alist nntp-connection-alist)
234         (buffer (if (stringp buffer) (get-buffer buffer) buffer))
235         process entry)
236     (while (setq entry (pop alist))
237       (when (eq buffer (cadr entry))
238         (setq process (car entry)
239               alist nil)))
240     (when process
241       (if (memq (process-status process) '(open run))
242           process
243         (when (buffer-name (process-buffer process))
244           (kill-buffer (process-buffer process)))
245         (setq nntp-connection-alist (delq entry nntp-connection-alist))
246         nil))))
247
248 (defsubst nntp-find-connection-entry (buffer)
249   "Return the entry for the connection to BUFFER."
250   (assq (nntp-find-connection buffer) nntp-connection-alist))
251
252 (defun nntp-find-connection-buffer (buffer)
253   "Return the process connection buffer tied to BUFFER."
254   (let ((process (nntp-find-connection buffer)))
255     (when process
256       (process-buffer process))))
257
258 (defsubst nntp-retrieve-data (command address port buffer
259                                    &optional wait-for callback decode)
260   "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS."
261   (let ((process (or (nntp-find-connection buffer)
262                      (nntp-open-connection buffer))))
263     (if (not process)
264         (nnheader-report 'nntp "Couldn't open connection to %s" address)
265       (unless (or nntp-inhibit-erase nnheader-callback-function)
266         (save-excursion
267           (set-buffer (process-buffer process))
268           (erase-buffer)))
269       (when command
270         (nntp-send-string process command))
271       (cond
272        ((eq callback 'ignore)
273         t)
274        ((and callback wait-for)
275         (save-excursion
276           (set-buffer (process-buffer process))
277           (unless nntp-inside-change-function
278             (erase-buffer))
279           (setq nntp-process-decode decode
280                 nntp-process-to-buffer buffer
281                 nntp-process-wait-for wait-for
282                 nntp-process-callback callback
283                 nntp-process-start-point (point-max)
284                 after-change-functions
285                 (list 'nntp-after-change-function-callback)))
286         t)
287        (wait-for
288         (nntp-wait-for process wait-for buffer decode))
289        (t t)))))
290
291 (defsubst nntp-send-command (wait-for &rest strings)
292   "Send STRINGS to server and wait until WAIT-FOR returns."
293   (when (and (not nnheader-callback-function)
294              (not nntp-inhibit-output))
295     (save-excursion
296       (set-buffer nntp-server-buffer)
297       (erase-buffer)))
298   (nntp-retrieve-data
299    (mapconcat 'identity strings " ")
300    nntp-address nntp-port-number nntp-server-buffer
301    wait-for nnheader-callback-function))
302
303 (defun nntp-send-command-nodelete (wait-for &rest strings)
304   "Send STRINGS to server and wait until WAIT-FOR returns."
305   (nntp-retrieve-data
306    (mapconcat 'identity strings " ")
307    nntp-address nntp-port-number nntp-server-buffer
308    wait-for nnheader-callback-function))
309
310 (defun nntp-send-command-and-decode (wait-for &rest strings)
311   "Send STRINGS to server and wait until WAIT-FOR returns."
312   (when (and (not nnheader-callback-function)
313              (not nntp-inhibit-output))
314     (save-excursion
315       (set-buffer nntp-server-buffer)
316       (erase-buffer)))
317   (nntp-retrieve-data
318    (mapconcat 'identity strings " ")
319    nntp-address nntp-port-number nntp-server-buffer
320    wait-for nnheader-callback-function t))
321
322 (defun nntp-send-buffer (wait-for)
323   "Send the current buffer to server and wait until WAIT-FOR returns."
324   (when (and (not nnheader-callback-function)
325              (not nntp-inhibit-output))
326     (save-excursion
327       (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
328       (erase-buffer)))
329   (nntp-encode-text)
330   (process-send-region (nntp-find-connection nntp-server-buffer)
331                        (point-min) (point-max))
332   (nntp-retrieve-data
333    nil nntp-address nntp-port-number nntp-server-buffer
334    wait-for nnheader-callback-function))
335
336 \f
337
338 ;;; Interface functions.
339
340 (nnoo-define-basics nntp)
341
342 (deffoo nntp-retrieve-headers (articles &optional group server fetch-old)
343   "Retrieve the headers of ARTICLES."
344   (nntp-possibly-change-group group server)
345   (save-excursion
346     (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
347     (erase-buffer)
348     (if (and (not gnus-nov-is-evil)
349              (not nntp-nov-is-evil)
350              (nntp-retrieve-headers-with-xover articles fetch-old))
351         ;; We successfully retrieved the headers via XOVER.
352         'nov
353       ;; XOVER didn't work, so we do it the hard, slow and inefficient
354       ;; way.
355       (let ((number (length articles))
356             (count 0)
357             (received 0)
358             (last-point (point-min))
359             (buf (nntp-find-connection-buffer nntp-server-buffer))
360             (nntp-inhibit-erase t))
361         ;; Send HEAD command.
362         (while articles
363           (nntp-send-command
364            nil
365            "HEAD" (if (numberp (car articles))
366                       (int-to-string (car articles))
367                     ;; `articles' is either a list of article numbers
368                     ;; or a list of article IDs.
369                     (car articles)))
370           (setq articles (cdr articles)
371                 count (1+ count))
372           ;; Every 400 header requests we have to read the stream in
373           ;; order to avoid deadlocks.
374           (when (or (null articles)     ;All requests have been sent.
375                     (zerop (% count nntp-maximum-request)))
376             (nntp-accept-response)
377             (while (progn
378                      (progn
379                        (set-buffer buf)
380                        (goto-char last-point))
381                      ;; Count replies.
382                      (while (re-search-forward "^[0-9]" nil t)
383                        (incf received))
384                      (setq last-point (point))
385                      (< received count))
386               ;; If number of headers is greater than 100, give
387               ;;  informative messages.
388               (and (numberp nntp-large-newsgroup)
389                    (> number nntp-large-newsgroup)
390                    (zerop (% received 20))
391                    (nnheader-message 6 "NNTP: Receiving headers... %d%%"
392                                      (/ (* received 100) number)))
393               (nntp-accept-response))))
394         ;; Wait for text of last command.
395         (goto-char (point-max))
396         (re-search-backward "^[0-9]" nil t)
397         (when (looking-at "^[23]")
398           (while (progn
399                    (goto-char (point-max))
400                    (forward-line -1)
401                    (not (looking-at "^\\.\r?\n")))
402             (nntp-accept-response)))
403         (and (numberp nntp-large-newsgroup)
404              (> number nntp-large-newsgroup)
405              (nnheader-message 6 "NNTP: Receiving headers...done"))
406
407         ;; Now all of replies are received.  Fold continuation lines.
408         (nnheader-fold-continuation-lines)
409         ;; Remove all "\r"'s.
410         (nnheader-strip-cr)
411         (copy-to-buffer nntp-server-buffer (point-min) (point-max))
412         'headers))))
413
414 (deffoo nntp-retrieve-groups (groups &optional server)
415   "Retrieve group info on GROUPS."
416   (nntp-possibly-change-group nil server)
417   (save-excursion
418     (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
419     ;; The first time this is run, this variable is `try'.  So we
420     ;; try.
421     (when (eq nntp-server-list-active-group 'try)
422       (nntp-try-list-active (car groups)))
423     (erase-buffer)
424     (let ((count 0)
425           (received 0)
426           (last-point (point-min))
427           (nntp-inhibit-erase t)
428           (command (if nntp-server-list-active-group "LIST ACTIVE" "GROUP")))
429       (while groups
430         ;; Send the command to the server.
431         (nntp-send-command nil command (pop groups))
432         (incf count)
433         ;; Every 400 requests we have to read the stream in
434         ;; order to avoid deadlocks.
435         (when (or (null groups)         ;All requests have been sent.
436                   (zerop (% count nntp-maximum-request)))
437           (nntp-accept-response)
438           (while (progn
439                    (goto-char last-point)
440                    ;; Count replies.
441                    (while (re-search-forward "^[0-9]" nil t)
442                      (incf received))
443                    (setq last-point (point))
444                    (< received count))
445             (nntp-accept-response))))
446
447       ;; Wait for the reply from the final command.
448       (goto-char (point-max))
449       (re-search-backward "^[0-9]" nil t)
450       (when (looking-at "^[23]")
451         (while (progn
452                  (goto-char (point-max))
453                  (if (not nntp-server-list-active-group)
454                      (not (re-search-backward "\r?\n" (- (point) 3) t))
455                    (not (re-search-backward "^\\.\r?\n" (- (point) 4) t))))
456           (nntp-accept-response)))
457
458       ;; Now all replies are received.  We remove CRs.
459       (goto-char (point-min))
460       (while (search-forward "\r" nil t)
461         (replace-match "" t t))
462
463       (if (not nntp-server-list-active-group)
464           (progn
465             (copy-to-buffer nntp-server-buffer (point-min) (point-max))
466             'group)
467         ;; We have read active entries, so we just delete the
468         ;; superfluous gunk.
469         (goto-char (point-min))
470         (while (re-search-forward "^[.2-5]" nil t)
471           (delete-region (match-beginning 0)
472                          (progn (forward-line 1) (point))))
473         (copy-to-buffer nntp-server-buffer (point-min) (point-max))
474         'active))))
475
476 (deffoo nntp-retrieve-articles (articles &optional group server)
477   (nntp-possibly-change-group group server)
478   (save-excursion
479     (let ((number (length articles))
480           (count 0)
481           (received 0)
482           (last-point (point-min))
483           (buf (nntp-find-connection-buffer nntp-server-buffer))
484           (nntp-inhibit-erase t)
485           (map (apply 'vector articles))
486           (point 1)
487           article alist)
488       (set-buffer buf)
489       (erase-buffer)
490       ;; Send HEAD command.
491       (while (setq article (pop articles))
492         (nntp-send-command
493          nil
494          "ARTICLE" (if (numberp article)
495                        (int-to-string article)
496                      ;; `articles' is either a list of article numbers
497                      ;; or a list of article IDs.
498                      article))
499         (incf count)
500         ;; Every 400 requests we have to read the stream in
501         ;; order to avoid deadlocks.
502         (when (or (null articles)       ;All requests have been sent.
503                   (zerop (% count nntp-maximum-request)))
504           (nntp-accept-response)
505           (while (progn
506                    (progn
507                      (set-buffer buf)
508                      (goto-char last-point))
509                    ;; Count replies.
510                    (while (nntp-next-result-arrived-p)
511                      (aset map received (cons (aref map received) (point)))
512                      (incf received))
513                    (setq last-point (point))
514                    (< received count))
515             ;; If number of headers is greater than 100, give
516             ;;  informative messages.
517             (and (numberp nntp-large-newsgroup)
518                  (> number nntp-large-newsgroup)
519                  (zerop (% received 20))
520                  (nnheader-message 6 "NNTP: Receiving articles... %d%%"
521                                    (/ (* received 100) number)))
522             (nntp-accept-response))))
523       (and (numberp nntp-large-newsgroup)
524            (> number nntp-large-newsgroup)
525            (nnheader-message 6 "NNTP: Receiving headers...done"))
526
527       ;; Now we have all the responses.  We go through the results,
528       ;; washes it and copies it over to the server buffer.
529       (set-buffer nntp-server-buffer)
530       (erase-buffer)
531       (mapcar
532        (lambda (entry)
533          (narrow-to-region
534           (setq point (goto-char (point-max)))
535           (progn
536             (insert-buffer-substring buf last-point (cdr entry))
537             (point-max)))
538          (nntp-decode-text)
539          (widen)
540          (cons (car entry) point))
541        map))))
542
543 (defun nntp-next-result-arrived-p ()
544   (let ((point (point)))
545     (cond
546      ((looking-at "2")
547       (if (re-search-forward "\n.\r?\n" nil t)
548           t
549         (goto-char point)
550         nil))
551      ((looking-at "[34]")
552       (forward-line 1)
553       t)
554      (t
555       nil))))
556
557 (defun nntp-try-list-active (group)
558   (nntp-list-active-group group)
559   (save-excursion
560     (set-buffer nntp-server-buffer)
561     (goto-char (point-min))
562     (cond ((or (eobp)
563                (looking-at "5[0-9]+"))
564            (setq nntp-server-list-active-group nil))
565           (t
566            (setq nntp-server-list-active-group t)))))
567
568 (deffoo nntp-list-active-group (group &optional server)
569   "Return the active info on GROUP (which can be a regexp."
570   (nntp-possibly-change-group nil server)
571   (nntp-send-command "^.*\r?\n" "LIST ACTIVE" group))
572
573 (deffoo nntp-request-article (article &optional group server buffer command)
574   (nntp-possibly-change-group group server)
575   (when (nntp-send-command-and-decode
576          "\r?\n\\.\r?\n" "ARTICLE"
577          (if (numberp article) (int-to-string article) article))
578     (if (and buffer
579              (not (equal buffer nntp-server-buffer)))
580         (save-excursion
581           (set-buffer nntp-server-buffer)
582           (copy-to-buffer buffer (point-min) (point-max))
583           (nntp-find-group-and-number))
584       (nntp-find-group-and-number))))
585
586 (deffoo nntp-request-head (article &optional group server)
587   (nntp-possibly-change-group group server)
588   (when (nntp-send-command
589          "\r?\n\\.\r?\n" "HEAD"
590          (if (numberp article) (int-to-string article) article))
591     (prog1
592         (nntp-find-group-and-number)
593       (nntp-decode-text))))
594
595 (deffoo nntp-request-body (article &optional group server)
596   (nntp-possibly-change-group group server)
597   (nntp-send-command-and-decode
598    "\r?\n\\.\r?\n" "BODY"
599    (if (numberp article) (int-to-string article) article)))
600
601 (deffoo nntp-request-group (group &optional server dont-check)
602   (nntp-possibly-change-group nil server)
603   (when (nntp-send-command "^2.*\n" "GROUP" group)
604     (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
605       (setcar (cddr entry) group))))
606
607 (deffoo nntp-close-group (group &optional server)
608   t)
609
610 (deffoo nntp-server-opened (&optional server)
611   "Say whether a connection to SERVER has been opened."
612   (and (nnoo-current-server-p 'nntp server)
613        nntp-server-buffer
614        (gnus-buffer-live-p nntp-server-buffer)
615        (nntp-find-connection nntp-server-buffer)))
616
617 (deffoo nntp-open-server (server &optional defs connectionless)
618   (nnheader-init-server-buffer)
619   (if (nntp-server-opened server)
620       t
621     (when (or (stringp (car defs))
622               (numberp (car defs)))
623       (setq defs (cons (list 'nntp-port-number (car defs)) (cdr defs))))
624     (unless (assq 'nntp-address defs)
625       (setq defs (append defs (list (list 'nntp-address server)))))
626     (nnoo-change-server 'nntp server defs)
627     (unless connectionless
628       (or (nntp-find-connection nntp-server-buffer)
629           (nntp-open-connection nntp-server-buffer)))))
630
631 (deffoo nntp-close-server (&optional server)
632   (nntp-possibly-change-group nil server t)
633   (let (process)
634     (while (setq process (car (pop nntp-connection-alist)))
635       (when (memq (process-status process) '(open run))
636         (set-process-sentinel process nil)
637         (nntp-send-string process "QUIT"))
638       (when (buffer-name (process-buffer process))
639         (kill-buffer (process-buffer process))))
640     (nnoo-close-server 'nntp)))
641
642 (deffoo nntp-request-close ()
643   (let (process)
644     (while (setq process (pop nntp-connection-list))
645       (when (memq (process-status process) '(open run))
646         (set-process-sentinel process nil)
647         (ignore-errors
648           (nntp-send-string process "QUIT")))
649       (when (buffer-name (process-buffer process))
650         (kill-buffer (process-buffer process))))))
651
652 (deffoo nntp-request-list (&optional server)
653   (nntp-possibly-change-group nil server)
654   (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST"))
655
656 (deffoo nntp-request-list-newsgroups (&optional server)
657   (nntp-possibly-change-group nil server)
658   (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS"))
659
660 (deffoo nntp-request-newgroups (date &optional server)
661   (nntp-possibly-change-group nil server)
662   (save-excursion
663     (set-buffer nntp-server-buffer)
664     (let* ((date (timezone-parse-date date))
665            (time-string
666             (format "%s%02d%02d %s%s%s"
667                     (substring (aref date 0) 2) (string-to-int (aref date 1))
668                     (string-to-int (aref date 2)) (substring (aref date 3) 0 2)
669                     (substring
670                      (aref date 3) 3 5) (substring (aref date 3) 6 8))))
671       (prog1
672           (nntp-send-command "^\\.\r?\n" "NEWGROUPS" time-string)
673         (nntp-decode-text)))))
674
675 (deffoo nntp-request-post (&optional server)
676   (nntp-possibly-change-group nil server)
677   (when (nntp-send-command "^[23].*\r?\n" "POST")
678     (nntp-send-buffer "^[23].*\n")))
679
680 (deffoo nntp-request-type (group article)
681   'news)
682
683 (deffoo nntp-asynchronous-p ()
684   t)
685
686 ;;; Hooky functions.
687
688 (defun nntp-send-mode-reader ()
689   "Send the MODE READER command to the nntp server.
690 This function is supposed to be called from `nntp-server-opened-hook'.
691 It will make innd servers spawn an nnrpd process to allow actual article
692 reading."
693   (nntp-send-command "^.*\r?\n" "MODE READER"))
694
695 (defun nntp-send-nosy-authinfo ()
696   "Send the AUTHINFO to the nntp server.
697 This function is supposed to be called from `nntp-server-opened-hook'.
698 It will prompt for a password."
699   (nntp-send-command
700    "^.*\r?\n" "AUTHINFO USER"
701    (read-string (format "NNTP (%s) user name: " nntp-address)))
702   (nntp-send-command
703    "^.*\r?\n" "AUTHINFO PASS"
704    (nnmail-read-passwd "NNTP (%s) password: " nntp-address)))
705
706 (defun nntp-send-authinfo ()
707   "Send the AUTHINFO to the nntp server.
708 This function is supposed to be called from `nntp-server-opened-hook'.
709 It will prompt for a password."
710   (nntp-send-command "^.*\r?\n" "AUTHINFO USER" (user-login-name))
711   (nntp-send-command
712    "^.*\r?\n" "AUTHINFO PASS"
713    (nnmail-read-passwd (format "NNTP (%s) password: " nntp-address))))
714
715 (defun nntp-send-authinfo-from-file ()
716   "Send the AUTHINFO to the nntp server.
717 This function is supposed to be called from `nntp-server-opened-hook'."
718   (when (file-exists-p "~/.nntp-authinfo")
719     (nnheader-temp-write nil
720       (insert-file-contents "~/.nntp-authinfo")
721       (goto-char (point-min))
722       (nntp-send-command "^.*\r?\n" "AUTHINFO USER" (user-login-name))
723       (nntp-send-command
724        "^.*\r?\n" "AUTHINFO PASS"
725        (buffer-substring (point) (progn (end-of-line) (point)))))))
726
727 ;;; Internal functions.
728
729 (defun nntp-make-process-buffer (buffer)
730   "Create a new, fresh buffer usable for nntp process connections."
731   (save-excursion
732     (set-buffer
733      (generate-new-buffer
734       (format " *server %s %s %s*"
735               nntp-address nntp-port-number
736               (buffer-name (get-buffer buffer)))))
737     (buffer-disable-undo (current-buffer))
738     (set (make-local-variable 'after-change-functions) nil)
739     (set (make-local-variable 'nntp-process-wait-for) nil)
740     (set (make-local-variable 'nntp-process-callback) nil)
741     (set (make-local-variable 'nntp-process-to-buffer) nil)
742     (set (make-local-variable 'nntp-process-start-point) nil)
743     (set (make-local-variable 'nntp-process-decode) nil)
744     (current-buffer)))
745
746 (defun nntp-open-connection (buffer)
747   "Open a connection to PORT on ADDRESS delivering output to BUFFER."
748   (run-hooks 'nntp-prepare-server-hook)
749   (let* ((pbuffer (nntp-make-process-buffer buffer))
750          (process
751           (condition-case ()
752               ;; 1997/5/4 by MORIOKA Tomohiko <morioka@jaist.ac.jp>
753               (let ((coding-system-for-read nntp-coding-system-for-read))
754                 (funcall nntp-open-connection-function pbuffer))
755             (error nil)
756             (quit nil))))
757     (when process
758       (process-kill-without-query process)
759       (nntp-wait-for process "^.*\n" buffer nil t)
760       (if (memq (process-status process) '(open run))
761           (prog1
762               (caar (push (list process buffer nil) nntp-connection-alist))
763             (push process nntp-connection-list)
764             (save-excursion
765               (set-buffer pbuffer)
766               (nntp-read-server-type)
767               (erase-buffer)
768               (set-buffer nntp-server-buffer)
769               (let ((nnheader-callback-function nil))
770                 (run-hooks 'nntp-server-opened-hook))))
771         (when (buffer-name (process-buffer process))
772           (kill-buffer (process-buffer process)))
773         nil))))
774
775 (defun nntp-open-network-stream (buffer)
776   (open-network-stream "nntpd" buffer nntp-address nntp-port-number))
777
778 (defun nntp-read-server-type ()
779   "Find out what the name of the server we have connected to is."
780   ;; Wait for the status string to arrive.
781   (setq nntp-server-type (buffer-string))
782   (let ((alist nntp-server-action-alist)
783         (case-fold-search t)
784         entry)
785     ;; Run server-specific commands.
786     (while alist
787       (setq entry (pop alist))
788       (when (string-match (car entry) nntp-server-type)
789         (if (and (listp (cadr entry))
790                  (not (eq 'lambda (caadr entry))))
791             (eval (cadr entry))
792           (funcall (cadr entry)))))))
793
794 (defun nntp-after-change-function-callback (beg end len)
795   (when nntp-process-callback
796     (save-match-data
797       (if (and (= beg (point-min))
798                (memq (char-after beg) '(?4 ?5)))
799           ;; Report back error messages.
800           (save-excursion
801             (goto-char beg)
802             (if (looking-at "480")
803                 (funcall nntp-authinfo-function)
804               (nntp-snarf-error-message)
805               (funcall nntp-process-callback nil)))
806         (goto-char end)
807         (when (and (> (point) nntp-process-start-point)
808                    (re-search-backward nntp-process-wait-for
809                                        nntp-process-start-point t))
810           (when (buffer-name (get-buffer nntp-process-to-buffer))
811             (let ((cur (current-buffer))
812                   (start nntp-process-start-point))
813               (save-excursion
814                 (set-buffer (get-buffer nntp-process-to-buffer))
815                 (goto-char (point-max))
816                 (let ((b (point)))
817                   (insert-buffer-substring cur start)
818                   (narrow-to-region b (point-max))
819                   (nntp-decode-text)
820                   (widen)))))
821           (goto-char end)
822           (let ((callback nntp-process-callback)
823                 (nntp-inside-change-function t))
824             (setq nntp-process-callback nil)
825             (save-excursion
826               (funcall callback (buffer-name
827                                  (get-buffer nntp-process-to-buffer))))))))))
828
829 (defun nntp-snarf-error-message ()
830   "Save the error message in the current buffer."
831   (let ((message (buffer-string)))
832     (while (string-match "[\r\n]+" message)
833       (setq message (replace-match " " t t message)))
834     (nnheader-report 'nntp message)
835     message))
836
837 (defun nntp-accept-process-output (process)
838   "Wait for output from PROCESS and message some dots."
839   (save-excursion
840     (set-buffer (or (nntp-find-connection-buffer nntp-server-buffer)
841                     nntp-server-buffer))
842     (let ((len (/ (point-max) 1024))
843           message-log-max)
844       (unless (< len 10)
845         (setq nntp-have-messaged t)
846         (nnheader-message 7 "nntp read: %dk" len)))
847     (accept-process-output process 1)))
848
849 (defun nntp-accept-response ()
850   "Wait for output from the process that outputs to BUFFER."
851   (nntp-accept-process-output (nntp-find-connection nntp-server-buffer)))
852
853 (defun nntp-possibly-change-group (group server &optional connectionless)
854   (let ((nnheader-callback-function nil))
855     (when server
856       (or (nntp-server-opened server)
857           (nntp-open-server server nil connectionless)))
858
859     (unless connectionless
860       (or (nntp-find-connection nntp-server-buffer)
861           (nntp-open-connection nntp-server-buffer))))
862
863   (when group
864     (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
865       (when (not (equal group (caddr entry)))
866         (save-excursion
867           (set-buffer (process-buffer (car entry)))
868           (erase-buffer)
869           (nntp-send-string (car entry) (concat "GROUP " group))
870           (nntp-wait-for-string "^2.*\n")
871           (setcar (cddr entry) group)
872           (erase-buffer))))))
873
874 (defun nntp-decode-text (&optional cr-only)
875   "Decode the text in the current buffer."
876   (goto-char (point-min))
877   (while (search-forward "\r" nil t)
878     (delete-char -1))
879   (unless cr-only
880     ;; Remove trailing ".\n" end-of-transfer marker.
881     (goto-char (point-max))
882     (forward-line -1)
883     (when (looking-at ".\n")
884       (delete-char 2))
885     ;; Delete status line.
886     (goto-char (point-min))
887     (delete-region (point) (progn (forward-line 1) (point)))
888     ;; Remove "." -> ".." encoding.
889     (while (search-forward "\n.." nil t)
890       (delete-char -1))))
891
892 (defun nntp-encode-text ()
893   "Encode the text in the current buffer."
894   (save-excursion
895     ;; Replace "." at beginning of line with "..".
896     (goto-char (point-min))
897     (while (re-search-forward "^\\." nil t)
898       (insert "."))
899     (goto-char (point-max))
900     ;; Insert newline at the end of the buffer.
901     (unless (bolp)
902       (insert "\n"))
903     ;; Insert `.' at end of buffer (end of text mark).
904     (goto-char (point-max))
905     (insert "." nntp-end-of-line)))
906
907 (defun nntp-retrieve-headers-with-xover (articles &optional fetch-old)
908   (set-buffer nntp-server-buffer)
909   (erase-buffer)
910   (cond
911
912    ;; This server does not talk NOV.
913    ((not nntp-server-xover)
914     nil)
915
916    ;; We don't care about gaps.
917    ((or (not nntp-nov-gap)
918         fetch-old)
919     (nntp-send-xover-command
920      (if fetch-old
921          (if (numberp fetch-old)
922              (max 1 (- (car articles) fetch-old))
923            1)
924        (car articles))
925      (car (last articles)) 'wait)
926
927     (goto-char (point-min))
928     (when (looking-at "[1-5][0-9][0-9] ")
929       (delete-region (point) (progn (forward-line 1) (point))))
930     (while (search-forward "\r" nil t)
931       (replace-match "" t t))
932     (goto-char (point-max))
933     (forward-line -1)
934     (when (looking-at "\\.")
935       (delete-region (point) (progn (forward-line 1) (point)))))
936
937    ;; We do it the hard way.  For each gap, an XOVER command is sent
938    ;; to the server.  We do not wait for a reply from the server, we
939    ;; just send them off as fast as we can.  That means that we have
940    ;; to count the number of responses we get back to find out when we
941    ;; have gotten all we asked for.
942    ((numberp nntp-nov-gap)
943     (let ((count 0)
944           (received 0)
945           (last-point (point-min))
946           (buf nntp-server-buffer)
947           ;;(process-buffer (nntp-find-connection (current-buffer))))
948           first)
949       ;; We have to check `nntp-server-xover'.  If it gets set to nil,
950       ;; that means that the server does not understand XOVER, but we
951       ;; won't know that until we try.
952       (while (and nntp-server-xover articles)
953         (setq first (car articles))
954         ;; Search forward until we find a gap, or until we run out of
955         ;; articles.
956         (while (and (cdr articles)
957                     (< (- (nth 1 articles) (car articles)) nntp-nov-gap))
958           (setq articles (cdr articles)))
959
960         (when (nntp-send-xover-command first (car articles))
961           (setq articles (cdr articles)
962                 count (1+ count))
963
964           ;; Every 400 requests we have to read the stream in
965           ;; order to avoid deadlocks.
966           (when (or (null articles)     ;All requests have been sent.
967                     (zerop (% count nntp-maximum-request)))
968             (accept-process-output)
969             ;; On some Emacs versions the preceding function has
970             ;; a tendency to change the buffer.  Perhaps.  It's
971             ;; quite difficult to reproduce, because it only
972             ;; seems to happen once in a blue moon.
973             (set-buffer buf)
974             (while (progn
975                      (goto-char last-point)
976                      ;; Count replies.
977                      (while (re-search-forward "^[0-9][0-9][0-9] " nil t)
978                        (setq received (1+ received)))
979                      (setq last-point (point))
980                      (< received count))
981               (accept-process-output)
982               (set-buffer buf)))))
983
984       (when nntp-server-xover
985         ;; Wait for the reply from the final command.
986         (goto-char (point-max))
987         (re-search-backward "^[0-9][0-9][0-9] " nil t)
988         (when (looking-at "^[23]")
989           (while (progn
990                    (goto-char (point-max))
991                    (forward-line -1)
992                    (not (looking-at "^\\.\r?\n")))
993             (nntp-accept-response)))
994
995         ;; We remove any "." lines and status lines.
996         (goto-char (point-min))
997         (while (search-forward "\r" nil t)
998           (delete-char -1))
999         (goto-char (point-min))
1000         (delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ")
1001         ;;(copy-to-buffer nntp-server-buffer (point-min) (point-max))
1002         t))))
1003
1004   nntp-server-xover)
1005
1006 (defun nntp-send-xover-command (beg end &optional wait-for-reply)
1007   "Send the XOVER command to the server."
1008   (let ((range (format "%d-%d" beg end))
1009         (nntp-inhibit-erase t))
1010     (if (stringp nntp-server-xover)
1011         ;; If `nntp-server-xover' is a string, then we just send this
1012         ;; command.
1013         (if wait-for-reply
1014             (nntp-send-command-nodelete
1015              "\r?\n\\.\r?\n" nntp-server-xover range)
1016           ;; We do not wait for the reply.
1017           (nntp-send-command-nodelete "\r?\n\\.\r?\n" nntp-server-xover range))
1018       (let ((commands nntp-xover-commands))
1019         ;; `nntp-xover-commands' is a list of possible XOVER commands.
1020         ;; We try them all until we get at positive response.
1021         (while (and commands (eq nntp-server-xover 'try))
1022           (nntp-send-command-nodelete "\r?\n\\.\r?\n" (car commands) range)
1023           (save-excursion
1024             (set-buffer nntp-server-buffer)
1025             (goto-char (point-min))
1026             (and (looking-at "[23]")    ; No error message.
1027                  ;; We also have to look at the lines.  Some buggy
1028                  ;; servers give back simple lines with just the
1029                  ;; article number.  How... helpful.
1030                  (progn
1031                    (forward-line 1)
1032                    (looking-at "[0-9]+\t...")) ; More text after number.
1033                  (setq nntp-server-xover (car commands))))
1034           (setq commands (cdr commands)))
1035         ;; If none of the commands worked, we disable XOVER.
1036         (when (eq nntp-server-xover 'try)
1037           (save-excursion
1038             (set-buffer nntp-server-buffer)
1039             (erase-buffer)
1040             (setq nntp-server-xover nil)))
1041         nntp-server-xover))))
1042
1043 ;;; Alternative connection methods.
1044
1045 (defun nntp-wait-for-string (regexp)
1046   "Wait until string arrives in the buffer."
1047   (let ((buf (current-buffer)))
1048     (goto-char (point-min))
1049     (while (not (re-search-forward regexp nil t))
1050       (accept-process-output (nntp-find-connection nntp-server-buffer))
1051       (set-buffer buf)
1052       (goto-char (point-min)))))
1053
1054 (defun nntp-open-telnet (buffer)
1055   (save-excursion
1056     (set-buffer buffer)
1057     (erase-buffer)
1058     (let ((proc (apply
1059                  'start-process
1060                  "nntpd" buffer nntp-telnet-command nntp-telnet-switches))
1061           (case-fold-search t))
1062       (when (memq (process-status proc) '(open run))
1063         (process-send-string proc "set escape \^X\n")
1064         (process-send-string proc (concat "open " nntp-address "\n"))
1065         (nntp-wait-for-string "^\r*.?login:")
1066         (process-send-string
1067          proc (concat
1068                (or nntp-telnet-user-name
1069                    (setq nntp-telnet-user-name (read-string "login: ")))
1070                "\n"))
1071         (nntp-wait-for-string "^\r*.?password:")
1072         (process-send-string
1073          proc (concat
1074                (or nntp-telnet-passwd
1075                    (setq nntp-telnet-passwd
1076                          (nnmail-read-passwd "Password: ")))
1077                "\n"))
1078         (erase-buffer)
1079         (nntp-wait-for-string "bash\\|\$ *\r?$\\|> *\r?")
1080         (process-send-string
1081          proc (concat (mapconcat 'identity nntp-telnet-parameters " ") "\n"))
1082         (nntp-wait-for-string "^\r*200")
1083         (beginning-of-line)
1084         (delete-region (point-min) (point))
1085         (process-send-string proc "\^]")
1086         (nntp-wait-for-string "^telnet")
1087         (process-send-string proc "mode character\n")
1088         (accept-process-output proc 1)
1089         (sit-for 1)
1090         (goto-char (point-min))
1091         (forward-line 1)
1092         (delete-region (point) (point-max)))
1093       proc)))
1094
1095 (defun nntp-open-rlogin (buffer)
1096   "Open a connection to SERVER using rsh."
1097   (let ((proc (if nntp-rlogin-user-name
1098                   (start-process
1099                    "nntpd" buffer "rsh"
1100                    nntp-address "-l" nntp-rlogin-user-name
1101                    (mapconcat 'identity
1102                               nntp-rlogin-parameters " "))
1103                 (start-process
1104                  "nntpd" buffer "rsh" nntp-address
1105                  (mapconcat 'identity
1106                             nntp-rlogin-parameters " ")))))
1107     (set-buffer buffer)
1108     (nntp-wait-for-string "^\r*200")
1109     (beginning-of-line)
1110     (delete-region (point-min) (point))
1111     proc))
1112
1113 (defun nntp-find-group-and-number ()
1114   (save-excursion
1115     (save-restriction
1116       (set-buffer nntp-server-buffer)
1117       (narrow-to-region (goto-char (point-min))
1118                         (or (search-forward "\n\n" nil t) (point-max)))
1119       (goto-char (point-min))
1120       ;; We first find the number by looking at the status line.
1121       (let ((number (and (looking-at "2[0-9][0-9] +\\([0-9]+\\) ")
1122                          (string-to-int
1123                           (buffer-substring (match-beginning 1)
1124                                             (match-end 1)))))
1125             group newsgroups xref)
1126         (and number (zerop number) (setq number nil))
1127         ;; Then we find the group name.
1128         (setq group
1129               (cond
1130                ;; If there is only one group in the Newsgroups header,
1131                ;; then it seems quite likely that this article comes
1132                ;; from that group, I'd say.
1133                ((and (setq newsgroups (mail-fetch-field "newsgroups"))
1134                      (not (string-match "," newsgroups)))
1135                 newsgroups)
1136                ;; If there is more than one group in the Newsgroups
1137                ;; header, then the Xref header should be filled out.
1138                ;; We hazard a guess that the group that has this
1139                ;; article number in the Xref header is the one we are
1140                ;; looking for.  This might very well be wrong if this
1141                ;; article happens to have the same number in several
1142                ;; groups, but that's life.
1143                ((and (setq xref (mail-fetch-field "xref"))
1144                      number
1145                      (string-match (format "\\([^ :]+\\):%d" number) xref))
1146                 (substring xref (match-beginning 1) (match-end 1)))
1147                (t "")))
1148         (when (string-match "\r" group)
1149           (setq group (substring group 0 (match-beginning 0))))
1150         (cons group number)))))
1151
1152 (provide 'nntp)
1153
1154 ;;; nntp.el ends here