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