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