compare symbol names with `equal'
[bpt/emacs.git] / lisp / gnus / nntp.el
CommitLineData
16409b0b 1;;; nntp.el --- nntp access for Gnus
f9936da6 2
ba318903 3;; Copyright (C) 1987-1990, 1992-1998, 2000-2014 Free Software
ab422c4d 4;; Foundation, Inc.
eec82323 5
6748645f 6;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
eec82323
LMI
7;; Keywords: news
8
9;; This file is part of GNU Emacs.
10
5e809f55
GM
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
eec82323 15
5e809f55
GM
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
eec82323
LMI
20
21;; You should have received a copy of the GNU General Public License
5e809f55 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
eec82323
LMI
23
24;;; Commentary:
25
26;;; Code:
27
aa8f8277 28(eval-and-compile
da91b5f2
CY
29 ;; In Emacs 24, `open-protocol-stream' is an autoloaded alias for
30 ;; `make-network-stream'.
31 (unless (fboundp 'open-protocol-stream)
32 (require 'proto-stream)))
aa8f8277 33
eec82323
LMI
34(require 'nnheader)
35(require 'nnoo)
36(require 'gnus-util)
01c52d31
MB
37(require 'gnus)
38(require 'gnus-group) ;; gnus-group-name-charset
eec82323
LMI
39
40(nnoo-declare nntp)
41
16409b0b 42(eval-when-compile (require 'cl))
eec82323 43
b8e0f0cd 44(autoload 'auth-source-search "auth-source")
e952b711 45
e79f14a4
RS
46(defgroup nntp nil
47 "NNTP access for Gnus."
48 :group 'gnus)
49
eec82323
LMI
50(defvoo nntp-address nil
51 "Address of the physical nntp server.")
52
53(defvoo nntp-port-number "nntp"
54 "Port number on the physical nntp server.")
55
56(defvoo nntp-server-opened-hook '(nntp-send-mode-reader)
57 "*Hook used for sending commands to the server at startup.
58The default value is `nntp-send-mode-reader', which makes an innd
6748645f 59server spawn an nnrpd server.")
eec82323
LMI
60
61(defvoo nntp-authinfo-function 'nntp-send-authinfo
6748645f
LMI
62 "Function used to send AUTHINFO to the server.
63It is called with no parameters.")
eec82323
LMI
64
65(defvoo nntp-server-action-alist
16409b0b
GM
66 '(("nntpd 1\\.5\\.11t"
67 (remove-hook 'nntp-server-opened-hook 'nntp-send-mode-reader))
68 ("NNRP server Netscape"
69 (setq nntp-server-list-active-group nil)))
eec82323
LMI
70 "Alist of regexps to match on server types and actions to be taken.
71For instance, if you want Gnus to beep every time you connect
72to innd, you could say something like:
73
74\(setq nntp-server-action-alist
75 '((\"innd\" (ding))))
76
77You probably don't want to do that, though.")
78
79(defvoo nntp-open-connection-function 'nntp-open-network-stream
e742e117
CY
80 "Method for connecting to a remote system.
81It should be a function, which is called with the output buffer
82as its single argument, or one of the following special values:
83
84- `nntp-open-network-stream' specifies a network connection,
85 upgrading to a TLS connection via STARTTLS if possible.
86- `nntp-open-plain-stream' specifies an unencrypted network
87 connection (no STARTTLS upgrade is attempted).
88- `nntp-open-ssl-stream' or `nntp-open-tls-stream' specify a TLS
89 network connection.
90
91Apart from the above special values, valid functions are as
92follows; please refer to their respective doc string for more
93information.
94For direct connections:
95- `nntp-open-netcat-stream'
96- `nntp-open-telnet-stream'
97For indirect connections:
98- `nntp-open-via-rlogin-and-netcat'
99- `nntp-open-via-rlogin-and-telnet'
100- `nntp-open-via-telnet-and-telnet'")
eec82323 101
45cb30ee
MB
102(defvoo nntp-never-echoes-commands nil
103 "*Non-nil means the nntp server never echoes commands.
104It is reported that some nntps server doesn't echo commands. So, you
105may want to set this to non-nil in the method for such a server setting
106`nntp-open-connection-function' to `nntp-open-ssl-stream' for example.
107Note that the `nntp-open-connection-functions-never-echo-commands'
108variable overrides the nil value of this variable.")
109
110(defvoo nntp-open-connection-functions-never-echo-commands
111 '(nntp-open-network-stream)
112 "*List of functions that never echo commands.
113Add or set a function which you set to `nntp-open-connection-function'
114to this list if it does not echo commands. Note that a non-nil value
115of the `nntp-never-echoes-commands' variable overrides this variable.")
116
23f87bed
MB
117(defvoo nntp-pre-command nil
118 "*Pre-command to use with the various nntp-open-via-* methods.
119This is where you would put \"runsocks\" or stuff like that.")
eec82323 120
23f87bed
MB
121(defvoo nntp-telnet-command "telnet"
122 "*Telnet command used to connect to the nntp server.
01c52d31
MB
123This command is used by the methods `nntp-open-telnet-stream',
124`nntp-open-via-rlogin-and-telnet' and `nntp-open-via-telnet-and-telnet'.")
eec82323 125
23f87bed
MB
126(defvoo nntp-telnet-switches '("-8")
127 "*Switches given to the telnet command `nntp-telnet-command'.")
eec82323 128
23f87bed
MB
129(defvoo nntp-end-of-line "\r\n"
130 "*String to use on the end of lines when talking to the NNTP server.
01c52d31
MB
131This is \"\\r\\n\" by default, but should be \"\\n\" when using an indirect
132connection method (nntp-open-via-*).")
eec82323 133
23f87bed
MB
134(defvoo nntp-via-rlogin-command "rsh"
135 "*Rlogin command used to connect to an intermediate host.
01c52d31
MB
136This command is used by the methods `nntp-open-via-rlogin-and-telnet'
137and `nntp-open-via-rlogin-and-netcat'. The default is \"rsh\", but \"ssh\"
138is a popular alternative.")
6748645f 139
23f87bed
MB
140(defvoo nntp-via-rlogin-command-switches nil
141 "*Switches given to the rlogin command `nntp-via-rlogin-command'.
142If you use \"ssh\" for `nntp-via-rlogin-command', you may set this to
143\(\"-C\") in order to compress all data connections, otherwise set this
144to \(\"-t\" \"-e\" \"none\") or (\"-C\" \"-t\" \"-e\" \"none\") if the telnet
145command requires a pseudo-tty allocation on an intermediate host.")
6748645f 146
23f87bed
MB
147(defvoo nntp-via-telnet-command "telnet"
148 "*Telnet command used to connect to an intermediate host.
149This command is used by the `nntp-open-via-telnet-and-telnet' method.")
a8151ef7 150
23f87bed
MB
151(defvoo nntp-via-telnet-switches '("-8")
152 "*Switches given to the telnet command `nntp-via-telnet-command'.")
a8151ef7 153
990e2c2f 154(defvoo nntp-netcat-command "nc"
01c52d31 155 "*Netcat command used to connect to the nntp server.
990e2c2f
SM
156This command is used by the `nntp-open-netcat-stream' and
157`nntp-open-via-rlogin-and-netcat' methods.")
01c52d31 158
990e2c2f
SM
159(defvoo nntp-netcat-switches nil
160 "*Switches given to the netcat command `nntp-netcat-command'.")
01c52d31 161
23f87bed
MB
162(defvoo nntp-via-user-name nil
163 "*User name to log in on an intermediate host with.
01c52d31 164This variable is used by the various nntp-open-via-* methods.")
23f87bed
MB
165
166(defvoo nntp-via-user-password nil
167 "*Password to use to log in on an intermediate host with.
168This variable is used by the `nntp-open-via-telnet-and-telnet' method.")
169
170(defvoo nntp-via-address nil
171 "*Address of an intermediate host to connect to.
01c52d31 172This variable is used by the various nntp-open-via-* methods.")
23f87bed
MB
173
174(defvoo nntp-via-envuser nil
175 "*Whether both telnet client and server support the ENVIRON option.
176If non-nil, there will be no prompt for a login name.")
177
178(defvoo nntp-via-shell-prompt "bash\\|\$ *\r?$\\|> *\r?"
179 "*Regular expression to match the shell prompt on an intermediate host.
180This variable is used by the `nntp-open-via-telnet-and-telnet' method.")
eec82323
LMI
181
182(defvoo nntp-large-newsgroup 50
23f87bed
MB
183 "*The number of articles which indicates a large newsgroup.
184If the number of articles is greater than the value, verbose
eec82323
LMI
185messages will be shown to indicate the current status.")
186
187(defvoo nntp-maximum-request 400
188 "*The maximum number of the requests sent to the NNTP server at one time.
189If Emacs hangs up while retrieving headers, set the variable to a
190lower value.")
191
192(defvoo nntp-nov-is-evil nil
193 "*If non-nil, nntp will never attempt to use XOVER when talking to the server.")
194
195(defvoo nntp-xover-commands '("XOVER" "XOVERVIEW")
196 "*List of strings that are used as commands to fetch NOV lines from a server.
197The strings are tried in turn until a positive response is gotten. If
198none of the commands are successful, nntp will just grab headers one
199by one.")
200
a8151ef7 201(defvoo nntp-nov-gap 5
eec82323
LMI
202 "*Maximum allowed gap between two articles.
203If the gap between two consecutive articles is bigger than this
204variable, split the XOVER request into two requests.")
205
37cc095b
MB
206(defvoo nntp-xref-number-is-evil nil
207 "*If non-nil, Gnus never trusts article numbers in the Xref header.
208Some news servers, e.g., ones running Diablo, run multiple engines
209having the same articles but article numbers are not kept synchronized
210between them. If you connect to such a server, set this to a non-nil
211value, and Gnus never uses article numbers (that appear in the Xref
212header and vary by which engine is chosen) to refer to articles.")
213
eec82323
LMI
214(defvoo nntp-prepare-server-hook nil
215 "*Hook run before a server is opened.
216If can be used to set up a server remotely, for instance. Say you
217have an account at the machine \"other.machine\". This machine has
218access to an NNTP server that you can't access locally. You could
219then use this hook to rsh to the remote machine and start a proxy NNTP
6748645f
LMI
220server there that you can connect to. See also
221`nntp-open-connection-function'")
eec82323 222
6748645f
LMI
223(defcustom nntp-authinfo-file "~/.authinfo"
224 ".netrc-like file that holds nntp authinfo passwords."
e79f14a4 225 :group 'nntp
6748645f
LMI
226 :type
227 '(choice file
228 (repeat :tag "Entries"
229 :menu-tag "Inline"
230 (list :format "%v"
231 :value ("" ("login" . "") ("password" . ""))
232 (string :tag "Host")
233 (checklist :inline t
234 (cons :format "%v"
235 (const :format "" "login")
236 (string :format "Login: %v"))
237 (cons :format "%v"
238 (const :format "" "password")
23f87bed 239 (string :format "Password: %v")))))))
6748645f 240
1e91d506
G
241(make-obsolete 'nntp-authinfo-file nil "Emacs 24.1")
242
eec82323
LMI
243\f
244
6748645f
LMI
245(defvoo nntp-connection-timeout nil
246 "*Number of seconds to wait before an nntp connection times out.
2eebe218
DL
247If this variable is nil, which is the default, no timers are set.
248NOTE: This variable is never seen to work in Emacs 20 and XEmacs 21.")
6748645f 249
23f87bed
MB
250(defvoo nntp-prepare-post-hook nil
251 "*Hook run just before posting an article. It is supposed to be used
252to insert Cancel-Lock headers.")
253
6b958814
G
254(defvoo nntp-server-list-active-group 'try
255 "If nil, then always use GROUP instead of LIST ACTIVE.
256This is usually slower, but on misconfigured servers that don't
257update their active files often, this can help.")
258
eec82323
LMI
259;;; Internal variables.
260
7e67562f 261(defvoo nntp-retrieval-in-progress nil)
6748645f
LMI
262(defvar nntp-record-commands nil
263 "*If non-nil, nntp will record all commands in the \"*nntp-log*\" buffer.")
264
eec82323
LMI
265(defvar nntp-have-messaged nil)
266
267(defvar nntp-process-wait-for nil)
268(defvar nntp-process-to-buffer nil)
269(defvar nntp-process-callback nil)
270(defvar nntp-process-decode nil)
271(defvar nntp-process-start-point nil)
272(defvar nntp-inside-change-function nil)
6748645f
LMI
273(defvoo nntp-last-command-time nil)
274(defvoo nntp-last-command nil)
275(defvoo nntp-authinfo-password nil)
276(defvoo nntp-authinfo-user nil)
01c52d31 277(defvoo nntp-authinfo-force nil)
eec82323
LMI
278
279(defvar nntp-connection-list nil)
280
281(defvoo nntp-server-type nil)
282(defvoo nntp-connection-alist nil)
283(defvoo nntp-status-string "")
284(defconst nntp-version "nntp 5.0")
285(defvoo nntp-inhibit-erase nil)
286(defvoo nntp-inhibit-output nil)
287
288(defvoo nntp-server-xover 'try)
eec82323 289
16409b0b
GM
290(defvar nntp-async-timer nil)
291(defvar nntp-async-process-list nil)
292
58090a8d 293(defvar nntp-authinfo-rejected nil
c9fc72fa
LMI
294"A custom error condition used to report 'Authentication Rejected' errors.
295Condition handlers that match just this condition ensure that the nntp
58090a8d
MB
296backend doesn't catch this error.")
297(put 'nntp-authinfo-rejected 'error-conditions '(error nntp-authinfo-rejected))
298(put 'nntp-authinfo-rejected 'error-message "Authorization Rejected")
299
eec82323
LMI
300\f
301
302;;; Internal functions.
303
304(defsubst nntp-send-string (process string)
305 "Send STRING to PROCESS."
6748645f
LMI
306 ;; We need to store the time to provide timeouts, and
307 ;; to store the command so the we can replay the command
308 ;; if the server gives us an AUTHINFO challenge.
309 (setq nntp-last-command-time (current-time)
310 nntp-last-command string)
311 (when nntp-record-commands
312 (nntp-record-command string))
23f87bed
MB
313 (process-send-string process (concat string nntp-end-of-line))
314 (or (memq (process-status process) '(open run))
315 (nntp-report "Server closed connection")))
eec82323 316
6748645f
LMI
317(defun nntp-record-command (string)
318 "Record the command STRING."
ed075cb4 319 (with-current-buffer (get-buffer-create "*nntp-log*")
6748645f 320 (goto-char (point-max))
240a298f
PE
321 (insert (format-time-string "%Y%m%dT%H%M%S.%3N")
322 " " nntp-address " " string "\n")))
6748645f 323
2612a3d6
SM
324(defvar nntp--report-1 nil)
325
23f87bed
MB
326(defun nntp-report (&rest args)
327 "Report an error from the nntp backend. The first string in ARGS
328can be a format string. For some commands, the failed command may be
329retried once before actually displaying the error report."
2612a3d6
SM
330 (if nntp--report-1
331 (progn
332 ;; Throw out to nntp-with-open-group-error so that the connection may
333 ;; be restored and the command retried."
334 (when nntp-record-commands
335 (nntp-record-command "*** CONNECTION LOST ***"))
336 (throw 'nntp-with-open-group-error t))
23f87bed 337
2612a3d6
SM
338 (when nntp-record-commands
339 (nntp-record-command "*** CALLED nntp-report ***"))
23f87bed 340
2612a3d6 341 (nnheader-report 'nntp args)
23f87bed 342
2612a3d6 343 (apply 'error args)))
23f87bed 344
1428d46b
MB
345(defmacro nntp-copy-to-buffer (buffer start end)
346 "Copy string from unibyte current buffer to multibyte buffer."
347 (if (featurep 'xemacs)
348 `(copy-to-buffer ,buffer ,start ,end)
349 `(let ((string (buffer-substring ,start ,end)))
350 (with-current-buffer ,buffer
351 (erase-buffer)
352 (insert (if enable-multibyte-characters
353 (mm-string-to-multibyte string)
354 string))
355 (goto-char (point-min))
356 nil))))
357
eec82323
LMI
358(defsubst nntp-wait-for (process wait-for buffer &optional decode discard)
359 "Wait for WAIT-FOR to arrive from PROCESS."
01c52d31 360
ed075cb4 361 (with-current-buffer (process-buffer process)
eec82323 362 (goto-char (point-min))
01c52d31 363
6748645f 364 (while (and (or (not (memq (char-after (point)) '(?2 ?3 ?4 ?5)))
58090a8d 365 (looking-at "48[02]"))
6748645f 366 (memq (process-status process) '(open run)))
58090a8d 367 (cond ((looking-at "480")
01c52d31 368 (nntp-handle-authinfo process))
58090a8d 369 ((looking-at "482")
04db63bc
G
370 (nnheader-report 'nntp "%s"
371 (get 'nntp-authinfo-rejected 'error-message))
58090a8d
MB
372 (signal 'nntp-authinfo-rejected nil))
373 ((looking-at "^.*\n")
374 (delete-region (point) (progn (forward-line 1) (point)))))
eec82323
LMI
375 (nntp-accept-process-output process)
376 (goto-char (point-min)))
377 (prog1
6748645f
LMI
378 (cond
379 ((looking-at "[45]")
380 (progn
381 (nntp-snarf-error-message)
382 nil))
383 ((not (memq (process-status process) '(open run)))
23f87bed 384 (nntp-report "Server closed connection"))
6748645f 385 (t
eec82323 386 (goto-char (point-max))
23f87bed
MB
387 (let ((limit (point-min))
388 response)
eec82323 389 (while (not (re-search-backward wait-for limit t))
6748645f 390 (nntp-accept-process-output process)
eec82323
LMI
391 ;; We assume that whatever we wait for is less than 1000
392 ;; characters long.
393 (setq limit (max (- (point-max) 1000) (point-min)))
23f87bed
MB
394 (goto-char (point-max)))
395 (setq response (match-string 0))
396 (with-current-buffer nntp-server-buffer
397 (setq nntp-process-response response)))
eec82323
LMI
398 (nntp-decode-text (not decode))
399 (unless discard
ed075cb4 400 (with-current-buffer buffer
23f87bed 401 (goto-char (point-max))
9f5e78f7 402 (nnheader-insert-buffer-substring (process-buffer process))
eec82323
LMI
403 ;; Nix out "nntp reading...." message.
404 (when nntp-have-messaged
405 (setq nntp-have-messaged nil)
23f87bed
MB
406 (nnheader-message 5 ""))))
407 t))
eec82323
LMI
408 (unless discard
409 (erase-buffer)))))
410
16409b0b
GM
411(defun nntp-kill-buffer (buffer)
412 (when (buffer-name buffer)
1e91d506
G
413 (let ((process (get-buffer-process buffer)))
414 (when process
415 (delete-process process)))
16409b0b
GM
416 (kill-buffer buffer)
417 (nnheader-init-server-buffer)))
418
01c52d31
MB
419(defun nntp-erase-buffer (buffer)
420 "Erase contents of BUFFER."
421 (with-current-buffer buffer
422 (erase-buffer)))
423
eec82323
LMI
424(defsubst nntp-find-connection (buffer)
425 "Find the connection delivering to BUFFER."
426 (let ((alist nntp-connection-alist)
427 (buffer (if (stringp buffer) (get-buffer buffer) buffer))
428 process entry)
23f87bed 429 (while (and alist (setq entry (pop alist)))
eec82323
LMI
430 (when (eq buffer (cadr entry))
431 (setq process (car entry)
432 alist nil)))
433 (when process
434 (if (memq (process-status process) '(open run))
435 process
16409b0b 436 (nntp-kill-buffer (process-buffer process))
eec82323
LMI
437 (setq nntp-connection-alist (delq entry nntp-connection-alist))
438 nil))))
439
440(defsubst nntp-find-connection-entry (buffer)
441 "Return the entry for the connection to BUFFER."
442 (assq (nntp-find-connection buffer) nntp-connection-alist))
443
444(defun nntp-find-connection-buffer (buffer)
445 "Return the process connection buffer tied to BUFFER."
446 (let ((process (nntp-find-connection buffer)))
447 (when process
448 (process-buffer process))))
449
450(defsubst nntp-retrieve-data (command address port buffer
6748645f 451 &optional wait-for callback decode)
eec82323
LMI
452 "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS."
453 (let ((process (or (nntp-find-connection buffer)
454 (nntp-open-connection buffer))))
23f87bed
MB
455 (if process
456 (progn
457 (unless (or nntp-inhibit-erase nnheader-callback-function)
01c52d31 458 (nntp-erase-buffer (process-buffer process)))
23f87bed
MB
459 (condition-case err
460 (progn
461 (when command
462 (nntp-send-string process command))
463 (cond
464 ((eq callback 'ignore)
465 t)
466 ((and callback wait-for)
467 (nntp-async-wait process wait-for buffer decode callback)
468 t)
469 (wait-for
470 (nntp-wait-for process wait-for buffer decode))
471 (t t)))
58090a8d
MB
472 (nntp-authinfo-rejected
473 (signal 'nntp-authinfo-rejected (cdr err)))
23f87bed
MB
474 (error
475 (nnheader-report 'nntp "Couldn't open connection to %s: %s"
476 address err))
477 (quit
478 (message "Quit retrieving data from nntp")
479 (signal 'quit nil)
480 nil)))
481 (nnheader-report 'nntp "Couldn't open connection to %s" address))))
eec82323
LMI
482
483(defsubst nntp-send-command (wait-for &rest strings)
484 "Send STRINGS to server and wait until WAIT-FOR returns."
485 (when (and (not nnheader-callback-function)
486 (not nntp-inhibit-output))
01c52d31 487 (nntp-erase-buffer nntp-server-buffer))
23f87bed
MB
488 (let* ((command (mapconcat 'identity strings " "))
489 (process (nntp-find-connection nntp-server-buffer))
490 (buffer (and process (process-buffer process)))
491 (pos (and buffer (with-current-buffer buffer (point)))))
492 (if process
493 (prog1
494 (nntp-retrieve-data command
495 nntp-address nntp-port-number
496 nntp-server-buffer
497 wait-for nnheader-callback-function)
498 ;; If nothing to wait for, still remove possibly echo'ed commands.
45cb30ee
MB
499 ;; We don't have echoes if `nntp-never-echoes-commands' is non-nil
500 ;; or the value of `nntp-open-connection-function' is in
501 ;; `nntp-open-connection-functions-never-echo-commands', so we
502 ;; skip this in that cases.
23f87bed 503 (unless (or wait-for
45cb30ee
MB
504 nntp-never-echoes-commands
505 (memq
506 nntp-open-connection-function
507 nntp-open-connection-functions-never-echo-commands))
23f87bed 508 (nntp-accept-response)
ed075cb4 509 (with-current-buffer buffer
23f87bed
MB
510 (goto-char pos)
511 (if (looking-at (regexp-quote command))
512 (delete-region pos (progn (forward-line 1)
01c52d31 513 (point-at-bol)))))))
23f87bed
MB
514 (nnheader-report 'nntp "Couldn't open connection to %s."
515 nntp-address))))
eec82323
LMI
516
517(defun nntp-send-command-nodelete (wait-for &rest strings)
518 "Send STRINGS to server and wait until WAIT-FOR returns."
23f87bed
MB
519 (let* ((command (mapconcat 'identity strings " "))
520 (process (nntp-find-connection nntp-server-buffer))
521 (buffer (and process (process-buffer process)))
522 (pos (and buffer (with-current-buffer buffer (point)))))
523 (if process
524 (prog1
525 (nntp-retrieve-data command
526 nntp-address nntp-port-number
527 nntp-server-buffer
528 wait-for nnheader-callback-function)
529 ;; If nothing to wait for, still remove possibly echo'ed commands
530 (unless wait-for
531 (nntp-accept-response)
ed075cb4 532 (with-current-buffer buffer
23f87bed
MB
533 (goto-char pos)
534 (if (looking-at (regexp-quote command))
535 (delete-region pos (progn (forward-line 1)
01c52d31 536 (point-at-bol)))))))
23f87bed
MB
537 (nnheader-report 'nntp "Couldn't open connection to %s."
538 nntp-address))))
eec82323
LMI
539
540(defun nntp-send-command-and-decode (wait-for &rest strings)
541 "Send STRINGS to server and wait until WAIT-FOR returns."
542 (when (and (not nnheader-callback-function)
543 (not nntp-inhibit-output))
01c52d31 544 (nntp-erase-buffer nntp-server-buffer))
23f87bed
MB
545 (let* ((command (mapconcat 'identity strings " "))
546 (process (nntp-find-connection nntp-server-buffer))
547 (buffer (and process (process-buffer process)))
548 (pos (and buffer (with-current-buffer buffer (point)))))
549 (if process
550 (prog1
551 (nntp-retrieve-data command
552 nntp-address nntp-port-number
553 nntp-server-buffer
554 wait-for nnheader-callback-function t)
555 ;; If nothing to wait for, still remove possibly echo'ed commands
556 (unless wait-for
557 (nntp-accept-response)
ed075cb4 558 (with-current-buffer buffer
01c52d31
MB
559 (goto-char pos)
560 (if (looking-at (regexp-quote command))
561 (delete-region pos (progn (forward-line 1) (point-at-bol))))
562 )))
23f87bed
MB
563 (nnheader-report 'nntp "Couldn't open connection to %s."
564 nntp-address))))
565
eec82323
LMI
566
567(defun nntp-send-buffer (wait-for)
568 "Send the current buffer to server and wait until WAIT-FOR returns."
569 (when (and (not nnheader-callback-function)
570 (not nntp-inhibit-output))
01c52d31
MB
571 (nntp-erase-buffer
572 (nntp-find-connection-buffer nntp-server-buffer)))
eec82323 573 (nntp-encode-text)
ed075cb4
SM
574 ;; Make sure we did not forget to encode some of the content.
575 (assert (save-excursion (goto-char (point-min))
576 (not (re-search-forward "[^\000-\377]" nil t))))
577 (mm-disable-multibyte)
578 (process-send-region (nntp-find-connection nntp-server-buffer)
579 (point-min) (point-max))
eec82323
LMI
580 (nntp-retrieve-data
581 nil nntp-address nntp-port-number nntp-server-buffer
582 wait-for nnheader-callback-function))
583
584\f
585
586;;; Interface functions.
587
588(nnoo-define-basics nntp)
589
6748645f
LMI
590(defsubst nntp-next-result-arrived-p ()
591 (cond
592 ;; A result that starts with a 2xx code is terminated by
593 ;; a line with only a "." on it.
16409b0b 594 ((eq (char-after) ?2)
6748645f 595 (if (re-search-forward "\n\\.\r?\n" nil t)
01c52d31
MB
596 (progn
597 ;; Some broken news servers add another dot at the end.
598 ;; Protect against inflooping there.
599 (while (looking-at "^\\.\r?\n")
600 (forward-line 1))
601 t)
6748645f
LMI
602 nil))
603 ;; A result that starts with a 3xx or 4xx code is terminated
604 ;; by a newline.
605 ((looking-at "[34]")
606 (if (search-forward "\n" nil t)
607 t
608 nil))
609 ;; No result here.
610 (t
611 nil)))
612
ed075cb4
SM
613(defun nntp-with-open-group-function (-group -server -connectionless -bodyfun)
614 "Protect against servers that don't like clients that keep idle connections opens.
615The problem being that these servers may either close a connection or
616simply ignore any further requests on a connection. Closed
617connections are not detected until `accept-process-output' has updated
618the `process-status'. Dropped connections are not detected until the
619connection timeouts (which may be several minutes) or
620`nntp-connection-timeout' has expired. When these occur
621`nntp-with-open-group', opens a new connection then re-issues the NNTP
622command whose response triggered the error."
2612a3d6
SM
623 (let ((nntp-report-n nntp--report-1)
624 (nntp--report-1 t)
625 (nntp-with-open-group-internal nil))
ed075cb4
SM
626 (while (catch 'nntp-with-open-group-error
627 ;; Open the connection to the server
628 ;; NOTE: Existing connections are NOT tested.
629 (nntp-possibly-change-group -group -server -connectionless)
630
631 (let ((-timer
632 (and nntp-connection-timeout
633 (run-at-time
634 nntp-connection-timeout nil
635 (lambda ()
636 (let* ((-process (nntp-find-connection
637 nntp-server-buffer))
638 (-buffer (and -process
639 (process-buffer -process))))
640 ;; When I an able to identify the
641 ;; connection to the server AND I've
c80e3b4a 642 ;; received NO response for
ed075cb4
SM
643 ;; nntp-connection-timeout seconds.
644 (when (and -buffer (eq 0 (buffer-size -buffer)))
645 ;; Close the connection. Take no
646 ;; other action as the accept input
647 ;; code will handle the closed
648 ;; connection.
649 (nntp-kill-buffer -buffer))))))))
650 (unwind-protect
651 (setq nntp-with-open-group-internal
652 (condition-case nil
653 (funcall -bodyfun)
654 (quit
655 (unless debug-on-quit
656 (nntp-close-server))
657 (signal 'quit nil))))
658 (when -timer
659 (nnheader-cancel-timer -timer)))
660 nil))
2612a3d6 661 (setq nntp--report-1 nntp-report-n))
ed075cb4
SM
662 nntp-with-open-group-internal))
663
23f87bed
MB
664(defmacro nntp-with-open-group (group server &optional connectionless &rest forms)
665 "Protect against servers that don't like clients that keep idle connections opens.
666The problem being that these servers may either close a connection or
667simply ignore any further requests on a connection. Closed
ed075cb4
SM
668connections are not detected until `accept-process-output' has updated
669the `process-status'. Dropped connections are not detected until the
23f87bed 670connection timeouts (which may be several minutes) or
ed075cb4
SM
671`nntp-connection-timeout' has expired. When these occur
672`nntp-with-open-group', opens a new connection then re-issues the NNTP
23f87bed 673command whose response triggered the error."
ed075cb4 674 (declare (indent 2) (debug (form form [&optional symbolp] def-body)))
23f87bed
MB
675 (when (and (listp connectionless)
676 (not (eq connectionless nil)))
677 (setq forms (cons connectionless forms)
678 connectionless nil))
ed075cb4 679 `(nntp-with-open-group-function ,group ,server ,connectionless (lambda () ,@forms)))
23f87bed 680
eec82323
LMI
681(deffoo nntp-retrieve-headers (articles &optional group server fetch-old)
682 "Retrieve the headers of ARTICLES."
23f87bed
MB
683 (nntp-with-open-group
684 group server
ed075cb4 685 (with-current-buffer (nntp-find-connection-buffer nntp-server-buffer)
23f87bed
MB
686 (erase-buffer)
687 (if (and (not gnus-nov-is-evil)
688 (not nntp-nov-is-evil)
689 (nntp-retrieve-headers-with-xover articles fetch-old))
690 ;; We successfully retrieved the headers via XOVER.
691 'nov
692 ;; XOVER didn't work, so we do it the hard, slow and inefficient
693 ;; way.
694 (let ((number (length articles))
695 (articles articles)
696 (count 0)
697 (received 0)
698 (last-point (point-min))
699 (buf (nntp-find-connection-buffer nntp-server-buffer))
700 (nntp-inhibit-erase t)
701 article)
702 ;; Send HEAD commands.
703 (while (setq article (pop articles))
704 (nntp-send-command
705 nil
706 "HEAD" (if (numberp article)
707 (int-to-string article)
708 ;; `articles' is either a list of article numbers
709 ;; or a list of article IDs.
710 article))
711 (incf count)
712 ;; Every 400 requests we have to read the stream in
713 ;; order to avoid deadlocks.
714 (when (or (null articles) ;All requests have been sent.
715 (zerop (% count nntp-maximum-request)))
716 (nntp-accept-response)
717 (while (progn
718 (set-buffer buf)
719 (goto-char last-point)
720 ;; Count replies.
721 (while (nntp-next-result-arrived-p)
722 (setq last-point (point))
723 (incf received))
724 (< received count))
725 ;; If number of headers is greater than 100, give
726 ;; informative messages.
727 (and (numberp nntp-large-newsgroup)
728 (> number nntp-large-newsgroup)
729 (zerop (% received 20))
730 (nnheader-message 6 "NNTP: Receiving headers... %d%%"
731 (/ (* received 100) number)))
732 (nntp-accept-response))))
733 (and (numberp nntp-large-newsgroup)
734 (> number nntp-large-newsgroup)
735 (nnheader-message 6 "NNTP: Receiving headers...done"))
736
737 ;; Now all of replies are received. Fold continuation lines.
738 (nnheader-fold-continuation-lines)
739 ;; Remove all "\r"'s.
740 (nnheader-strip-cr)
1428d46b 741 (nntp-copy-to-buffer nntp-server-buffer (point-min) (point-max))
23f87bed 742 'headers)))))
eec82323 743
5415d076 744(deffoo nntp-retrieve-group-data-early (server infos)
1d88c091
LI
745 "Retrieve group info on INFOS."
746 (nntp-with-open-group nil server
7e67562f 747 (let ((buffer (nntp-find-connection-buffer nntp-server-buffer)))
964646c4
LI
748 (unless infos
749 (with-current-buffer buffer
750 (setq nntp-retrieval-in-progress nil)))
7e67562f 751 (when (and buffer
964646c4 752 infos
7e67562f
G
753 (with-current-buffer buffer
754 (not nntp-retrieval-in-progress)))
755 ;; The first time this is run, this variable is `try'. So we
756 ;; try.
757 (when (eq nntp-server-list-active-group 'try)
758 (nntp-try-list-active
759 (gnus-group-real-name (gnus-info-group (car infos)))))
760 (with-current-buffer buffer
761 (erase-buffer)
762 ;; Mark this buffer as "in use" in case we try to issue two
763 ;; retrievals from the same server. This shouldn't happen,
764 ;; so this is mostly a sanity check.
765 (setq nntp-retrieval-in-progress t)
766 (let ((nntp-inhibit-erase t)
767 (command (if nntp-server-list-active-group
768 "LIST ACTIVE" "GROUP")))
769 (dolist (info infos)
770 (nntp-send-command
771 nil command (gnus-group-real-name (gnus-info-group info)))))
772 (length infos))))))
1d88c091 773
5415d076 774(deffoo nntp-finish-retrieve-group-infos (server infos count)
1d88c091
LI
775 (nntp-with-open-group nil server
776 (let ((buf (nntp-find-connection-buffer nntp-server-buffer))
777 (method (gnus-find-method-for-group
778 (gnus-info-group (car infos))
779 (car infos)))
780 (received 0)
781 (last-point 1))
7e67562f
G
782 (with-current-buffer buf
783 (setq nntp-retrieval-in-progress nil))
76b8d9dd
LI
784 (when (and buf
785 count)
1d88c091
LI
786 (with-current-buffer buf
787 (while (and (gnus-buffer-live-p buf)
788 (progn
789 (goto-char last-point)
790 ;; Count replies.
06b840e0
LI
791 (while (re-search-forward
792 (if nntp-server-list-active-group
793 "^[.]"
794 "^[0-9]")
795 nil t)
1d88c091
LI
796 (incf received))
797 (setq last-point (point))
798 (< received count)))
799 (nntp-accept-response))
800 ;; We now have all the entries. Remove CRs.
5415d076 801 (nnheader-strip-cr)
1d88c091
LI
802 (if (not nntp-server-list-active-group)
803 (progn
804 (nntp-copy-to-buffer nntp-server-buffer
805 (point-min) (point-max))
89b163db
G
806 (with-current-buffer nntp-server-buffer
807 (gnus-groups-to-gnus-format method gnus-active-hashtb t)))
1d88c091
LI
808 ;; We have read active entries, so we just delete the
809 ;; superfluous gunk.
810 (goto-char (point-min))
811 (while (re-search-forward "^[.2-5]" nil t)
812 (delete-region (match-beginning 0)
813 (progn (forward-line 1) (point))))
814 (nntp-copy-to-buffer nntp-server-buffer (point-min) (point-max))
5415d076 815 (with-current-buffer nntp-server-buffer
06b840e0
LI
816 (gnus-active-to-gnus-format
817 ;; Kludge to use the extended method name if you have
818 ;; an extended one.
819 (if (consp (gnus-info-method (car infos)))
820 (gnus-info-method (car infos))
821 method)
822 gnus-active-hashtb nil t))))))))
1d88c091 823
eec82323
LMI
824(deffoo nntp-retrieve-groups (groups &optional server)
825 "Retrieve group info on GROUPS."
23f87bed
MB
826 (nntp-with-open-group
827 nil server
e7719ea1
G
828 (when (and (nntp-find-connection-buffer nntp-server-buffer)
829 (with-current-buffer
830 (nntp-find-connection-buffer nntp-server-buffer)
831 (if (not nntp-retrieval-in-progress)
832 t
833 (message "Warning: Refusing to do retrieval from %s because a retrieval is already happening"
834 server)
835 nil)))
23f87bed
MB
836 (catch 'done
837 (save-excursion
838 ;; Erase nntp-server-buffer before nntp-inhibit-erase.
01c52d31 839 (nntp-erase-buffer nntp-server-buffer)
23f87bed
MB
840 (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
841 ;; The first time this is run, this variable is `try'. So we
842 ;; try.
843 (when (eq nntp-server-list-active-group 'try)
844 (nntp-try-list-active (car groups)))
845 (erase-buffer)
846 (let ((count 0)
847 (groups groups)
848 (received 0)
849 (last-point (point-min))
850 (nntp-inhibit-erase t)
851 (buf (nntp-find-connection-buffer nntp-server-buffer))
852 (command (if nntp-server-list-active-group
853 "LIST ACTIVE" "GROUP")))
854 (while groups
855 ;; Timeout may have killed the buffer.
856 (unless (gnus-buffer-live-p buf)
857 (nnheader-report 'nntp "Connection to %s is closed." server)
858 (throw 'done nil))
859 ;; Send the command to the server.
860 (nntp-send-command nil command (pop groups))
861 (incf count)
862 ;; Every 400 requests we have to read the stream in
863 ;; order to avoid deadlocks.
864 (when (or (null groups) ;All requests have been sent.
865 (zerop (% count nntp-maximum-request)))
866 (nntp-accept-response)
867 (while (and (gnus-buffer-live-p buf)
868 (progn
869 ;; Search `blue moon' in this file for the
870 ;; reason why set-buffer here.
871 (set-buffer buf)
872 (goto-char last-point)
873 ;; Count replies.
874 (while (re-search-forward "^[0-9]" nil t)
875 (incf received))
876 (setq last-point (point))
877 (< received count)))
878 (nntp-accept-response))))
879
880 ;; Wait for the reply from the final command.
881 (unless (gnus-buffer-live-p buf)
882 (nnheader-report 'nntp "Connection to %s is closed." server)
883 (throw 'done nil))
884 (set-buffer buf)
885 (goto-char (point-max))
886 (re-search-backward "^[0-9]" nil t)
887 (when (looking-at "^[23]")
888 (while (and (gnus-buffer-live-p buf)
889 (progn
890 (set-buffer buf)
891 (goto-char (point-max))
892 (if (not nntp-server-list-active-group)
893 (not (re-search-backward "\r?\n"
894 (- (point) 3) t))
895 (not (re-search-backward "^\\.\r?\n"
896 (- (point) 4) t)))))
897 (nntp-accept-response)))
898
899 ;; Now all replies are received. We remove CRs.
900 (unless (gnus-buffer-live-p buf)
901 (nnheader-report 'nntp "Connection to %s is closed." server)
902 (throw 'done nil))
903 (set-buffer buf)
904 (goto-char (point-min))
905 (while (search-forward "\r" nil t)
906 (replace-match "" t t))
907
908 (if (not nntp-server-list-active-group)
909 (progn
1428d46b
MB
910 (nntp-copy-to-buffer nntp-server-buffer
911 (point-min) (point-max))
23f87bed
MB
912 'group)
913 ;; We have read active entries, so we just delete the
914 ;; superfluous gunk.
915 (goto-char (point-min))
916 (while (re-search-forward "^[.2-5]" nil t)
917 (delete-region (match-beginning 0)
918 (progn (forward-line 1) (point))))
1428d46b 919 (nntp-copy-to-buffer nntp-server-buffer (point-min) (point-max))
23f87bed 920 'active)))))))
eec82323
LMI
921
922(deffoo nntp-retrieve-articles (articles &optional group server)
23f87bed
MB
923 (nntp-with-open-group
924 group server
925 (save-excursion
926 (let ((number (length articles))
927 (articles articles)
928 (count 0)
929 (received 0)
930 (last-point (point-min))
931 (buf (nntp-find-connection-buffer nntp-server-buffer))
932 (nntp-inhibit-erase t)
933 (map (apply 'vector articles))
934 (point 1)
935 article)
936 (set-buffer buf)
937 (erase-buffer)
938 ;; Send ARTICLE command.
939 (while (setq article (pop articles))
940 (nntp-send-command
941 nil
942 "ARTICLE" (if (numberp article)
943 (int-to-string article)
944 ;; `articles' is either a list of article numbers
945 ;; or a list of article IDs.
946 article))
947 (incf count)
948 ;; Every 400 requests we have to read the stream in
949 ;; order to avoid deadlocks.
950 (when (or (null articles) ;All requests have been sent.
951 (zerop (% count nntp-maximum-request)))
952 (nntp-accept-response)
953 (while (progn
954 (set-buffer buf)
955 (goto-char last-point)
956 ;; Count replies.
957 (while (nntp-next-result-arrived-p)
958 (aset map received (cons (aref map received) (point)))
959 (setq last-point (point))
960 (incf received))
961 (< received count))
962 ;; If number of headers is greater than 100, give
963 ;; informative messages.
964 (and (numberp nntp-large-newsgroup)
965 (> number nntp-large-newsgroup)
966 (zerop (% received 20))
967 (nnheader-message 6 "NNTP: Receiving articles... %d%%"
968 (/ (* received 100) number)))
969 (nntp-accept-response))))
970 (and (numberp nntp-large-newsgroup)
971 (> number nntp-large-newsgroup)
972 (nnheader-message 6 "NNTP: Receiving articles...done"))
973
974 ;; Now we have all the responses. We go through the results,
975 ;; wash it and copy it over to the server buffer.
976 (set-buffer nntp-server-buffer)
977 (erase-buffer)
978 (setq last-point (point-min))
979 (mapcar
980 (lambda (entry)
981 (narrow-to-region
982 (setq point (goto-char (point-max)))
983 (progn
9f5e78f7 984 (nnheader-insert-buffer-substring buf last-point (cdr entry))
23f87bed
MB
985 (point-max)))
986 (setq last-point (cdr entry))
987 (nntp-decode-text)
988 (widen)
989 (cons (car entry) point))
990 map)))))
eec82323 991
eec82323
LMI
992(defun nntp-try-list-active (group)
993 (nntp-list-active-group group)
ed075cb4 994 (with-current-buffer nntp-server-buffer
eec82323
LMI
995 (goto-char (point-min))
996 (cond ((or (eobp)
997 (looking-at "5[0-9]+"))
998 (setq nntp-server-list-active-group nil))
999 (t
1000 (setq nntp-server-list-active-group t)))))
1001
1002(deffoo nntp-list-active-group (group &optional server)
16409b0b 1003 "Return the active info on GROUP (which can be a regexp)."
23f87bed
MB
1004 (nntp-with-open-group
1005 nil server
1006 (nntp-send-command "^\\.*\r?\n" "LIST ACTIVE" group)))
16409b0b
GM
1007
1008(deffoo nntp-request-group-articles (group &optional server)
1009 "Return the list of existing articles in GROUP."
23f87bed
MB
1010 (nntp-with-open-group
1011 nil server
1012 (nntp-send-command "^\\.*\r?\n" "LISTGROUP" group)))
eec82323
LMI
1013
1014(deffoo nntp-request-article (article &optional group server buffer command)
23f87bed 1015 (nntp-with-open-group
b87f32fc 1016 group server
23f87bed
MB
1017 (when (nntp-send-command-and-decode
1018 "\r?\n\\.\r?\n" "ARTICLE"
1019 (if (numberp article) (int-to-string article) article))
b87f32fc
G
1020 (when (and buffer
1021 (not (equal buffer nntp-server-buffer)))
1022 (with-current-buffer nntp-server-buffer
1023 (copy-to-buffer buffer (point-min) (point-max))))
1024 (nntp-find-group-and-number group))))
eec82323
LMI
1025
1026(deffoo nntp-request-head (article &optional group server)
23f87bed
MB
1027 (nntp-with-open-group
1028 group server
1029 (when (nntp-send-command
1030 "\r?\n\\.\r?\n" "HEAD"
1031 (if (numberp article) (int-to-string article) article))
1032 (prog1
1033 (nntp-find-group-and-number group)
1034 (nntp-decode-text)))))
eec82323
LMI
1035
1036(deffoo nntp-request-body (article &optional group server)
23f87bed
MB
1037 (nntp-with-open-group
1038 group server
1039 (nntp-send-command-and-decode
1040 "\r?\n\\.\r?\n" "BODY"
1041 (if (numberp article) (int-to-string article) article))))
eec82323 1042
286c4fc2 1043(deffoo nntp-request-group (group &optional server dont-check info)
e84b4b86 1044 (nntp-with-open-group
23f87bed
MB
1045 nil server
1046 (when (nntp-send-command "^[245].*\n" "GROUP" group)
1047 (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
1048 (setcar (cddr entry) group)))))
eec82323
LMI
1049
1050(deffoo nntp-close-group (group &optional server)
1051 t)
1052
1053(deffoo nntp-server-opened (&optional server)
1054 "Say whether a connection to SERVER has been opened."
1055 (and (nnoo-current-server-p 'nntp server)
1056 nntp-server-buffer
1057 (gnus-buffer-live-p nntp-server-buffer)
1058 (nntp-find-connection nntp-server-buffer)))
1059
1060(deffoo nntp-open-server (server &optional defs connectionless)
1061 (nnheader-init-server-buffer)
1062 (if (nntp-server-opened server)
1063 t
1064 (when (or (stringp (car defs))
1065 (numberp (car defs)))
1066 (setq defs (cons (list 'nntp-port-number (car defs)) (cdr defs))))
1067 (unless (assq 'nntp-address defs)
1068 (setq defs (append defs (list (list 'nntp-address server)))))
1069 (nnoo-change-server 'nntp server defs)
20a673b2
KY
1070 (if connectionless
1071 t
eec82323
LMI
1072 (or (nntp-find-connection nntp-server-buffer)
1073 (nntp-open-connection nntp-server-buffer)))))
1074
1075(deffoo nntp-close-server (&optional server)
1076 (nntp-possibly-change-group nil server t)
6748645f
LMI
1077 (let ((process (nntp-find-connection nntp-server-buffer)))
1078 (while process
eec82323 1079 (when (memq (process-status process) '(open run))
6748645f
LMI
1080 (ignore-errors
1081 (nntp-send-string process "QUIT")
1082 (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
1083 ;; Ok, this is evil, but when using telnet and stuff
1084 ;; as the connection method, it's important that the
1085 ;; QUIT command actually is sent out before we kill
1086 ;; the process.
1087 (sleep-for 1))))
16409b0b 1088 (nntp-kill-buffer (process-buffer process))
6748645f 1089 (setq process (car (pop nntp-connection-alist))))
eec82323
LMI
1090 (nnoo-close-server 'nntp)))
1091
1092(deffoo nntp-request-close ()
1093 (let (process)
1094 (while (setq process (pop nntp-connection-list))
1095 (when (memq (process-status process) '(open run))
eec82323 1096 (ignore-errors
6748645f
LMI
1097 (nntp-send-string process "QUIT")
1098 (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
1099 ;; Ok, this is evil, but when using telnet and stuff
1100 ;; as the connection method, it's important that the
1101 ;; QUIT command actually is sent out before we kill
1102 ;; the process.
1103 (sleep-for 1))))
16409b0b 1104 (nntp-kill-buffer (process-buffer process)))))
eec82323
LMI
1105
1106(deffoo nntp-request-list (&optional server)
23f87bed
MB
1107 (nntp-with-open-group
1108 nil server
1109 (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST")))
eec82323
LMI
1110
1111(deffoo nntp-request-list-newsgroups (&optional server)
23f87bed
MB
1112 (nntp-with-open-group
1113 nil server
1114 (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS")))
eec82323
LMI
1115
1116(deffoo nntp-request-newgroups (date &optional server)
23f87bed
MB
1117 (nntp-with-open-group
1118 nil server
ed075cb4 1119 (with-current-buffer nntp-server-buffer
23f87bed
MB
1120 (let* ((time (date-to-time date))
1121 (ls (- (cadr time) (nth 8 (decode-time time)))))
1122 (cond ((< ls 0)
1123 (setcar time (1- (car time)))
1124 (setcar (cdr time) (+ ls 65536)))
1125 ((>= ls 65536)
1126 (setcar time (1+ (car time)))
1127 (setcar (cdr time) (- ls 65536)))
1128 (t
1129 (setcar (cdr time) ls)))
1130 (prog1
1131 (nntp-send-command
1132 "^\\.\r?\n" "NEWGROUPS"
1133 (format-time-string "%y%m%d %H%M%S" time)
1134 "GMT")
1135 (nntp-decode-text))))))
eec82323
LMI
1136
1137(deffoo nntp-request-post (&optional server)
23f87bed
MB
1138 (nntp-with-open-group
1139 nil server
1140 (when (nntp-send-command "^[23].*\r?\n" "POST")
1141 (let ((response (with-current-buffer nntp-server-buffer
1142 nntp-process-response))
1143 server-id)
1144 (when (and response
1145 (string-match "^[23].*\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
1146 response))
1147 (setq server-id (match-string 1 response))
1148 (narrow-to-region (goto-char (point-min))
1149 (if (search-forward "\n\n" nil t)
1150 (1- (point))
1151 (point-max)))
1152 (unless (mail-fetch-field "Message-ID")
1153 (goto-char (point-min))
1154 (insert "Message-ID: " server-id "\n"))
1155 (widen))
1156 (run-hooks 'nntp-prepare-post-hook)
1157 (nntp-send-buffer "^[23].*\n")))))
eec82323
LMI
1158
1159(deffoo nntp-request-type (group article)
1160 'news)
1161
1162(deffoo nntp-asynchronous-p ()
1163 t)
1164
01c52d31 1165
eec82323
LMI
1166;;; Hooky functions.
1167
1168(defun nntp-send-mode-reader ()
1169 "Send the MODE READER command to the nntp server.
1170This function is supposed to be called from `nntp-server-opened-hook'.
1171It will make innd servers spawn an nnrpd process to allow actual article
1172reading."
16409b0b 1173 (nntp-send-command "^.*\n" "MODE READER"))
eec82323 1174
c8ccffb1 1175(declare-function netrc-parse "netrc" (&optional file))
aa8f8277
GM
1176(declare-function netrc-machine "netrc"
1177 (list machine &optional port defaultport))
1178(declare-function netrc-get "netrc" (alist type))
1179
6748645f 1180(defun nntp-send-authinfo (&optional send-if-force)
eec82323 1181 "Send the AUTHINFO to the nntp server.
6748645f
LMI
1182It will look in the \"~/.authinfo\" file for matching entries. If
1183nothing suitable is found there, it will prompt for a user name
1184and a password.
1185
1186If SEND-IF-FORCE, only send authinfo to the server if the
1187.authinfo file has the FORCE token."
aa8f8277 1188 (require 'netrc)
01c52d31
MB
1189 (let* ((list (netrc-parse nntp-authinfo-file))
1190 (alist (netrc-machine list nntp-address "nntp"))
b8e0f0cd 1191 (auth-info
50fc698e
KY
1192 (nth 0 (auth-source-search
1193 :max 1
b63e0a68
KY
1194 :host (list nntp-address (nnoo-current-server 'nntp))
1195 :port `("119" "nntp" ,(format "%s" nntp-port-number)
1196 "563" "nntps" "snews"))))
b8e0f0cd 1197 (auth-user (plist-get auth-info :user))
638f517c 1198 (auth-force (plist-get auth-info :force))
b8e0f0cd
G
1199 (auth-passwd (plist-get auth-info :secret))
1200 (auth-passwd (if (functionp auth-passwd)
1201 (funcall auth-passwd)
1202 auth-passwd))
638f517c
TZ
1203 (force (or (netrc-get alist "force")
1204 nntp-authinfo-force
1205 auth-force))
8abf1b22 1206 (user (or
e952b711 1207 ;; this is preferred to netrc-*
3b36c17e 1208 auth-user
8abf1b22 1209 (netrc-get alist "login")
e952b711
MB
1210 nntp-authinfo-user))
1211 (passwd (or
1212 ;; this is preferred to netrc-*
3b36c17e 1213 auth-passwd
e952b711 1214 (netrc-get alist "password"))))
6748645f
LMI
1215 (when (or (not send-if-force)
1216 force)
1217 (unless user
1218 (setq user (read-string (format "NNTP (%s) user name: " nntp-address))
1219 nntp-authinfo-user user))
1220 (unless (member user '(nil ""))
1221 (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user)
1222 (when t ;???Should check if AUTHINFO succeeded
16409b0b
GM
1223 (nntp-send-command
1224 "^2.*\r?\n" "AUTHINFO PASS"
1225 (or passwd
1226 nntp-authinfo-password
1227 (setq nntp-authinfo-password
23f87bed
MB
1228 (read-passwd (format "NNTP (%s@%s) password: "
1229 user nntp-address))))))))))
6748645f 1230
eec82323
LMI
1231;;; Internal functions.
1232
6748645f
LMI
1233(defun nntp-handle-authinfo (process)
1234 "Take care of an authinfo response from the server."
1235 (let ((last nntp-last-command))
1236 (funcall nntp-authinfo-function)
1237 ;; We have to re-send the function that was interrupted by
1238 ;; the authinfo request.
01c52d31 1239 (nntp-erase-buffer nntp-server-buffer)
6748645f
LMI
1240 (nntp-send-string process last)))
1241
eec82323
LMI
1242(defun nntp-make-process-buffer (buffer)
1243 "Create a new, fresh buffer usable for nntp process connections."
ed075cb4
SM
1244 (with-current-buffer
1245 (generate-new-buffer
1246 (format " *server %s %s %s*"
1247 nntp-address nntp-port-number
1248 (gnus-buffer-exists-p buffer)))
1428d46b 1249 (mm-disable-multibyte)
eec82323
LMI
1250 (set (make-local-variable 'after-change-functions) nil)
1251 (set (make-local-variable 'nntp-process-wait-for) nil)
1252 (set (make-local-variable 'nntp-process-callback) nil)
1253 (set (make-local-variable 'nntp-process-to-buffer) nil)
1254 (set (make-local-variable 'nntp-process-start-point) nil)
1255 (set (make-local-variable 'nntp-process-decode) nil)
7e67562f 1256 (set (make-local-variable 'nntp-retrieval-in-progress) nil)
eec82323
LMI
1257 (current-buffer)))
1258
1259(defun nntp-open-connection (buffer)
1260 "Open a connection to PORT on ADDRESS delivering output to BUFFER."
1261 (run-hooks 'nntp-prepare-server-hook)
1262 (let* ((pbuffer (nntp-make-process-buffer buffer))
6748645f
LMI
1263 (timer
1264 (and nntp-connection-timeout
01c52d31 1265 (run-at-time
6748645f
LMI
1266 nntp-connection-timeout nil
1267 `(lambda ()
16409b0b 1268 (nntp-kill-buffer ,pbuffer)))))
eec82323 1269 (process
ed797193 1270 (condition-case err
89b163db
G
1271 (let ((coding-system-for-read 'binary)
1272 (coding-system-for-write 'binary)
e742e117
CY
1273 (map '((nntp-open-network-stream network)
1274 (network-only plain) ; compat
1275 (nntp-open-plain-stream plain)
ed797193
G
1276 (nntp-open-ssl-stream tls)
1277 (nntp-open-tls-stream tls))))
1278 (if (assoc nntp-open-connection-function map)
f2eefd24
CY
1279 (open-protocol-stream
1280 "nntpd" pbuffer nntp-address nntp-port-number
e742e117 1281 :type (cadr (assoc nntp-open-connection-function map))
f2eefd24 1282 :end-of-command "^\\([2345]\\|[.]\\).*\n"
b9989bfa 1283 :capability-command "HELP\r\n"
f2eefd24
CY
1284 :success "^3"
1285 :starttls-function
1286 (lambda (capabilities)
1287 (if (not (string-match "STARTTLS" capabilities))
1288 nil
1289 "STARTTLS\r\n")))
ed797193
G
1290 (funcall nntp-open-connection-function pbuffer)))
1291 (error
f2eefd24 1292 (nnheader-report 'nntp ">>> %s" err))
2eebe218 1293 (quit
01c52d31 1294 (message "Quit opening connection to %s" nntp-address)
2eebe218
DL
1295 (nntp-kill-buffer pbuffer)
1296 (signal 'quit nil)
1297 nil))))
6748645f
LMI
1298 (when timer
1299 (nnheader-cancel-timer timer))
f2eefd24
CY
1300 (when (and process
1301 (not (memq (process-status process) '(open run))))
02e8d7e9
LI
1302 (with-current-buffer pbuffer
1303 (goto-char (point-min))
1304 (nnheader-report 'nntp "Error when connecting: %s"
1305 (buffer-substring (point) (line-end-position))))
f2eefd24 1306 (setq process nil))
23f87bed
MB
1307 (unless process
1308 (nntp-kill-buffer pbuffer))
6748645f
LMI
1309 (when (and (buffer-name pbuffer)
1310 process)
7887e229
KY
1311 (when (and (fboundp 'set-network-process-option) ;; Unavailable in XEmacs.
1312 (fboundp 'process-type) ;; Emacs 22 doesn't provide it.
68b5d5ee
SM
1313 (eq (process-type process) 'network))
1314 ;; Use TCP-keepalive so that connections that pass through a NAT router
1315 ;; don't hang when left idle.
1316 (set-network-process-option process :keepalive t))
4a43ee9b 1317 (gnus-set-process-query-on-exit-flag process nil)
23f87bed
MB
1318 (if (and (nntp-wait-for process "^2.*\n" buffer nil t)
1319 (memq (process-status process) '(open run)))
eec82323
LMI
1320 (prog1
1321 (caar (push (list process buffer nil) nntp-connection-alist))
1322 (push process nntp-connection-list)
ed075cb4 1323 (with-current-buffer pbuffer
eec82323
LMI
1324 (nntp-read-server-type)
1325 (erase-buffer)
1326 (set-buffer nntp-server-buffer)
1327 (let ((nnheader-callback-function nil))
6748645f
LMI
1328 (run-hooks 'nntp-server-opened-hook)
1329 (nntp-send-authinfo t))))
16409b0b 1330 (nntp-kill-buffer (process-buffer process))
eec82323
LMI
1331 nil))))
1332
eec82323
LMI
1333(defun nntp-read-server-type ()
1334 "Find out what the name of the server we have connected to is."
1335 ;; Wait for the status string to arrive.
1336 (setq nntp-server-type (buffer-string))
01c52d31 1337 (let ((case-fold-search t))
eec82323 1338 ;; Run server-specific commands.
01c52d31 1339 (dolist (entry nntp-server-action-alist)
eec82323
LMI
1340 (when (string-match (car entry) nntp-server-type)
1341 (if (and (listp (cadr entry))
1342 (not (eq 'lambda (caadr entry))))
1343 (eval (cadr entry))
1344 (funcall (cadr entry)))))))
1345
16409b0b 1346(defun nntp-async-wait (process wait-for buffer decode callback)
ed075cb4 1347 (with-current-buffer (process-buffer process)
16409b0b
GM
1348 (unless nntp-inside-change-function
1349 (erase-buffer))
1350 (setq nntp-process-wait-for wait-for
1351 nntp-process-to-buffer buffer
1352 nntp-process-decode decode
1353 nntp-process-callback callback
1354 nntp-process-start-point (point-max))
6b8382e4 1355 (setq after-change-functions '(nntp-after-change-function))))
16409b0b 1356
16409b0b
GM
1357(defun nntp-async-stop (proc)
1358 (setq nntp-async-process-list (delq proc nntp-async-process-list))
1359 (when (and nntp-async-timer (not nntp-async-process-list))
1360 (nnheader-cancel-timer nntp-async-timer)
1361 (setq nntp-async-timer nil)))
1362
1363(defun nntp-after-change-function (beg end len)
1364 (unwind-protect
1365 ;; we only care about insertions at eob
1366 (when (and (eq 0 len) (eq (point-max) end))
1367 (save-match-data
1368 (let ((proc (get-buffer-process (current-buffer))))
1369 (when proc
1370 (nntp-async-trigger proc)))))
1371 ;; any throw from after-change-functions will leave it
1372 ;; set to nil. so we reset it here, if necessary.
1373 (when quit-flag
1374 (setq after-change-functions '(nntp-after-change-function)))))
1375
1376(defun nntp-async-trigger (process)
ed075cb4 1377 (with-current-buffer (process-buffer process)
16409b0b
GM
1378 (when nntp-process-callback
1379 ;; do we have an error message?
1380 (goto-char nntp-process-start-point)
1381 (if (memq (following-char) '(?4 ?5))
1382 ;; wants credentials?
1383 (if (looking-at "480")
2eebe218 1384 (nntp-handle-authinfo process)
16409b0b
GM
1385 ;; report error message.
1386 (nntp-snarf-error-message)
1387 (nntp-do-callback nil))
1388
1389 ;; got what we expect?
1390 (goto-char (point-max))
1391 (when (re-search-backward
1392 nntp-process-wait-for nntp-process-start-point t)
23f87bed
MB
1393 (let ((response (match-string 0)))
1394 (with-current-buffer nntp-server-buffer
1395 (setq nntp-process-response response)))
16409b0b
GM
1396 (nntp-async-stop process)
1397 ;; convert it.
6748645f 1398 (when (gnus-buffer-exists-p nntp-process-to-buffer)
16409b0b
GM
1399 (let ((buf (current-buffer))
1400 (start nntp-process-start-point)
1401 (decode nntp-process-decode))
ed075cb4 1402 (with-current-buffer nntp-process-to-buffer
eec82323 1403 (goto-char (point-max))
16409b0b
GM
1404 (save-restriction
1405 (narrow-to-region (point) (point))
9f5e78f7 1406 (nnheader-insert-buffer-substring buf start)
16409b0b
GM
1407 (when decode
1408 (nntp-decode-text))))))
1409 ;; report it.
1410 (goto-char (point-max))
1411 (nntp-do-callback
1412 (buffer-name (get-buffer nntp-process-to-buffer))))))))
1413
1414(defun nntp-do-callback (arg)
1415 (let ((callback nntp-process-callback)
1416 (nntp-inside-change-function t))
1417 (setq nntp-process-callback nil)
1418 (funcall callback arg)))
eec82323
LMI
1419
1420(defun nntp-snarf-error-message ()
1421 "Save the error message in the current buffer."
1422 (let ((message (buffer-string)))
1423 (while (string-match "[\r\n]+" message)
1424 (setq message (replace-match " " t t message)))
04db63bc 1425 (nnheader-report 'nntp "%s" message)
eec82323
LMI
1426 message))
1427
23f87bed 1428(defun nntp-accept-process-output (process)
eec82323 1429 "Wait for output from PROCESS and message some dots."
01c52d31
MB
1430 (with-current-buffer (or (nntp-find-connection-buffer nntp-server-buffer)
1431 nntp-server-buffer)
c7279817 1432 (let ((len (/ (buffer-size) 1024))
eec82323
LMI
1433 message-log-max)
1434 (unless (< len 10)
1435 (setq nntp-have-messaged t)
1436 (nnheader-message 7 "nntp read: %dk" len)))
01c52d31
MB
1437 (prog1
1438 (nnheader-accept-process-output process)
1439 ;; accept-process-output may update status of process to indicate
1440 ;; that the server has closed the connection. This MUST be
1441 ;; handled here as the buffer restored by the save-excursion may
1442 ;; be the process's former output buffer (i.e. now killed)
1443 (or (and process
1444 (memq (process-status process) '(open run)))
1445 (nntp-report "Server closed connection")))))
eec82323
LMI
1446
1447(defun nntp-accept-response ()
1448 "Wait for output from the process that outputs to BUFFER."
1449 (nntp-accept-process-output (nntp-find-connection nntp-server-buffer)))
1450
1451(defun nntp-possibly-change-group (group server &optional connectionless)
1452 (let ((nnheader-callback-function nil))
1453 (when server
1454 (or (nntp-server-opened server)
1455 (nntp-open-server server nil connectionless)))
1456
1457 (unless connectionless
1458 (or (nntp-find-connection nntp-server-buffer)
1459 (nntp-open-connection nntp-server-buffer))))
1460
1461 (when group
1462 (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
23f87bed
MB
1463 (cond ((not entry)
1464 (nntp-report "Server closed connection"))
1465 ((not (equal group (caddr entry)))
ed075cb4 1466 (with-current-buffer (process-buffer (car entry))
23f87bed
MB
1467 (erase-buffer)
1468 (nntp-send-command "^[245].*\n" "GROUP" group)
1469 (setcar (cddr entry) group)
1470 (erase-buffer)
01c52d31 1471 (nntp-erase-buffer nntp-server-buffer)))))))
eec82323
LMI
1472
1473(defun nntp-decode-text (&optional cr-only)
1474 "Decode the text in the current buffer."
1475 (goto-char (point-min))
1476 (while (search-forward "\r" nil t)
1477 (delete-char -1))
1478 (unless cr-only
1479 ;; Remove trailing ".\n" end-of-transfer marker.
1480 (goto-char (point-max))
1481 (forward-line -1)
1482 (when (looking-at ".\n")
1483 (delete-char 2))
1484 ;; Delete status line.
1485 (goto-char (point-min))
2eebe218
DL
1486 (while (looking-at "[1-5][0-9][0-9] .*\n")
1487 ;; For some unknown reason, there is more than one status line.
1488 (delete-region (point) (progn (forward-line 1) (point))))
eec82323
LMI
1489 ;; Remove "." -> ".." encoding.
1490 (while (search-forward "\n.." nil t)
1491 (delete-char -1))))
1492
1493(defun nntp-encode-text ()
1494 "Encode the text in the current buffer."
1495 (save-excursion
1496 ;; Replace "." at beginning of line with "..".
1497 (goto-char (point-min))
1498 (while (re-search-forward "^\\." nil t)
1499 (insert "."))
1500 (goto-char (point-max))
1501 ;; Insert newline at the end of the buffer.
1502 (unless (bolp)
1503 (insert "\n"))
1504 ;; Insert `.' at end of buffer (end of text mark).
1505 (goto-char (point-max))
45926d06
RS
1506 (insert ".\n")
1507 (goto-char (point-min))
1508 (while (not (eobp))
1509 (end-of-line)
1510 (delete-char 1)
1511 (insert nntp-end-of-line))))
eec82323
LMI
1512
1513(defun nntp-retrieve-headers-with-xover (articles &optional fetch-old)
1514 (set-buffer nntp-server-buffer)
1515 (erase-buffer)
1516 (cond
1517
1518 ;; This server does not talk NOV.
1519 ((not nntp-server-xover)
1520 nil)
1521
1522 ;; We don't care about gaps.
1523 ((or (not nntp-nov-gap)
1524 fetch-old)
1525 (nntp-send-xover-command
1526 (if fetch-old
1527 (if (numberp fetch-old)
1528 (max 1 (- (car articles) fetch-old))
1529 1)
1530 (car articles))
1531 (car (last articles)) 'wait)
1532
1533 (goto-char (point-min))
16409b0b 1534 (when (looking-at "[1-5][0-9][0-9] .*\n")
eec82323
LMI
1535 (delete-region (point) (progn (forward-line 1) (point))))
1536 (while (search-forward "\r" nil t)
1537 (replace-match "" t t))
1538 (goto-char (point-max))
1539 (forward-line -1)
1540 (when (looking-at "\\.")
1541 (delete-region (point) (progn (forward-line 1) (point)))))
1542
1543 ;; We do it the hard way. For each gap, an XOVER command is sent
1544 ;; to the server. We do not wait for a reply from the server, we
1545 ;; just send them off as fast as we can. That means that we have
1546 ;; to count the number of responses we get back to find out when we
1547 ;; have gotten all we asked for.
1548 ((numberp nntp-nov-gap)
1549 (let ((count 0)
1550 (received 0)
16409b0b
GM
1551 last-point
1552 in-process-buffer-p
eec82323 1553 (buf nntp-server-buffer)
16409b0b 1554 (process-buffer (nntp-find-connection-buffer nntp-server-buffer))
23f87bed 1555 first last status)
eec82323
LMI
1556 ;; We have to check `nntp-server-xover'. If it gets set to nil,
1557 ;; that means that the server does not understand XOVER, but we
1558 ;; won't know that until we try.
1559 (while (and nntp-server-xover articles)
1560 (setq first (car articles))
1561 ;; Search forward until we find a gap, or until we run out of
1562 ;; articles.
1563 (while (and (cdr articles)
1564 (< (- (nth 1 articles) (car articles)) nntp-nov-gap))
1565 (setq articles (cdr articles)))
1566
16409b0b 1567 (setq in-process-buffer-p (stringp nntp-server-xover))
23f87bed
MB
1568 (nntp-send-xover-command first (setq last (car articles)))
1569 (setq articles (cdr articles))
a1506d29 1570
16409b0b
GM
1571 (when (and nntp-server-xover in-process-buffer-p)
1572 ;; Don't count tried request.
1573 (setq count (1+ count))
a1506d29 1574
eec82323
LMI
1575 ;; Every 400 requests we have to read the stream in
1576 ;; order to avoid deadlocks.
1577 (when (or (null articles) ;All requests have been sent.
23f87bed 1578 (= 1 (% count nntp-maximum-request)))
16409b0b
GM
1579
1580 (nntp-accept-response)
1581 ;; On some Emacs versions the preceding function has a
1582 ;; tendency to change the buffer. Perhaps. It's quite
1583 ;; difficult to reproduce, because it only seems to happen
1584 ;; once in a blue moon.
1585 (set-buffer process-buffer)
eec82323 1586 (while (progn
16409b0b 1587 (goto-char (or last-point (point-min)))
eec82323 1588 ;; Count replies.
23f87bed
MB
1589 (while (re-search-forward "^\\([0-9][0-9][0-9]\\) .*\n"
1590 nil t)
1591 (incf received)
1592 (setq status (match-string 1))
1593 (if (string-match "^[45]" status)
1594 (setq status 'error)
1595 (setq status 'ok)))
eec82323 1596 (setq last-point (point))
23f87bed
MB
1597 (or (< received count)
1598 (if (eq status 'error)
1599 nil
1600 ;; I haven't started reading the final response
1601 (progn
1602 (goto-char (point-max))
1603 (forward-line -1)
1604 (not (looking-at "^\\.\r?\n"))))))
1605 ;; I haven't read the end of the final response
16409b0b 1606 (nntp-accept-response)
23f87bed
MB
1607 (set-buffer process-buffer))))
1608
1609 ;; Some nntp servers seem to have an extension to the XOVER
1610 ;; extension. On these servers, requesting an article range
97610156 1611 ;; preceding the active range does not return an error as
23f87bed
MB
1612 ;; specified in the RFC. What we instead get is the NOV entry
1613 ;; for the first available article. Obviously, a client can
1614 ;; use that entry to avoid making unnecessary requests. The
1615 ;; only problem is for a client that assumes that the response
40ba43b4 1616 ;; will always be within the requested range. For such a
23f87bed
MB
1617 ;; client, we can get N copies of the same entry (one for each
1618 ;; XOVER command sent to the server).
1619
1620 (when (<= count 1)
1621 (goto-char (point-min))
1622 (when (re-search-forward "^[0-9][0-9][0-9] .*\n\\([0-9]+\\)" nil t)
e9bd5782 1623 (let ((low-limit (string-to-number
e84b4b86 1624 (buffer-substring (match-beginning 1)
23f87bed
MB
1625 (match-end 1)))))
1626 (while (and articles (<= (car articles) low-limit))
1627 (setq articles (cdr articles))))))
1628 (set-buffer buf))
eec82323
LMI
1629
1630 (when nntp-server-xover
16409b0b 1631 (when in-process-buffer-p
16409b0b
GM
1632 (set-buffer buf)
1633 (goto-char (point-max))
9f5e78f7 1634 (nnheader-insert-buffer-substring process-buffer)
16409b0b
GM
1635 (set-buffer process-buffer)
1636 (erase-buffer)
1637 (set-buffer buf))
eec82323
LMI
1638
1639 ;; We remove any "." lines and status lines.
1640 (goto-char (point-min))
1641 (while (search-forward "\r" nil t)
1642 (delete-char -1))
1643 (goto-char (point-min))
1644 (delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ")
eec82323
LMI
1645 t))))
1646
1647 nntp-server-xover)
1648
1649(defun nntp-send-xover-command (beg end &optional wait-for-reply)
1650 "Send the XOVER command to the server."
1651 (let ((range (format "%d-%d" beg end))
1652 (nntp-inhibit-erase t))
1653 (if (stringp nntp-server-xover)
1654 ;; If `nntp-server-xover' is a string, then we just send this
1655 ;; command.
1656 (if wait-for-reply
1657 (nntp-send-command-nodelete
1658 "\r?\n\\.\r?\n" nntp-server-xover range)
1659 ;; We do not wait for the reply.
16409b0b 1660 (nntp-send-command-nodelete nil nntp-server-xover range))
eec82323
LMI
1661 (let ((commands nntp-xover-commands))
1662 ;; `nntp-xover-commands' is a list of possible XOVER commands.
1663 ;; We try them all until we get at positive response.
1664 (while (and commands (eq nntp-server-xover 'try))
1665 (nntp-send-command-nodelete "\r?\n\\.\r?\n" (car commands) range)
ed075cb4 1666 (with-current-buffer nntp-server-buffer
eec82323
LMI
1667 (goto-char (point-min))
1668 (and (looking-at "[23]") ; No error message.
1669 ;; We also have to look at the lines. Some buggy
1670 ;; servers give back simple lines with just the
1671 ;; article number. How... helpful.
1672 (progn
1673 (forward-line 1)
5cedca8d
MB
1674 ;; More text after number, or a dot.
1675 (looking-at "[0-9]+\t...\\|\\.\r?\n"))
eec82323
LMI
1676 (setq nntp-server-xover (car commands))))
1677 (setq commands (cdr commands)))
1678 ;; If none of the commands worked, we disable XOVER.
1679 (when (eq nntp-server-xover 'try)
01c52d31
MB
1680 (nntp-erase-buffer nntp-server-buffer)
1681 (setq nntp-server-xover nil))
23f87bed 1682 nntp-server-xover))))
eec82323 1683
23f87bed
MB
1684(defun nntp-find-group-and-number (&optional group)
1685 (save-excursion
1686 (save-restriction
ed075cb4 1687 ;; FIXME: This is REALLY FISHY: set-buffer after save-restriction?!?
23f87bed
MB
1688 (set-buffer nntp-server-buffer)
1689 (narrow-to-region (goto-char (point-min))
1690 (or (search-forward "\n\n" nil t) (point-max)))
1691 (goto-char (point-min))
1692 ;; We first find the number by looking at the status line.
1693 (let ((number (and (looking-at "2[0-9][0-9] +\\([0-9]+\\) ")
e9bd5782 1694 (string-to-number
23f87bed
MB
1695 (buffer-substring (match-beginning 1)
1696 (match-end 1)))))
1697 newsgroups xref)
1698 (and number (zerop number) (setq number nil))
1699 (if number
1700 ;; Then we find the group name.
1701 (setq group
1702 (cond
1703 ;; If there is only one group in the Newsgroups
1704 ;; header, then it seems quite likely that this
1705 ;; article comes from that group, I'd say.
1706 ((and (setq newsgroups
1707 (mail-fetch-field "newsgroups"))
1708 (not (string-match "," newsgroups)))
1709 newsgroups)
1710 ;; If there is more than one group in the
1711 ;; Newsgroups header, then the Xref header should
1712 ;; be filled out. We hazard a guess that the group
1713 ;; that has this article number in the Xref header
1714 ;; is the one we are looking for. This might very
1715 ;; well be wrong if this article happens to have
1716 ;; the same number in several groups, but that's
1717 ;; life.
1718 ((and (setq xref (mail-fetch-field "xref"))
1719 number
1720 (string-match
1721 (format "\\([^ :]+\\):%d" number) xref))
1722 (match-string 1 xref))
1723 (t "")))
1724 (cond
37cc095b
MB
1725 ((and (not nntp-xref-number-is-evil)
1726 (setq xref (mail-fetch-field "xref"))
23f87bed
MB
1727 (string-match
1728 (if group
1729 (concat "\\(" (regexp-quote group) "\\):\\([0-9]+\\)")
1730 "\\([^ :]+\\):\\([0-9]+\\)")
1731 xref))
1732 (setq group (match-string 1 xref)
e9bd5782 1733 number (string-to-number (match-string 2 xref))))
23f87bed
MB
1734 ((and (setq newsgroups
1735 (mail-fetch-field "newsgroups"))
1736 (not (string-match "," newsgroups)))
1737 (setq group newsgroups))
1738 (group)
1739 (t (setq group ""))))
1740 (when (string-match "\r" group)
1741 (setq group (substring group 0 (match-beginning 0))))
1742 (cons group number)))))
eec82323
LMI
1743
1744(defun nntp-wait-for-string (regexp)
1745 "Wait until string arrives in the buffer."
23f87bed
MB
1746 (let ((buf (current-buffer))
1747 proc)
eec82323 1748 (goto-char (point-min))
23f87bed
MB
1749 (while (and (setq proc (get-buffer-process buf))
1750 (memq (process-status proc) '(open run))
1751 (not (re-search-forward regexp nil t)))
d832b437 1752 (accept-process-output proc 0.1)
eec82323
LMI
1753 (set-buffer buf)
1754 (goto-char (point-min)))))
1755
23f87bed
MB
1756
1757;; ==========================================================================
1758;; Obsolete nntp-open-* connection methods -- drv
1759;; ==========================================================================
1760
1761(defvoo nntp-open-telnet-envuser nil
1762 "*If non-nil, telnet session (client and server both) will support the ENVIRON option and not prompt for login name.")
1763
1764(defvoo nntp-telnet-shell-prompt "bash\\|\$ *\r?$\\|> *\r?"
1765 "*Regular expression to match the shell prompt on the remote machine.")
1766
1767(defvoo nntp-rlogin-program "rsh"
1768 "*Program used to log in on remote machines.
1769The default is \"rsh\", but \"ssh\" is a popular alternative.")
1770
1771(defvoo nntp-rlogin-parameters '("telnet" "-8" "${NNTPSERVER:=news}" "nntp")
1772 "*Parameters to `nntp-open-rlogin'.
1773That function may be used as `nntp-open-connection-function'. In that
1774case, this list will be used as the parameter list given to rsh.")
1775
1776(defvoo nntp-rlogin-user-name nil
1777 "*User name on remote system when using the rlogin connect method.")
1778
1779(defvoo nntp-telnet-parameters
1780 '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp")
1781 "*Parameters to `nntp-open-telnet'.
1782That function may be used as `nntp-open-connection-function'. In that
1783case, this list will be executed as a command after logging in
1784via telnet.")
1785
1786(defvoo nntp-telnet-user-name nil
1787 "User name to log in via telnet with.")
1788
1789(defvoo nntp-telnet-passwd nil
1790 "Password to use to log in via telnet with.")
1791
990e2c2f
SM
1792(defun nntp-service-to-port (svc)
1793 (cond
1794 ((integerp svc) (number-to-string svc))
49b196a5 1795 ((string-match "\\`[0-9]+\\'" svc) svc)
990e2c2f
SM
1796 (t
1797 (with-temp-buffer
1798 (ignore-errors (insert-file-contents "/etc/services"))
1799 (goto-char (point-min))
1800 (if (re-search-forward (concat "^" (regexp-quote svc)
49b196a5 1801 "[ \t]+\\([0-9]+\\)/tcp"))
990e2c2f
SM
1802 (match-string 1)
1803 svc)))))
1804
eec82323 1805(defun nntp-open-telnet (buffer)
990e2c2f 1806 (with-current-buffer buffer
eec82323 1807 (erase-buffer)
a8151ef7
LMI
1808 (let ((proc (apply
1809 'start-process
1810 "nntpd" buffer nntp-telnet-command nntp-telnet-switches))
eec82323
LMI
1811 (case-fold-search t))
1812 (when (memq (process-status proc) '(open run))
2eebe218 1813 (nntp-wait-for-string "^r?telnet")
eec82323 1814 (process-send-string proc "set escape \^X\n")
6748645f
LMI
1815 (cond
1816 ((and nntp-open-telnet-envuser nntp-telnet-user-name)
1817 (process-send-string proc (concat "open " "-l" nntp-telnet-user-name
1818 nntp-address "\n")))
1819 (t
1820 (process-send-string proc (concat "open " nntp-address "\n"))))
1821 (cond
1822 ((not nntp-open-telnet-envuser)
1823 (nntp-wait-for-string "^\r*.?login:")
1824 (process-send-string
1825 proc (concat
1826 (or nntp-telnet-user-name
1827 (setq nntp-telnet-user-name (read-string "login: ")))
1828 "\n"))))
eec82323
LMI
1829 (nntp-wait-for-string "^\r*.?password:")
1830 (process-send-string
1831 proc (concat
1832 (or nntp-telnet-passwd
1833 (setq nntp-telnet-passwd
23f87bed 1834 (read-passwd "Password: ")))
eec82323 1835 "\n"))
6748645f 1836 (nntp-wait-for-string nntp-telnet-shell-prompt)
eec82323
LMI
1837 (process-send-string
1838 proc (concat (mapconcat 'identity nntp-telnet-parameters " ") "\n"))
6748645f 1839 (nntp-wait-for-string "^\r*20[01]")
eec82323
LMI
1840 (beginning-of-line)
1841 (delete-region (point-min) (point))
1842 (process-send-string proc "\^]")
2eebe218 1843 (nntp-wait-for-string "^r?telnet")
eec82323
LMI
1844 (process-send-string proc "mode character\n")
1845 (accept-process-output proc 1)
1846 (sit-for 1)
1847 (goto-char (point-min))
1848 (forward-line 1)
1849 (delete-region (point) (point-max)))
1850 proc)))
1851
1852(defun nntp-open-rlogin (buffer)
1853 "Open a connection to SERVER using rsh."
1854 (let ((proc (if nntp-rlogin-user-name
6748645f
LMI
1855 (apply 'start-process
1856 "nntpd" buffer nntp-rlogin-program
1857 nntp-address "-l" nntp-rlogin-user-name
1858 nntp-rlogin-parameters)
1859 (apply 'start-process
1860 "nntpd" buffer nntp-rlogin-program nntp-address
1861 nntp-rlogin-parameters))))
990e2c2f 1862 (with-current-buffer buffer
6748645f
LMI
1863 (nntp-wait-for-string "^\r*20[01]")
1864 (beginning-of-line)
1865 (delete-region (point-min) (point))
1866 proc)))
eec82323 1867
23f87bed
MB
1868
1869;; ==========================================================================
1870;; Replacements for the nntp-open-* functions -- drv
1871;; ==========================================================================
1872
1873(defun nntp-open-telnet-stream (buffer)
1874 "Open a nntp connection by telnet'ing the news server.
990e2c2f 1875`nntp-open-netcat-stream' is recommended in place of this function
ed075cb4 1876because it is more reliable.
23f87bed
MB
1877
1878Please refer to the following variables to customize the connection:
1879- `nntp-pre-command',
1880- `nntp-telnet-command',
1881- `nntp-telnet-switches',
1882- `nntp-address',
1883- `nntp-port-number',
1884- `nntp-end-of-line'."
1885 (let ((command `(,nntp-telnet-command
1886 ,@nntp-telnet-switches
9b3ebcb6 1887 ,nntp-address
990e2c2f 1888 ,(nntp-service-to-port nntp-port-number)))
23f87bed
MB
1889 proc)
1890 (and nntp-pre-command
1891 (push nntp-pre-command command))
1892 (setq proc (apply 'start-process "nntpd" buffer command))
ed075cb4 1893 (with-current-buffer buffer
23f87bed
MB
1894 (nntp-wait-for-string "^\r*20[01]")
1895 (beginning-of-line)
1896 (delete-region (point-min) (point))
1897 proc)))
1898
1899(defun nntp-open-via-rlogin-and-telnet (buffer)
1900 "Open a connection to an nntp server through an intermediate host.
1901First rlogin to the remote host, and then telnet the real news server
1902from there.
ed075cb4
SM
1903`nntp-open-via-rlogin-and-netcat' is recommended in place of this function
1904because it is more reliable.
23f87bed
MB
1905
1906Please refer to the following variables to customize the connection:
1907- `nntp-pre-command',
1908- `nntp-via-rlogin-command',
1909- `nntp-via-rlogin-command-switches',
1910- `nntp-via-user-name',
1911- `nntp-via-address',
1912- `nntp-telnet-command',
1913- `nntp-telnet-switches',
1914- `nntp-address',
1915- `nntp-port-number',
1916- `nntp-end-of-line'."
1917 (let ((command `(,nntp-via-address
1918 ,nntp-telnet-command
1919 ,@nntp-telnet-switches))
1920 proc)
1921 (when nntp-via-user-name
1922 (setq command `("-l" ,nntp-via-user-name ,@command)))
1923 (when nntp-via-rlogin-command-switches
1924 (setq command (append nntp-via-rlogin-command-switches command)))
1925 (push nntp-via-rlogin-command command)
1926 (and nntp-pre-command
1927 (push nntp-pre-command command))
1928 (setq proc (apply 'start-process "nntpd" buffer command))
ed075cb4 1929 (with-current-buffer buffer
23f87bed 1930 (nntp-wait-for-string "^r?telnet")
9b3ebcb6 1931 (process-send-string proc (concat "open " nntp-address " "
990e2c2f 1932 (nntp-service-to-port nntp-port-number)
9b3ebcb6 1933 "\n"))
23f87bed
MB
1934 (nntp-wait-for-string "^\r*20[01]")
1935 (beginning-of-line)
1936 (delete-region (point-min) (point))
1937 (process-send-string proc "\^]")
1938 (nntp-wait-for-string "^r?telnet")
1939 (process-send-string proc "mode character\n")
1940 (accept-process-output proc 1)
1941 (sit-for 1)
eec82323 1942 (goto-char (point-min))
23f87bed
MB
1943 (forward-line 1)
1944 (delete-region (point) (point-max)))
1945 proc))
1946
01c52d31
MB
1947(defun nntp-open-via-rlogin-and-netcat (buffer)
1948 "Open a connection to an nntp server through an intermediate host.
1949First rlogin to the remote host, and then connect to the real news
1950server from there using the netcat command.
1951
1952Please refer to the following variables to customize the connection:
1953- `nntp-pre-command',
1954- `nntp-via-rlogin-command',
1955- `nntp-via-rlogin-command-switches',
1956- `nntp-via-user-name',
1957- `nntp-via-address',
990e2c2f
SM
1958- `nntp-netcat-command',
1959- `nntp-netcat-switches',
01c52d31 1960- `nntp-address',
990e2c2f 1961- `nntp-port-number'."
01c52d31
MB
1962 (let ((command `(,@(when nntp-pre-command
1963 (list nntp-pre-command))
1964 ,nntp-via-rlogin-command
990e2c2f 1965 ,@nntp-via-rlogin-command-switches
01c52d31
MB
1966 ,@(when nntp-via-user-name
1967 (list "-l" nntp-via-user-name))
1968 ,nntp-via-address
990e2c2f
SM
1969 ,nntp-netcat-command
1970 ,@nntp-netcat-switches
01c52d31 1971 ,nntp-address
990e2c2f
SM
1972 ,(nntp-service-to-port nntp-port-number))))
1973 ;; A non-nil connection type results in mightily odd behavior where
1974 ;; (process-send-string proc "\^M") ends up sending a "\n" to the
1975 ;; ssh process. --Stef
1976 ;; Also a nil connection allow ssh-askpass to work under X11.
1977 (let ((process-connection-type nil))
1978 (apply 'start-process "nntpd" buffer command))))
1979
1980(defun nntp-open-netcat-stream (buffer)
1981 "Open a connection to an nntp server through netcat.
1982I.e. use the `nc' command rather than Emacs's builtin networking code.
1983
1984Please refer to the following variables to customize the connection:
1985- `nntp-pre-command',
1986- `nntp-netcat-command',
1987- `nntp-netcat-switches',
1988- `nntp-address',
1989- `nntp-port-number'."
1990 (let ((command `(,nntp-netcat-command
1991 ,@nntp-netcat-switches
1992 ,nntp-address
1993 ,(nntp-service-to-port nntp-port-number))))
1994 (and nntp-pre-command (push nntp-pre-command command))
1995 (let ((process-connection-type nil)) ;See `nntp-open-via-rlogin-and-netcat'.
1996 (apply 'start-process "nntpd" buffer command))))
c9fc72fa 1997
01c52d31 1998
23f87bed
MB
1999(defun nntp-open-via-telnet-and-telnet (buffer)
2000 "Open a connection to an nntp server through an intermediate host.
2001First telnet the remote host, and then telnet the real news server
2002from there.
2003
2004Please refer to the following variables to customize the connection:
2005- `nntp-pre-command',
2006- `nntp-via-telnet-command',
2007- `nntp-via-telnet-switches',
2008- `nntp-via-address',
2009- `nntp-via-envuser',
2010- `nntp-via-user-name',
2011- `nntp-via-user-password',
2012- `nntp-via-shell-prompt',
2013- `nntp-telnet-command',
2014- `nntp-telnet-switches',
2015- `nntp-address',
2016- `nntp-port-number',
2017- `nntp-end-of-line'."
ed075cb4 2018 (with-current-buffer buffer
23f87bed
MB
2019 (erase-buffer)
2020 (let ((command `(,nntp-via-telnet-command ,@nntp-via-telnet-switches))
2021 (case-fold-search t)
2022 proc)
2023 (and nntp-pre-command (push nntp-pre-command command))
2024 (setq proc (apply 'start-process "nntpd" buffer command))
2025 (when (memq (process-status proc) '(open run))
2026 (nntp-wait-for-string "^r?telnet")
2027 (process-send-string proc "set escape \^X\n")
2028 (cond
2029 ((and nntp-via-envuser nntp-via-user-name)
2030 (process-send-string proc (concat "open " "-l" nntp-via-user-name
2031 nntp-via-address "\n")))
2032 (t
2033 (process-send-string proc (concat "open " nntp-via-address
2034 "\n"))))
2035 (when (not nntp-via-envuser)
2036 (nntp-wait-for-string "^\r*.?login:")
2037 (process-send-string proc
2038 (concat
2039 (or nntp-via-user-name
2040 (setq nntp-via-user-name
2041 (read-string "login: ")))
2042 "\n")))
2043 (nntp-wait-for-string "^\r*.?password:")
2044 (process-send-string proc
2045 (concat
2046 (or nntp-via-user-password
2047 (setq nntp-via-user-password
2048 (read-passwd "Password: ")))
2049 "\n"))
2050 (nntp-wait-for-string nntp-via-shell-prompt)
2051 (let ((real-telnet-command `("exec"
2052 ,nntp-telnet-command
2053 ,@nntp-telnet-switches
2054 ,nntp-address
990e2c2f 2055 ,(nntp-service-to-port nntp-port-number))))
23f87bed
MB
2056 (process-send-string proc
2057 (concat (mapconcat 'identity
2058 real-telnet-command " ")
2059 "\n")))
2060 (nntp-wait-for-string "^\r*20[01]")
2061 (beginning-of-line)
2062 (delete-region (point-min) (point))
2063 (process-send-string proc "\^]")
2064 (nntp-wait-for-string "^r?telnet")
2065 (process-send-string proc "mode character\n")
2066 (accept-process-output proc 1)
2067 (sit-for 1)
2068 (goto-char (point-min))
2069 (forward-line 1)
2070 (delete-region (point) (point-max)))
2071 proc)))
eec82323
LMI
2072
2073(provide 'nntp)
2074
2075;;; nntp.el ends here