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