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