023e9164b96ea2e8b692eceeaef77d908843fa2e
[gnus] / lisp / nntp.el
1 ;;; nntp.el --- nntp access for Gnus
2
3 ;; Copyright (C) 1987, 1988, 1989, 1990, 1992, 1993, 1994, 1995, 1996,
4 ;; 1997, 1998, 2000, 2001, 2002 Free Software Foundation, Inc.
5
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; Keywords: news
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published
13 ;; by the Free Software Foundation; either version 2, or (at your
14 ;; option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 ;; General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (require 'nnheader)
30 (require 'nnoo)
31 (require 'gnus-util)
32
33 (nnoo-declare nntp)
34
35 (eval-when-compile (require 'cl))
36
37 (defvoo nntp-address nil
38   "Address of the physical nntp server.")
39
40 (defvoo nntp-port-number "nntp"
41   "Port number on the physical nntp server.")
42
43 (defvoo nntp-server-opened-hook '(nntp-send-mode-reader)
44   "*Hook used for sending commands to the server at startup.
45 The default value is `nntp-send-mode-reader', which makes an innd
46 server spawn an nnrpd server.")
47
48 (defvoo nntp-authinfo-function 'nntp-send-authinfo
49   "Function used to send AUTHINFO to the server.
50 It is called with no parameters.")
51
52 (defvoo nntp-server-action-alist
53     '(("nntpd 1\\.5\\.11t"
54        (remove-hook 'nntp-server-opened-hook 'nntp-send-mode-reader))
55       ("NNRP server Netscape"
56        (setq nntp-server-list-active-group nil)))
57   "Alist of regexps to match on server types and actions to be taken.
58 For instance, if you want Gnus to beep every time you connect
59 to innd, you could say something like:
60
61 \(setq nntp-server-action-alist
62        '((\"innd\" (ding))))
63
64 You probably don't want to do that, though.")
65
66 (defvoo nntp-open-connection-function 'nntp-open-network-stream
67   "*Function used for connecting to a remote system.
68 It will be called with the buffer to output in as argument.
69
70 Currently, five such functions are provided (please refer to their
71 respective doc string for more information), three of them establishing
72 direct connections to the nntp server, and two of them using an indirect
73 host.
74
75 Direct connections:
76 - `nntp-open-network-stream' (the default),
77 - `nntp-open-ssl-stream',
78 - `nntp-open-telnet-stream'.
79
80 Indirect connections:
81 - `nntp-open-via-rlogin-and-telnet',
82 - `nntp-open-via-telnet-and-telnet'.")
83
84 (defvoo nntp-pre-command nil
85   "*Pre-command to use with the various nntp-open-via-* methods.
86 This is where you would put \"runsocks\" or stuff like that.")
87
88 (defvoo nntp-telnet-command "telnet"
89   "*Telnet command used to connect to the nntp server.
90 This command is used by the various nntp-open-via-* methods.")
91
92 (defvoo nntp-telnet-switches '("-8")
93   "*Switches given to the telnet command `nntp-telnet-command'.")
94
95 (defvoo nntp-end-of-line "\r\n"
96   "*String to use on the end of lines when talking to the NNTP server.
97 This is \"\\r\\n\" by default, but should be \"\\n\" when
98 using and indirect connection method (nntp-open-via-*).")
99
100 (defvoo nntp-via-rlogin-command "rsh"
101   "*Rlogin command used to connect to an intermediate host.
102 This command is used by the `nntp-open-via-rlogin-and-telnet' method.
103 The default is \"rsh\", but \"ssh\" is a popular alternative.")
104
105 (defvoo nntp-via-rlogin-command-switches nil
106   "*Switches given to the rlogin command `nntp-via-rlogin-command'.
107 Set this to (\"-t\") if you use \"ssh\" for `nntp-via-rlogin-command' and
108 the telnet command requires a pseudo-tty allocation on an intermediate
109 host.")
110
111 (defvoo nntp-via-telnet-command "telnet"
112   "*Telnet command used to connect to an intermediate host.
113 This command is used by the `nntp-open-via-telnet-and-telnet' method.")
114
115 (defvoo nntp-via-telnet-switches '("-8")
116   "*Switches given to the telnet command `nntp-via-telnet-command'.")
117
118 (defvoo nntp-via-user-name nil
119   "*User name to log in on an intermediate host with.
120 This variable is used by the `nntp-open-via-telnet-and-telnet' method.")
121
122 (defvoo nntp-via-user-password nil
123   "*Password to use to log in on an intermediate host with.
124 This variable is used by the `nntp-open-via-telnet-and-telnet' method.")
125
126 (defvoo nntp-via-address nil
127   "*Address of an intermediate host to connect to.
128 This variable is used by the `nntp-open-via-rlogin-and-telnet' and
129 `nntp-open-via-telnet-and-telnet' methods.")
130
131 (defvoo nntp-via-envuser nil
132   "*Whether both telnet client and server support the ENVIRON option.
133 If non-nil, there will be no prompt for a login name.")
134
135 (defvoo nntp-via-shell-prompt "bash\\|\$ *\r?$\\|> *\r?"
136   "*Regular expression to match the shell prompt on an intermediate host.
137 This variable is used by the `nntp-open-via-telnet-and-telnet' method.")
138
139 (defvoo nntp-large-newsgroup 50
140   "*The number of the articles which indicates a large newsgroup.
141 If the number of the articles is greater than the value, verbose
142 messages will be shown to indicate the current status.")
143
144 (defvoo nntp-maximum-request 400
145   "*The maximum number of the requests sent to the NNTP server at one time.
146 If Emacs hangs up while retrieving headers, set the variable to a
147 lower value.")
148
149 (defvoo nntp-nov-is-evil nil
150   "*If non-nil, nntp will never attempt to use XOVER when talking to the server.")
151
152 (defvoo nntp-xover-commands '("XOVER" "XOVERVIEW")
153   "*List of strings that are used as commands to fetch NOV lines from a server.
154 The strings are tried in turn until a positive response is gotten.  If
155 none of the commands are successful, nntp will just grab headers one
156 by one.")
157
158 (defvoo nntp-nov-gap 5
159   "*Maximum allowed gap between two articles.
160 If the gap between two consecutive articles is bigger than this
161 variable, split the XOVER request into two requests.")
162
163 (defvoo nntp-prepare-server-hook nil
164   "*Hook run before a server is opened.
165 If can be used to set up a server remotely, for instance.  Say you
166 have an account at the machine \"other.machine\".  This machine has
167 access to an NNTP server that you can't access locally.  You could
168 then use this hook to rsh to the remote machine and start a proxy NNTP
169 server there that you can connect to.  See also
170 `nntp-open-connection-function'")
171
172 (defvoo nntp-warn-about-losing-connection t
173   "*If non-nil, beep when a server closes connection.")
174
175 (defvoo nntp-coding-system-for-read 'binary
176   "*Coding system to read from NNTP.")
177
178 (defvoo nntp-coding-system-for-write 'binary
179   "*Coding system to write to NNTP.")
180
181 (defcustom nntp-authinfo-file "~/.authinfo"
182   ".netrc-like file that holds nntp authinfo passwords."
183   :type
184   '(choice file
185            (repeat :tag "Entries"
186                    :menu-tag "Inline"
187                    (list :format "%v"
188                          :value ("" ("login" . "") ("password" . ""))
189                          (string :tag "Host")
190                          (checklist :inline t
191                                     (cons :format "%v"
192                                           (const :format "" "login")
193                                           (string :format "Login: %v"))
194                                     (cons :format "%v"
195                                           (const :format "" "password")
196                                           (string :format "Password: %v")))))))
197
198 \f
199
200 (defvoo nntp-connection-timeout nil
201   "*Number of seconds to wait before an nntp connection times out.
202 If this variable is nil, which is the default, no timers are set.
203 NOTE: This variable is never seen to work in Emacs 20 and XEmacs 21.")
204
205 (defvoo nntp-prepare-post-hook nil
206   "*Hook run just before posting an article.  It is supposed to be used
207 to insert Cancel-Lock headers.")
208
209 ;;; Internal variables.
210
211 (defvar nntp-record-commands nil
212   "*If non-nil, nntp will record all commands in the \"*nntp-log*\" buffer.")
213
214 (defvar nntp-have-messaged nil)
215
216 (defvar nntp-process-wait-for nil)
217 (defvar nntp-process-to-buffer nil)
218 (defvar nntp-process-callback nil)
219 (defvar nntp-process-decode nil)
220 (defvar nntp-process-start-point nil)
221 (defvar nntp-inside-change-function nil)
222 (defvoo nntp-last-command-time nil)
223 (defvoo nntp-last-command nil)
224 (defvoo nntp-authinfo-password nil)
225 (defvoo nntp-authinfo-user nil)
226
227 (defvar nntp-connection-list nil)
228
229 (defvoo nntp-server-type nil)
230 (defvoo nntp-connection-alist nil)
231 (defvoo nntp-status-string "")
232 (defconst nntp-version "nntp 5.0")
233 (defvoo nntp-inhibit-erase nil)
234 (defvoo nntp-inhibit-output nil)
235
236 (defvoo nntp-server-xover 'try)
237 (defvoo nntp-server-list-active-group 'try)
238
239 (defvar nntp-async-needs-kluge
240   (string-match "^GNU Emacs 20\\.3\\." (emacs-version))
241   "*When non-nil, nntp will poll asynchronous connections
242 once a second.  By default, this is turned on only for Emacs
243 20.3, which has a bug that breaks nntp's normal method of
244 noticing asynchronous data.")
245
246 (defvar nntp-async-timer nil)
247 (defvar nntp-async-process-list nil)
248
249 (eval-and-compile
250   (autoload 'mail-source-read-passwd "mail-source")
251   (autoload 'open-ssl-stream "ssl"))
252
253 \f
254
255 ;;; Internal functions.
256
257 (defsubst nntp-send-string (process string)
258   "Send STRING to PROCESS."
259   ;; We need to store the time to provide timeouts, and
260   ;; to store the command so the we can replay the command
261   ;; if the server gives us an AUTHINFO challenge.
262   (setq nntp-last-command-time (current-time)
263         nntp-last-command string)
264   (when nntp-record-commands
265     (nntp-record-command string))
266   (process-send-string process (concat string nntp-end-of-line)))
267
268 (defun nntp-record-command (string)
269   "Record the command STRING."
270   (save-excursion
271     (set-buffer (get-buffer-create "*nntp-log*"))
272     (goto-char (point-max))
273     (let ((time (current-time)))
274       (insert (format-time-string "%Y%m%dT%H%M%S" time)
275               "." (format "%03d" (/ (nth 2 time) 1000))
276               " " nntp-address " " string "\n"))))
277
278 (defsubst nntp-wait-for (process wait-for buffer &optional decode discard)
279   "Wait for WAIT-FOR to arrive from PROCESS."
280   (save-excursion
281     (set-buffer (process-buffer process))
282     (goto-char (point-min))
283     (while (and (or (not (memq (char-after (point)) '(?2 ?3 ?4 ?5)))
284                     (looking-at "480"))
285                 (memq (process-status process) '(open run)))
286       (when (looking-at "480")
287         (nntp-handle-authinfo process))
288       (when (looking-at "^.*\n")
289         (delete-region (point) (progn (forward-line 1) (point))))
290       (nntp-accept-process-output process)
291       (goto-char (point-min)))
292     (prog1
293         (cond
294          ((looking-at "[45]")
295           (progn
296             (nntp-snarf-error-message)
297             nil))
298          ((not (memq (process-status process) '(open run)))
299           (nnheader-report 'nntp "Server closed connection"))
300          (t
301           (goto-char (point-max))
302           (let ((limit (point-min))
303                 response)
304             (while (not (re-search-backward wait-for limit t))
305               (nntp-accept-process-output process)
306               ;; We assume that whatever we wait for is less than 1000
307               ;; characters long.
308               (setq limit (max (- (point-max) 1000) (point-min)))
309               (goto-char (point-max)))
310             (setq response (match-string 0))
311             (with-current-buffer nntp-server-buffer
312               (setq nntp-process-response response)))
313           (nntp-decode-text (not decode))
314           (unless discard
315             (save-excursion
316               (set-buffer buffer)
317               (goto-char (point-max))
318               (insert-buffer-substring (process-buffer process))
319               ;; Nix out "nntp reading...." message.
320               (when nntp-have-messaged
321                 (setq nntp-have-messaged nil)
322                 (nnheader-message 5 ""))))
323           t))
324       (unless discard
325         (erase-buffer)))))
326
327 (defun nntp-kill-buffer (buffer)
328   (when (buffer-name buffer)
329     (kill-buffer buffer)
330     (nnheader-init-server-buffer)))
331
332 (defsubst nntp-find-connection (buffer)
333   "Find the connection delivering to BUFFER."
334   (let ((alist nntp-connection-alist)
335         (buffer (if (stringp buffer) (get-buffer buffer) buffer))
336         process entry)
337     (while (and alist (setq entry (pop alist)))
338       (when (eq buffer (cadr entry))
339         (setq process (car entry)
340               alist nil)))
341     (when process
342       (if (memq (process-status process) '(open run))
343           process
344         (nntp-kill-buffer (process-buffer process))
345         (setq nntp-connection-alist (delq entry nntp-connection-alist))
346         nil))))
347
348 (defsubst nntp-find-connection-entry (buffer)
349   "Return the entry for the connection to BUFFER."
350   (assq (nntp-find-connection buffer) nntp-connection-alist))
351
352 (defun nntp-find-connection-buffer (buffer)
353   "Return the process connection buffer tied to BUFFER."
354   (let ((process (nntp-find-connection buffer)))
355     (when process
356       (process-buffer process))))
357
358 (defsubst nntp-retrieve-data (command address port buffer
359                                       &optional wait-for callback decode)
360   "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS."
361   (let ((process (or (nntp-find-connection buffer)
362                      (nntp-open-connection buffer))))
363     (if (not process)
364         (nnheader-report 'nntp "Couldn't open connection to %s" address)
365       (unless (or nntp-inhibit-erase nnheader-callback-function)
366         (save-excursion
367           (set-buffer (process-buffer process))
368           (erase-buffer)))
369       (condition-case err
370           (progn
371             (when command
372               (nntp-send-string process command))
373             (cond
374              ((eq callback 'ignore)
375               t)
376              ((and callback wait-for)
377               (nntp-async-wait process wait-for buffer decode callback)
378               t)
379              (wait-for
380               (nntp-wait-for process wait-for buffer decode))
381              (t t)))
382         (error
383          (nnheader-report 'nntp "Couldn't open connection to %s: %s"
384                           address err))
385         (quit
386          (message "Quit retrieving data from nntp")
387          (signal 'quit nil)
388          nil)))))
389
390 (defsubst nntp-send-command (wait-for &rest strings)
391   "Send STRINGS to server and wait until WAIT-FOR returns."
392   (when (and (not nnheader-callback-function)
393              (not nntp-inhibit-output))
394     (save-excursion
395       (set-buffer nntp-server-buffer)
396       (erase-buffer)))
397   (let* ((command (mapconcat 'identity strings " "))
398          (process (nntp-find-connection nntp-server-buffer))
399          (buffer (and process (process-buffer process)))
400          (pos (and buffer (with-current-buffer buffer (point)))))
401     (if process
402         (prog1
403             (nntp-retrieve-data command
404                                 nntp-address nntp-port-number
405                                 nntp-server-buffer
406                                 wait-for nnheader-callback-function)
407           ;; If nothing to wait for, still remove possibly echo'ed commands
408           (unless wait-for
409             (nntp-accept-response)
410             (save-excursion
411               (set-buffer buffer)
412               (goto-char pos)
413               (if (looking-at (regexp-quote command))
414                   (delete-region pos (progn (forward-line 1)
415                                             (gnus-point-at-bol))))
416               )))
417       (nnheader-report 'nntp "Couldn't open connection to %s."
418                        nntp-address))))
419
420 (defun nntp-send-command-nodelete (wait-for &rest strings)
421   "Send STRINGS to server and wait until WAIT-FOR returns."
422   (let* ((command (mapconcat 'identity strings " "))
423          (process (nntp-find-connection nntp-server-buffer))
424          (buffer (and process (process-buffer process)))
425          (pos (and buffer (with-current-buffer buffer (point)))))
426     (if process
427         (prog1
428             (nntp-retrieve-data command
429                                 nntp-address nntp-port-number
430                                 nntp-server-buffer
431                                 wait-for nnheader-callback-function)
432           ;; If nothing to wait for, still remove possibly echo'ed commands
433           (unless wait-for
434             (nntp-accept-response)
435             (save-excursion
436               (set-buffer buffer)
437               (goto-char pos)
438               (if (looking-at (regexp-quote command))
439                   (delete-region pos (progn (forward-line 1)
440                                             (gnus-point-at-bol))))
441               )))
442       (nnheader-report 'nntp "Couldn't open connection to %s."
443                        nntp-address))))
444
445 (defun nntp-send-command-and-decode (wait-for &rest strings)
446   "Send STRINGS to server and wait until WAIT-FOR returns."
447   (when (and (not nnheader-callback-function)
448              (not nntp-inhibit-output))
449     (save-excursion
450       (set-buffer nntp-server-buffer)
451       (erase-buffer)))
452   (let* ((command (mapconcat 'identity strings " "))
453          (process (nntp-find-connection nntp-server-buffer))
454          (buffer (and process (process-buffer process)))
455          (pos (and buffer (with-current-buffer buffer (point)))))
456     (if process
457         (prog1
458             (nntp-retrieve-data command
459                                 nntp-address nntp-port-number
460                                 nntp-server-buffer
461                                 wait-for nnheader-callback-function t)
462           ;; If nothing to wait for, still remove possibly echo'ed commands
463           (unless wait-for
464             (nntp-accept-response)
465             (save-excursion
466           (set-buffer buffer)
467           (goto-char pos)
468           (if (looking-at (regexp-quote command))
469               (delete-region pos (progn (forward-line 1) (gnus-point-at-bol))))
470           )))
471       (nnheader-report 'nntp "Couldn't open connection to %s."
472                        nntp-address))))
473
474
475 (defun nntp-send-buffer (wait-for)
476   "Send the current buffer to server and wait until WAIT-FOR returns."
477   (when (and (not nnheader-callback-function)
478              (not nntp-inhibit-output))
479     (save-excursion
480       (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
481       (erase-buffer)))
482   (nntp-encode-text)
483   (mm-with-unibyte-current-buffer
484     ;; Some encoded unicode text contains character 0x80-0x9f e.g. Euro.
485     (process-send-region (nntp-find-connection nntp-server-buffer)
486                          (point-min) (point-max)))
487   (nntp-retrieve-data
488    nil nntp-address nntp-port-number nntp-server-buffer
489    wait-for nnheader-callback-function))
490
491 \f
492
493 ;;; Interface functions.
494
495 (nnoo-define-basics nntp)
496
497 (defsubst nntp-next-result-arrived-p ()
498   (cond
499    ;; A result that starts with a 2xx code is terminated by
500    ;; a line with only a "." on it.
501    ((eq (char-after) ?2)
502     (if (re-search-forward "\n\\.\r?\n" nil t)
503         t
504       nil))
505    ;; A result that starts with a 3xx or 4xx code is terminated
506    ;; by a newline.
507    ((looking-at "[34]")
508     (if (search-forward "\n" nil t)
509         t
510       nil))
511    ;; No result here.
512    (t
513     nil)))
514
515 (deffoo nntp-retrieve-headers (articles &optional group server fetch-old)
516   "Retrieve the headers of ARTICLES."
517   (nntp-possibly-change-group group server)
518   (save-excursion
519     (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
520     (erase-buffer)
521     (if (and (not gnus-nov-is-evil)
522              (not nntp-nov-is-evil)
523              (nntp-retrieve-headers-with-xover articles fetch-old))
524         ;; We successfully retrieved the headers via XOVER.
525         'nov
526       ;; XOVER didn't work, so we do it the hard, slow and inefficient
527       ;; way.
528       (let ((number (length articles))
529             (count 0)
530             (received 0)
531             (last-point (point-min))
532             (buf (nntp-find-connection-buffer nntp-server-buffer))
533             (nntp-inhibit-erase t)
534             article)
535         ;; Send HEAD commands.
536         (while (setq article (pop articles))
537           (nntp-send-command
538            nil
539            "HEAD" (if (numberp article)
540                       (int-to-string article)
541                     ;; `articles' is either a list of article numbers
542                     ;; or a list of article IDs.
543                     article))
544           (incf count)
545           ;; Every 400 requests we have to read the stream in
546           ;; order to avoid deadlocks.
547           (when (or (null articles)     ;All requests have been sent.
548                     (zerop (% count nntp-maximum-request)))
549             (nntp-accept-response)
550             (while (progn
551                      (set-buffer buf)
552                      (goto-char last-point)
553                      ;; Count replies.
554                      (while (nntp-next-result-arrived-p)
555                        (setq last-point (point))
556                        (incf received))
557                      (< received count))
558               ;; If number of headers is greater than 100, give
559               ;;  informative messages.
560               (and (numberp nntp-large-newsgroup)
561                    (> number nntp-large-newsgroup)
562                    (zerop (% received 20))
563                    (nnheader-message 6 "NNTP: Receiving headers... %d%%"
564                                      (/ (* received 100) number)))
565               (nntp-accept-response))))
566         (and (numberp nntp-large-newsgroup)
567              (> number nntp-large-newsgroup)
568              (nnheader-message 6 "NNTP: Receiving headers...done"))
569
570         ;; Now all of replies are received.  Fold continuation lines.
571         (nnheader-fold-continuation-lines)
572         ;; Remove all "\r"'s.
573         (nnheader-strip-cr)
574         (copy-to-buffer nntp-server-buffer (point-min) (point-max))
575         'headers))))
576
577 (deffoo nntp-retrieve-groups (groups &optional server)
578   "Retrieve group info on GROUPS."
579   (nntp-possibly-change-group nil server)
580   (when (nntp-find-connection-buffer nntp-server-buffer)
581     (catch 'done
582       (save-excursion
583         ;; Erase nntp-server-buffer before nntp-inhibit-erase.
584         (set-buffer nntp-server-buffer)
585         (erase-buffer)
586         (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
587         ;; The first time this is run, this variable is `try'.  So we
588         ;; try.
589         (when (eq nntp-server-list-active-group 'try)
590           (nntp-try-list-active (car groups)))
591         (erase-buffer)
592         (let ((count 0)
593               (received 0)
594               (last-point (point-min))
595               (nntp-inhibit-erase t)
596               (buf (nntp-find-connection-buffer nntp-server-buffer))
597               (command (if nntp-server-list-active-group
598                            "LIST ACTIVE" "GROUP")))
599           (while groups
600             ;; Timeout may have killed the buffer.
601             (unless (gnus-buffer-live-p buf)
602               (nnheader-report 'nntp "Connection to %s is closed." server)
603               (throw 'done nil))
604             ;; Send the command to the server.
605             (nntp-send-command nil command (pop groups))
606             (incf count)
607             ;; Every 400 requests we have to read the stream in
608             ;; order to avoid deadlocks.
609             (when (or (null groups)     ;All requests have been sent.
610                       (zerop (% count nntp-maximum-request)))
611               (nntp-accept-response)
612               (while (and (gnus-buffer-live-p buf)
613                           (progn
614                             ;; Search `blue moon' in this file for the
615                             ;; reason why set-buffer here.
616                             (set-buffer buf)
617                             (goto-char last-point)
618                             ;; Count replies.
619                             (while (re-search-forward "^[0-9]" nil t)
620                               (incf received))
621                             (setq last-point (point))
622                             (< received count)))
623                 (nntp-accept-response))))
624
625           ;; Wait for the reply from the final command.
626           (unless (gnus-buffer-live-p buf)
627             (nnheader-report 'nntp "Connection to %s is closed." server)
628             (throw 'done nil))
629           (set-buffer buf)
630           (goto-char (point-max))
631           (re-search-backward "^[0-9]" nil t)
632           (when (looking-at "^[23]")
633             (while (and (gnus-buffer-live-p buf)
634                         (progn
635                           (set-buffer buf)
636                           (goto-char (point-max))
637                           (if (not nntp-server-list-active-group)
638                               (not (re-search-backward "\r?\n" (- (point) 3) t))
639                             (not (re-search-backward "^\\.\r?\n"
640                                                      (- (point) 4) t)))))
641               (nntp-accept-response)))
642
643           ;; Now all replies are received.  We remove CRs.
644           (unless (gnus-buffer-live-p buf)
645             (nnheader-report 'nntp "Connection to %s is closed." server)
646             (throw 'done nil))
647           (set-buffer buf)
648           (goto-char (point-min))
649           (while (search-forward "\r" nil t)
650             (replace-match "" t t))
651
652           (if (not nntp-server-list-active-group)
653               (progn
654                 (copy-to-buffer nntp-server-buffer (point-min) (point-max))
655                 'group)
656             ;; We have read active entries, so we just delete the
657             ;; superfluous gunk.
658             (goto-char (point-min))
659             (while (re-search-forward "^[.2-5]" nil t)
660               (delete-region (match-beginning 0)
661                              (progn (forward-line 1) (point))))
662             (copy-to-buffer nntp-server-buffer (point-min) (point-max))
663             'active))))))
664
665 (deffoo nntp-retrieve-articles (articles &optional group server)
666   (nntp-possibly-change-group group server)
667   (save-excursion
668     (let ((number (length articles))
669           (count 0)
670           (received 0)
671           (last-point (point-min))
672           (buf (nntp-find-connection-buffer nntp-server-buffer))
673           (nntp-inhibit-erase t)
674           (map (apply 'vector articles))
675           (point 1)
676           article)
677       (set-buffer buf)
678       (erase-buffer)
679       ;; Send ARTICLE command.
680       (while (setq article (pop articles))
681         (nntp-send-command
682          nil
683          "ARTICLE" (if (numberp article)
684                        (int-to-string article)
685                      ;; `articles' is either a list of article numbers
686                      ;; or a list of article IDs.
687                      article))
688         (incf count)
689         ;; Every 400 requests we have to read the stream in
690         ;; order to avoid deadlocks.
691         (when (or (null articles)       ;All requests have been sent.
692                   (zerop (% count nntp-maximum-request)))
693           (nntp-accept-response)
694           (while (progn
695                    (set-buffer buf)
696                    (goto-char last-point)
697                    ;; Count replies.
698                    (while (nntp-next-result-arrived-p)
699                      (aset map received (cons (aref map received) (point)))
700                      (setq last-point (point))
701                      (incf received))
702                    (< received count))
703             ;; If number of headers is greater than 100, give
704             ;;  informative messages.
705             (and (numberp nntp-large-newsgroup)
706                  (> number nntp-large-newsgroup)
707                  (zerop (% received 20))
708                  (nnheader-message 6 "NNTP: Receiving articles... %d%%"
709                                    (/ (* received 100) number)))
710             (nntp-accept-response))))
711       (and (numberp nntp-large-newsgroup)
712            (> number nntp-large-newsgroup)
713            (nnheader-message 6 "NNTP: Receiving articles...done"))
714
715       ;; Now we have all the responses.  We go through the results,
716       ;; wash it and copy it over to the server buffer.
717       (set-buffer nntp-server-buffer)
718       (erase-buffer)
719       (setq last-point (point-min))
720       (mapcar
721        (lambda (entry)
722          (narrow-to-region
723           (setq point (goto-char (point-max)))
724           (progn
725             (insert-buffer-substring buf last-point (cdr entry))
726             (point-max)))
727          (setq last-point (cdr entry))
728          (nntp-decode-text)
729          (widen)
730          (cons (car entry) point))
731        map))))
732
733 (defun nntp-try-list-active (group)
734   (nntp-list-active-group group)
735   (save-excursion
736     (set-buffer nntp-server-buffer)
737     (goto-char (point-min))
738     (cond ((or (eobp)
739                (looking-at "5[0-9]+"))
740            (setq nntp-server-list-active-group nil))
741           (t
742            (setq nntp-server-list-active-group t)))))
743
744 (deffoo nntp-list-active-group (group &optional server)
745   "Return the active info on GROUP (which can be a regexp)."
746   (nntp-possibly-change-group nil server)
747   (nntp-send-command "^\\.*\r?\n" "LIST ACTIVE" group))
748
749 (deffoo nntp-request-group-articles (group &optional server)
750   "Return the list of existing articles in GROUP."
751   (nntp-possibly-change-group nil server)
752   (nntp-send-command "^\\.*\r?\n" "LISTGROUP" group))
753
754 (deffoo nntp-request-article (article &optional group server buffer command)
755   (nntp-possibly-change-group group server)
756   (when (nntp-send-command-and-decode
757          "\r?\n\\.\r?\n" "ARTICLE"
758          (if (numberp article) (int-to-string article) article))
759     (if (and buffer
760              (not (equal buffer nntp-server-buffer)))
761         (save-excursion
762           (set-buffer nntp-server-buffer)
763           (copy-to-buffer buffer (point-min) (point-max))
764           (nntp-find-group-and-number group))
765       (nntp-find-group-and-number group))))
766
767 (deffoo nntp-request-head (article &optional group server)
768   (nntp-possibly-change-group group server)
769   (when (nntp-send-command
770          "\r?\n\\.\r?\n" "HEAD"
771          (if (numberp article) (int-to-string article) article))
772     (prog1
773         (nntp-find-group-and-number group)
774       (nntp-decode-text))))
775
776 (deffoo nntp-request-body (article &optional group server)
777   (nntp-possibly-change-group group server)
778   (nntp-send-command-and-decode
779    "\r?\n\\.\r?\n" "BODY"
780    (if (numberp article) (int-to-string article) article)))
781
782 (deffoo nntp-request-group (group &optional server dont-check)
783   (nntp-possibly-change-group nil server)
784   (when (nntp-send-command "^[245].*\n" "GROUP" group)
785     (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
786       (setcar (cddr entry) group))))
787
788 (deffoo nntp-close-group (group &optional server)
789   t)
790
791 (deffoo nntp-server-opened (&optional server)
792   "Say whether a connection to SERVER has been opened."
793   (and (nnoo-current-server-p 'nntp server)
794        nntp-server-buffer
795        (gnus-buffer-live-p nntp-server-buffer)
796        (nntp-find-connection nntp-server-buffer)))
797
798 (deffoo nntp-open-server (server &optional defs connectionless)
799   (nnheader-init-server-buffer)
800   (if (nntp-server-opened server)
801       t
802     (when (or (stringp (car defs))
803               (numberp (car defs)))
804       (setq defs (cons (list 'nntp-port-number (car defs)) (cdr defs))))
805     (unless (assq 'nntp-address defs)
806       (setq defs (append defs (list (list 'nntp-address server)))))
807     (nnoo-change-server 'nntp server defs)
808     (unless connectionless
809       (or (nntp-find-connection nntp-server-buffer)
810           (nntp-open-connection nntp-server-buffer)))))
811
812 (deffoo nntp-close-server (&optional server)
813   (nntp-possibly-change-group nil server t)
814   (let ((process (nntp-find-connection nntp-server-buffer)))
815     (while process
816       (when (memq (process-status process) '(open run))
817         (ignore-errors
818           (nntp-send-string process "QUIT")
819           (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
820             ;; Ok, this is evil, but when using telnet and stuff
821             ;; as the connection method, it's important that the
822             ;; QUIT command actually is sent out before we kill
823             ;; the process.
824             (sleep-for 1))))
825       (nntp-kill-buffer (process-buffer process))
826       (setq process (car (pop nntp-connection-alist))))
827     (nnoo-close-server 'nntp)))
828
829 (deffoo nntp-request-close ()
830   (let (process)
831     (while (setq process (pop nntp-connection-list))
832       (when (memq (process-status process) '(open run))
833         (ignore-errors
834           (nntp-send-string process "QUIT")
835           (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
836             ;; Ok, this is evil, but when using telnet and stuff
837             ;; as the connection method, it's important that the
838             ;; QUIT command actually is sent out before we kill
839             ;; the process.
840             (sleep-for 1))))
841       (nntp-kill-buffer (process-buffer process)))))
842
843 (deffoo nntp-request-list (&optional server)
844   (nntp-possibly-change-group nil server)
845   (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST"))
846
847 (deffoo nntp-request-list-newsgroups (&optional server)
848   (nntp-possibly-change-group nil server)
849   (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS"))
850
851 (deffoo nntp-request-newgroups (date &optional server)
852   (nntp-possibly-change-group nil server)
853   (save-excursion
854     (set-buffer nntp-server-buffer)
855     (let* ((time (date-to-time date))
856            (ls (- (cadr time) (nth 8 (decode-time time)))))
857       (cond ((< ls 0)
858              (setcar time (1- (car time)))
859              (setcar (cdr time) (+ ls 65536)))
860             ((>= ls 65536)
861              (setcar time (1+ (car time)))
862              (setcar (cdr time) (- ls 65536)))
863             (t
864              (setcar (cdr time) ls)))
865       (prog1
866           (nntp-send-command
867            "^\\.\r?\n" "NEWGROUPS"
868            (format-time-string "%y%m%d %H%M%S" time)
869            "GMT")
870         (nntp-decode-text)))))
871
872 (deffoo nntp-request-post (&optional server)
873   (nntp-possibly-change-group nil server)
874   (when (nntp-send-command "^[23].*\r?\n" "POST")
875     (let ((response (with-current-buffer nntp-server-buffer
876                       nntp-process-response))
877           server-id)
878       (when (and response
879                  (string-match "^[23].*\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
880                                response))
881         (setq server-id (match-string 1 response))
882         (narrow-to-region (goto-char (point-min))
883                           (if (search-forward "\n\n" nil t)
884                               (1- (point))
885                             (point-max)))
886         (unless (mail-fetch-field "Message-ID")
887           (goto-char (point-min))
888           (insert "Message-ID: " server-id "\n"))
889         (widen))
890       (run-hooks 'nntp-prepare-post-hook)
891       (nntp-send-buffer "^[23].*\n"))))
892
893 (deffoo nntp-request-type (group article)
894   'news)
895
896 (deffoo nntp-asynchronous-p ()
897   t)
898
899 ;;; Hooky functions.
900
901 (defun nntp-send-mode-reader ()
902   "Send the MODE READER command to the nntp server.
903 This function is supposed to be called from `nntp-server-opened-hook'.
904 It will make innd servers spawn an nnrpd process to allow actual article
905 reading."
906   (nntp-send-command "^.*\n" "MODE READER"))
907
908 (defun nntp-send-authinfo (&optional send-if-force)
909   "Send the AUTHINFO to the nntp server.
910 It will look in the \"~/.authinfo\" file for matching entries.  If
911 nothing suitable is found there, it will prompt for a user name
912 and a password.
913
914 If SEND-IF-FORCE, only send authinfo to the server if the
915 .authinfo file has the FORCE token."
916   (let* ((list (gnus-parse-netrc nntp-authinfo-file))
917          (alist (gnus-netrc-machine list nntp-address "nntp"))
918          (force (gnus-netrc-get alist "force"))
919          (user (or (gnus-netrc-get alist "login") nntp-authinfo-user))
920          (passwd (gnus-netrc-get alist "password")))
921     (when (or (not send-if-force)
922               force)
923       (unless user
924         (setq user (read-string (format "NNTP (%s) user name: " nntp-address))
925               nntp-authinfo-user user))
926       (unless (member user '(nil ""))
927         (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user)
928         (when t                         ;???Should check if AUTHINFO succeeded
929           (nntp-send-command
930            "^2.*\r?\n" "AUTHINFO PASS"
931            (or passwd
932                nntp-authinfo-password
933                (setq nntp-authinfo-password
934                      (mail-source-read-passwd
935                       (format "NNTP (%s@%s) password: "
936                               user nntp-address))))))))))
937
938 (defun nntp-send-nosy-authinfo ()
939   "Send the AUTHINFO to the nntp server."
940   (let ((user (read-string (format "NNTP (%s) user name: " nntp-address))))
941     (unless (member user '(nil ""))
942       (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user)
943       (when t                           ;???Should check if AUTHINFO succeeded
944         (nntp-send-command "^2.*\r?\n" "AUTHINFO PASS"
945                            (mail-source-read-passwd "NNTP (%s@%s) password: "
946                                                     user nntp-address))))))
947
948 (defun nntp-send-authinfo-from-file ()
949   "Send the AUTHINFO to the nntp server.
950
951 The authinfo login name is taken from the user's login name and the
952 password contained in '~/.nntp-authinfo'."
953   (when (file-exists-p "~/.nntp-authinfo")
954     (with-temp-buffer
955       (insert-file-contents "~/.nntp-authinfo")
956       (goto-char (point-min))
957       (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" (user-login-name))
958       (nntp-send-command
959        "^2.*\r?\n" "AUTHINFO PASS"
960        (buffer-substring (point) (progn (end-of-line) (point)))))))
961
962 ;;; Internal functions.
963
964 (defun nntp-handle-authinfo (process)
965   "Take care of an authinfo response from the server."
966   (let ((last nntp-last-command))
967     (funcall nntp-authinfo-function)
968     ;; We have to re-send the function that was interrupted by
969     ;; the authinfo request.
970     (save-excursion
971       (set-buffer nntp-server-buffer)
972       (erase-buffer))
973     (nntp-send-string process last)))
974
975 (defun nntp-make-process-buffer (buffer)
976   "Create a new, fresh buffer usable for nntp process connections."
977   (save-excursion
978     (set-buffer
979      (generate-new-buffer
980       (format " *server %s %s %s*"
981               nntp-address nntp-port-number
982               (gnus-buffer-exists-p buffer))))
983     (mm-enable-multibyte)
984     (set (make-local-variable 'after-change-functions) nil)
985     (set (make-local-variable 'nntp-process-wait-for) nil)
986     (set (make-local-variable 'nntp-process-callback) nil)
987     (set (make-local-variable 'nntp-process-to-buffer) nil)
988     (set (make-local-variable 'nntp-process-start-point) nil)
989     (set (make-local-variable 'nntp-process-decode) nil)
990     (current-buffer)))
991
992 (defun nntp-open-connection (buffer)
993   "Open a connection to PORT on ADDRESS delivering output to BUFFER."
994   (run-hooks 'nntp-prepare-server-hook)
995   (let* ((pbuffer (nntp-make-process-buffer buffer))
996          (timer
997           (and nntp-connection-timeout
998                (nnheader-run-at-time
999                 nntp-connection-timeout nil
1000                 `(lambda ()
1001                    (nntp-kill-buffer ,pbuffer)))))
1002          (process
1003           (condition-case ()
1004               (let ((coding-system-for-read nntp-coding-system-for-read)
1005                     (coding-system-for-write nntp-coding-system-for-write))
1006                 (funcall nntp-open-connection-function pbuffer))
1007             (error nil)
1008             (quit
1009              (message "Quit opening connection")
1010              (nntp-kill-buffer pbuffer)
1011              (signal 'quit nil)
1012              nil))))
1013     (when timer
1014       (nnheader-cancel-timer timer))
1015     (unless process
1016       (nntp-kill-buffer pbuffer))
1017     (when (and (buffer-name pbuffer)
1018                process)
1019       (process-kill-without-query process)
1020       (if (and (nntp-wait-for process "^2.*\n" buffer nil t)
1021                (memq (process-status process) '(open run)))
1022           (prog1
1023               (caar (push (list process buffer nil) nntp-connection-alist))
1024             (push process nntp-connection-list)
1025             (save-excursion
1026               (set-buffer pbuffer)
1027               (nntp-read-server-type)
1028               (erase-buffer)
1029               (set-buffer nntp-server-buffer)
1030               (let ((nnheader-callback-function nil))
1031                 (run-hooks 'nntp-server-opened-hook)
1032                 (nntp-send-authinfo t))))
1033         (nntp-kill-buffer (process-buffer process))
1034         nil))))
1035
1036 (defun nntp-open-network-stream (buffer)
1037   (open-network-stream "nntpd" buffer nntp-address nntp-port-number))
1038
1039 (defun nntp-open-ssl-stream (buffer)
1040   (let ((proc (open-ssl-stream "nntpd" buffer nntp-address nntp-port-number)))
1041     (save-excursion
1042       (set-buffer buffer)
1043       (nntp-wait-for-string "^\r*20[01]")
1044       (beginning-of-line)
1045       (delete-region (point-min) (point))
1046       proc)))
1047
1048 (defun nntp-read-server-type ()
1049   "Find out what the name of the server we have connected to is."
1050   ;; Wait for the status string to arrive.
1051   (setq nntp-server-type (buffer-string))
1052   (let ((alist nntp-server-action-alist)
1053         (case-fold-search t)
1054         entry)
1055     ;; Run server-specific commands.
1056     (while alist
1057       (setq entry (pop alist))
1058       (when (string-match (car entry) nntp-server-type)
1059         (if (and (listp (cadr entry))
1060                  (not (eq 'lambda (caadr entry))))
1061             (eval (cadr entry))
1062           (funcall (cadr entry)))))))
1063
1064 (defun nntp-async-wait (process wait-for buffer decode callback)
1065   (save-excursion
1066     (set-buffer (process-buffer process))
1067     (unless nntp-inside-change-function
1068       (erase-buffer))
1069     (setq nntp-process-wait-for wait-for
1070           nntp-process-to-buffer buffer
1071           nntp-process-decode decode
1072           nntp-process-callback callback
1073           nntp-process-start-point (point-max))
1074     (setq after-change-functions '(nntp-after-change-function))
1075     (if nntp-async-needs-kluge
1076         (nntp-async-kluge process))))
1077
1078 (defun nntp-async-kluge (process)
1079   ;; emacs 20.3 bug: process output with encoding 'binary
1080   ;; doesn't trigger after-change-functions.
1081   (unless nntp-async-timer
1082     (setq nntp-async-timer
1083           (nnheader-run-at-time 1 1 'nntp-async-timer-handler)))
1084   (add-to-list 'nntp-async-process-list process))
1085
1086 (defun nntp-async-timer-handler ()
1087   (mapcar
1088    (lambda (proc)
1089      (if (memq (process-status proc) '(open run))
1090          (nntp-async-trigger proc)
1091        (nntp-async-stop proc)))
1092    nntp-async-process-list))
1093
1094 (defun nntp-async-stop (proc)
1095   (setq nntp-async-process-list (delq proc nntp-async-process-list))
1096   (when (and nntp-async-timer (not nntp-async-process-list))
1097     (nnheader-cancel-timer nntp-async-timer)
1098     (setq nntp-async-timer nil)))
1099
1100 (defun nntp-after-change-function (beg end len)
1101   (unwind-protect
1102       ;; we only care about insertions at eob
1103       (when (and (eq 0 len) (eq (point-max) end))
1104         (save-match-data
1105           (let ((proc (get-buffer-process (current-buffer))))
1106             (when proc
1107               (nntp-async-trigger proc)))))
1108     ;; any throw from after-change-functions will leave it
1109     ;; set to nil.  so we reset it here, if necessary.
1110     (when quit-flag
1111       (setq after-change-functions '(nntp-after-change-function)))))
1112
1113 (defun nntp-async-trigger (process)
1114   (save-excursion
1115     (set-buffer (process-buffer process))
1116     (when nntp-process-callback
1117       ;; do we have an error message?
1118       (goto-char nntp-process-start-point)
1119       (if (memq (following-char) '(?4 ?5))
1120           ;; wants credentials?
1121           (if (looking-at "480")
1122               (nntp-handle-authinfo process)
1123             ;; report error message.
1124             (nntp-snarf-error-message)
1125             (nntp-do-callback nil))
1126
1127         ;; got what we expect?
1128         (goto-char (point-max))
1129         (when (re-search-backward
1130                nntp-process-wait-for nntp-process-start-point t)
1131           (let ((response (match-string 0)))
1132             (with-current-buffer nntp-server-buffer
1133               (setq nntp-process-response response)))
1134           (nntp-async-stop process)
1135           ;; convert it.
1136           (when (gnus-buffer-exists-p nntp-process-to-buffer)
1137             (let ((buf (current-buffer))
1138                   (start nntp-process-start-point)
1139                   (decode nntp-process-decode))
1140               (save-excursion
1141                 (set-buffer nntp-process-to-buffer)
1142                 (goto-char (point-max))
1143                 (save-restriction
1144                   (narrow-to-region (point) (point))
1145                   (insert-buffer-substring buf start)
1146                   (when decode
1147                     (nntp-decode-text))))))
1148           ;; report it.
1149           (goto-char (point-max))
1150           (nntp-do-callback
1151            (buffer-name (get-buffer nntp-process-to-buffer))))))))
1152
1153 (defun nntp-do-callback (arg)
1154   (let ((callback nntp-process-callback)
1155         (nntp-inside-change-function t))
1156     (setq nntp-process-callback nil)
1157     (funcall callback arg)))
1158
1159 (defun nntp-snarf-error-message ()
1160   "Save the error message in the current buffer."
1161   (let ((message (buffer-string)))
1162     (while (string-match "[\r\n]+" message)
1163       (setq message (replace-match " " t t message)))
1164     (nnheader-report 'nntp message)
1165     message))
1166
1167 (defun nntp-accept-process-output (process &optional timeout)
1168   "Wait for output from PROCESS and message some dots."
1169   (save-excursion
1170     (set-buffer (or (nntp-find-connection-buffer nntp-server-buffer)
1171                     nntp-server-buffer))
1172     (let ((len (/ (point-max) 1024))
1173           message-log-max)
1174       (unless (< len 10)
1175         (setq nntp-have-messaged t)
1176         (nnheader-message 7 "nntp read: %dk" len)))
1177     (accept-process-output process (or timeout 1))))
1178
1179 (defun nntp-accept-response ()
1180   "Wait for output from the process that outputs to BUFFER."
1181   (nntp-accept-process-output (nntp-find-connection nntp-server-buffer)))
1182
1183 (defun nntp-possibly-change-group (group server &optional connectionless)
1184   (let ((nnheader-callback-function nil))
1185     (when server
1186       (or (nntp-server-opened server)
1187           (nntp-open-server server nil connectionless)))
1188
1189     (unless connectionless
1190       (or (nntp-find-connection nntp-server-buffer)
1191           (nntp-open-connection nntp-server-buffer))))
1192
1193   (when group
1194     (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
1195       (when (not (equal group (caddr entry)))
1196         (save-excursion
1197           (set-buffer (process-buffer (car entry)))
1198           (erase-buffer)
1199           (nntp-send-command "^[245].*\n" "GROUP" group)
1200           (setcar (cddr entry) group)
1201           (erase-buffer)
1202           (save-excursion
1203             (set-buffer nntp-server-buffer)
1204             (erase-buffer)))))))
1205
1206 (defun nntp-decode-text (&optional cr-only)
1207   "Decode the text in the current buffer."
1208   (goto-char (point-min))
1209   (while (search-forward "\r" nil t)
1210     (delete-char -1))
1211   (unless cr-only
1212     ;; Remove trailing ".\n" end-of-transfer marker.
1213     (goto-char (point-max))
1214     (forward-line -1)
1215     (when (looking-at ".\n")
1216       (delete-char 2))
1217     ;; Delete status line.
1218     (goto-char (point-min))
1219     (while (looking-at "[1-5][0-9][0-9] .*\n")
1220       ;; For some unknown reason, there is more than one status line.
1221       (delete-region (point) (progn (forward-line 1) (point))))
1222     ;; Remove "." -> ".." encoding.
1223     (while (search-forward "\n.." nil t)
1224       (delete-char -1))))
1225
1226 (defun nntp-encode-text ()
1227   "Encode the text in the current buffer."
1228   (save-excursion
1229     ;; Replace "." at beginning of line with "..".
1230     (goto-char (point-min))
1231     (while (re-search-forward "^\\." nil t)
1232       (insert "."))
1233     (goto-char (point-max))
1234     ;; Insert newline at the end of the buffer.
1235     (unless (bolp)
1236       (insert "\n"))
1237     ;; Insert `.' at end of buffer (end of text mark).
1238     (goto-char (point-max))
1239     (insert ".\n")
1240     (goto-char (point-min))
1241     (while (not (eobp))
1242       (end-of-line)
1243       (delete-char 1)
1244       (insert nntp-end-of-line))))
1245
1246 (defun nntp-retrieve-headers-with-xover (articles &optional fetch-old)
1247   (set-buffer nntp-server-buffer)
1248   (erase-buffer)
1249   (cond
1250
1251    ;; This server does not talk NOV.
1252    ((not nntp-server-xover)
1253     nil)
1254
1255    ;; We don't care about gaps.
1256    ((or (not nntp-nov-gap)
1257         fetch-old)
1258     (nntp-send-xover-command
1259      (if fetch-old
1260          (if (numberp fetch-old)
1261              (max 1 (- (car articles) fetch-old))
1262            1)
1263        (car articles))
1264      (car (last articles)) 'wait)
1265
1266     (goto-char (point-min))
1267     (when (looking-at "[1-5][0-9][0-9] .*\n")
1268       (delete-region (point) (progn (forward-line 1) (point))))
1269     (while (search-forward "\r" nil t)
1270       (replace-match "" t t))
1271     (goto-char (point-max))
1272     (forward-line -1)
1273     (when (looking-at "\\.")
1274       (delete-region (point) (progn (forward-line 1) (point)))))
1275
1276    ;; We do it the hard way.  For each gap, an XOVER command is sent
1277    ;; to the server.  We do not wait for a reply from the server, we
1278    ;; just send them off as fast as we can.  That means that we have
1279    ;; to count the number of responses we get back to find out when we
1280    ;; have gotten all we asked for.
1281    ((numberp nntp-nov-gap)
1282     (let ((count 0)
1283           (received 0)
1284           last-point
1285           in-process-buffer-p
1286           (buf nntp-server-buffer)
1287           (process-buffer (nntp-find-connection-buffer nntp-server-buffer))
1288           first)
1289       ;; We have to check `nntp-server-xover'.  If it gets set to nil,
1290       ;; that means that the server does not understand XOVER, but we
1291       ;; won't know that until we try.
1292       (while (and nntp-server-xover articles)
1293         (setq first (car articles))
1294         ;; Search forward until we find a gap, or until we run out of
1295         ;; articles.
1296         (while (and (cdr articles)
1297                     (< (- (nth 1 articles) (car articles)) nntp-nov-gap))
1298           (setq articles (cdr articles)))
1299
1300         (setq in-process-buffer-p (stringp nntp-server-xover))
1301         (nntp-send-xover-command first (car articles))
1302         (setq articles (cdr articles))
1303
1304         (when (and nntp-server-xover in-process-buffer-p)
1305           ;; Don't count tried request.
1306           (setq count (1+ count))
1307
1308           ;; Every 400 requests we have to read the stream in
1309           ;; order to avoid deadlocks.
1310           (when (or (null articles)     ;All requests have been sent.
1311                     (zerop (% count nntp-maximum-request)))
1312
1313             (nntp-accept-response)
1314             ;; On some Emacs versions the preceding function has a
1315             ;; tendency to change the buffer.  Perhaps.  It's quite
1316             ;; difficult to reproduce, because it only seems to happen
1317             ;; once in a blue moon.
1318             (set-buffer process-buffer)
1319             (while (progn
1320                      (goto-char (or last-point (point-min)))
1321                      ;; Count replies.
1322                      (while (re-search-forward "^[0-9][0-9][0-9] .*\n" nil t)
1323                        (incf received))
1324                      (setq last-point (point))
1325                      (< received count))
1326               (nntp-accept-response)
1327               (set-buffer process-buffer))
1328             (set-buffer buf))))
1329
1330       (when nntp-server-xover
1331         (when in-process-buffer-p
1332           (set-buffer process-buffer)
1333           ;; Wait for the reply from the final command.
1334           (goto-char (point-max))
1335           (while (not (re-search-backward "^[0-9][0-9][0-9] " nil t))
1336             (nntp-accept-response)
1337             (set-buffer process-buffer)
1338             (goto-char (point-max)))
1339           (when (looking-at "^[23]")
1340             (while (progn
1341                      (goto-char (point-max))
1342                      (forward-line -1)
1343                      (not (looking-at "^\\.\r?\n")))
1344               (nntp-accept-response)
1345               (set-buffer process-buffer)))
1346           (set-buffer buf)
1347           (goto-char (point-max))
1348           (insert-buffer-substring process-buffer)
1349           (set-buffer process-buffer)
1350           (erase-buffer)
1351           (set-buffer buf))
1352
1353         ;; We remove any "." lines and status lines.
1354         (goto-char (point-min))
1355         (while (search-forward "\r" nil t)
1356           (delete-char -1))
1357         (goto-char (point-min))
1358         (delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ")
1359         t))))
1360
1361   nntp-server-xover)
1362
1363 (defun nntp-send-xover-command (beg end &optional wait-for-reply)
1364   "Send the XOVER command to the server."
1365   (let ((range (format "%d-%d" beg end))
1366         (nntp-inhibit-erase t))
1367     (if (stringp nntp-server-xover)
1368         ;; If `nntp-server-xover' is a string, then we just send this
1369         ;; command.
1370         (if wait-for-reply
1371             (nntp-send-command-nodelete
1372              "\r?\n\\.\r?\n" nntp-server-xover range)
1373           ;; We do not wait for the reply.
1374           (nntp-send-command-nodelete nil nntp-server-xover range))
1375       (let ((commands nntp-xover-commands))
1376         ;; `nntp-xover-commands' is a list of possible XOVER commands.
1377         ;; We try them all until we get at positive response.
1378         (while (and commands (eq nntp-server-xover 'try))
1379           (nntp-send-command-nodelete "\r?\n\\.\r?\n" (car commands) range)
1380           (save-excursion
1381             (set-buffer nntp-server-buffer)
1382             (goto-char (point-min))
1383             (and (looking-at "[23]")    ; No error message.
1384                  ;; We also have to look at the lines.  Some buggy
1385                  ;; servers give back simple lines with just the
1386                  ;; article number.  How... helpful.
1387                  (progn
1388                    (forward-line 1)
1389                    (looking-at "[0-9]+\t...")) ; More text after number.
1390                  (setq nntp-server-xover (car commands))))
1391           (setq commands (cdr commands)))
1392         ;; If none of the commands worked, we disable XOVER.
1393         (when (eq nntp-server-xover 'try)
1394           (save-excursion
1395             (set-buffer nntp-server-buffer)
1396             (erase-buffer)
1397             (setq nntp-server-xover nil)))
1398         nntp-server-xover))))
1399
1400 (defun nntp-find-group-and-number (&optional group)
1401   (save-excursion
1402     (save-restriction
1403       (set-buffer nntp-server-buffer)
1404       (narrow-to-region (goto-char (point-min))
1405                         (or (search-forward "\n\n" nil t) (point-max)))
1406       (goto-char (point-min))
1407       ;; We first find the number by looking at the status line.
1408       (let ((number (and (looking-at "2[0-9][0-9] +\\([0-9]+\\) ")
1409                          (string-to-int
1410                           (buffer-substring (match-beginning 1)
1411                                             (match-end 1)))))
1412             newsgroups xref)
1413         (and number (zerop number) (setq number nil))
1414         (if number
1415             ;; Then we find the group name.
1416             (setq group
1417                   (cond
1418                    ;; If there is only one group in the Newsgroups
1419                    ;; header, then it seems quite likely that this
1420                    ;; article comes from that group, I'd say.
1421                    ((and (setq newsgroups
1422                                (mail-fetch-field "newsgroups"))
1423                          (not (string-match "," newsgroups)))
1424                     newsgroups)
1425                    ;; If there is more than one group in the
1426                    ;; Newsgroups header, then the Xref header should
1427                    ;; be filled out.  We hazard a guess that the group
1428                    ;; that has this article number in the Xref header
1429                    ;; is the one we are looking for.  This might very
1430                    ;; well be wrong if this article happens to have
1431                    ;; the same number in several groups, but that's
1432                    ;; life.
1433                    ((and (setq xref (mail-fetch-field "xref"))
1434                          number
1435                          (string-match
1436                           (format "\\([^ :]+\\):%d" number) xref))
1437                     (match-string 1 xref))
1438                    (t "")))
1439           (cond
1440            ((and (setq xref (mail-fetch-field "xref"))
1441                  (string-match
1442                   (if group
1443                       (concat "\\(" (regexp-quote group) "\\):\\([0-9]+\\)")
1444                     "\\([^ :]+\\):\\([0-9]+\\)")
1445                   xref))
1446             (setq group (match-string 1 xref)
1447                   number (string-to-int (match-string 2 xref))))
1448            ((and (setq newsgroups
1449                        (mail-fetch-field "newsgroups"))
1450                  (not (string-match "," newsgroups)))
1451             (setq group newsgroups))
1452            (group)
1453            (t (setq group ""))))
1454         (when (string-match "\r" group)
1455           (setq group (substring group 0 (match-beginning 0))))
1456         (cons group number)))))
1457
1458 (defun nntp-wait-for-string (regexp)
1459   "Wait until string arrives in the buffer."
1460   (let ((buf (current-buffer)))
1461     (goto-char (point-min))
1462     (while (not (re-search-forward regexp nil t))
1463       (accept-process-output (nntp-find-connection nntp-server-buffer))
1464       (set-buffer buf)
1465       (goto-char (point-min)))))
1466
1467
1468 ;; ==========================================================================
1469 ;; Obsolete nntp-open-* connection methods -- drv
1470 ;; ==========================================================================
1471
1472 (defvoo nntp-open-telnet-envuser nil
1473   "*If non-nil, telnet session (client and server both) will support the ENVIRON option and not prompt for login name.")
1474
1475 (defvoo nntp-telnet-shell-prompt "bash\\|\$ *\r?$\\|> *\r?"
1476   "*Regular expression to match the shell prompt on the remote machine.")
1477
1478 (defvoo nntp-rlogin-program "rsh"
1479   "*Program used to log in on remote machines.
1480 The default is \"rsh\", but \"ssh\" is a popular alternative.")
1481
1482 (defvoo nntp-rlogin-parameters '("telnet" "-8" "${NNTPSERVER:=news}" "nntp")
1483   "*Parameters to `nntp-open-rlogin'.
1484 That function may be used as `nntp-open-connection-function'.  In that
1485 case, this list will be used as the parameter list given to rsh.")
1486
1487 (defvoo nntp-rlogin-user-name nil
1488   "*User name on remote system when using the rlogin connect method.")
1489
1490 (defvoo nntp-telnet-parameters
1491     '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp")
1492   "*Parameters to `nntp-open-telnet'.
1493 That function may be used as `nntp-open-connection-function'.  In that
1494 case, this list will be executed as a command after logging in
1495 via telnet.")
1496
1497 (defvoo nntp-telnet-user-name nil
1498   "User name to log in via telnet with.")
1499
1500 (defvoo nntp-telnet-passwd nil
1501   "Password to use to log in via telnet with.")
1502
1503 (defun nntp-open-telnet (buffer)
1504   (save-excursion
1505     (set-buffer buffer)
1506     (erase-buffer)
1507     (let ((proc (apply
1508                  'start-process
1509           &n