| 1 | ;;; nntp.el --- nntp access for Gnus |
| 2 | ;; Copyright (C) 1987, 1988, 1989, 1990, 1992, 1993, 1994, 1995, 1996, |
| 3 | ;; 1997, 1998, 2000 |
| 4 | ;; Free Software Foundation, Inc. |
| 5 | |
| 6 | ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org> |
| 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 | |
| 35 | (eval-when-compile (require 'cl)) |
| 36 | |
| 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 |
| 46 | server spawn an nnrpd server.") |
| 47 | |
| 48 | (defvoo nntp-authinfo-function 'nntp-send-authinfo |
| 49 | "Function used to send AUTHINFO to the server. |
| 50 | It is called with no parameters.") |
| 51 | |
| 52 | (defvoo nntp-server-action-alist |
| 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))) |
| 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 |
| 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.") |
| 77 | |
| 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 | |
| 82 | (defvoo nntp-rlogin-parameters '("telnet" "-8" "${NNTPSERVER:=news}" "nntp") |
| 83 | "*Parameters to `nntp-open-rlogin'. |
| 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 | |
| 90 | (defvoo nntp-telnet-parameters |
| 91 | '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp") |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 139 | (defvoo nntp-nov-gap 5 |
| 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 | |
| 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 |
| 150 | server there that you can connect to. See also |
| 151 | `nntp-open-connection-function'") |
| 152 | |
| 153 | (defvoo nntp-warn-about-losing-connection t |
| 154 | "*If non-nil, beep when a server closes connection.") |
| 155 | |
| 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 | |
| 179 | \f |
| 180 | |
| 181 | (defvoo nntp-connection-timeout nil |
| 182 | "*Number of seconds to wait before an nntp connection times out. |
| 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.") |
| 185 | |
| 186 | ;;; Internal variables. |
| 187 | |
| 188 | (defvar nntp-record-commands nil |
| 189 | "*If non-nil, nntp will record all commands in the \"*nntp-log*\" buffer.") |
| 190 | |
| 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) |
| 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) |
| 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 | |
| 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 | |
| 226 | (eval-and-compile |
| 227 | (autoload 'mail-source-read-passwd "mail-source") |
| 228 | (autoload 'open-ssl-stream "ssl")) |
| 229 | |
| 230 | \f |
| 231 | |
| 232 | ;;; Internal functions. |
| 233 | |
| 234 | (defsubst nntp-send-string (process string) |
| 235 | "Send STRING to PROCESS." |
| 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)) |
| 243 | (process-send-string process (concat string nntp-end-of-line))) |
| 244 | |
| 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 | |
| 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)) |
| 260 | (while (and (or (not (memq (char-after (point)) '(?2 ?3 ?4 ?5))) |
| 261 | (looking-at "480")) |
| 262 | (memq (process-status process) '(open run))) |
| 263 | (when (looking-at "480") |
| 264 | (nntp-handle-authinfo process)) |
| 265 | (nntp-accept-process-output process) |
| 266 | (goto-char (point-min))) |
| 267 | (prog1 |
| 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 |
| 276 | (goto-char (point-max)) |
| 277 | (let ((limit (point-min))) |
| 278 | (while (not (re-search-backward wait-for limit t)) |
| 279 | (nntp-accept-process-output process) |
| 280 | ;; We assume that whatever we wait for is less than 1000 |
| 281 | ;; characters long. |
| 282 | (setq limit (max (- (point-max) 1000) (point-min))) |
| 283 | (goto-char (point-max)))) |
| 284 | (nntp-decode-text (not decode)) |
| 285 | (unless discard |
| 286 | (save-excursion |
| 287 | (set-buffer buffer) |
| 288 | (goto-char (point-max)) |
| 289 | (insert-buffer-substring (process-buffer process)) |
| 290 | ;; Nix out "nntp reading...." message. |
| 291 | (when nntp-have-messaged |
| 292 | (setq nntp-have-messaged nil) |
| 293 | (nnheader-message 5 "")) |
| 294 | t)))) |
| 295 | (unless discard |
| 296 | (erase-buffer))))) |
| 297 | |
| 298 | (defun nntp-kill-buffer (buffer) |
| 299 | (when (buffer-name buffer) |
| 300 | (kill-buffer buffer) |
| 301 | (nnheader-init-server-buffer))) |
| 302 | |
| 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 |
| 315 | (nntp-kill-buffer (process-buffer process)) |
| 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 |
| 330 | &optional wait-for callback decode) |
| 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))) |
| 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))) |
| 353 | (error |
| 354 | (nnheader-report 'nntp "Couldn't open connection to %s: %s" |
| 355 | address err)) |
| 356 | (quit |
| 357 | (message "Quit retrieving data from nntp") |
| 358 | (signal 'quit nil) |
| 359 | nil))))) |
| 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) |
| 400 | (process-send-region (nntp-find-connection nntp-server-buffer) |
| 401 | (point-min) (point-max)) |
| 402 | (nntp-retrieve-data |
| 403 | nil nntp-address nntp-port-number nntp-server-buffer |
| 404 | wait-for nnheader-callback-function)) |
| 405 | |
| 406 | \f |
| 407 | |
| 408 | ;;; Interface functions. |
| 409 | |
| 410 | (nnoo-define-basics nntp) |
| 411 | |
| 412 | (defsubst nntp-next-result-arrived-p () |
| 413 | (cond |
| 414 | ;; A result that starts with a 2xx code is terminated by |
| 415 | ;; a line with only a "." on it. |
| 416 | ((eq (char-after) ?2) |
| 417 | (if (re-search-forward "\n\\.\r?\n" nil t) |
| 418 | t |
| 419 | nil)) |
| 420 | ;; A result that starts with a 3xx or 4xx code is terminated |
| 421 | ;; by a newline. |
| 422 | ((looking-at "[34]") |
| 423 | (if (search-forward "\n" nil t) |
| 424 | t |
| 425 | nil)) |
| 426 | ;; No result here. |
| 427 | (t |
| 428 | nil))) |
| 429 | |
| 430 | (deffoo nntp-retrieve-headers (articles &optional group server fetch-old) |
| 431 | "Retrieve the headers of ARTICLES." |
| 432 | (nntp-possibly-change-group group server) |
| 433 | (save-excursion |
| 434 | (set-buffer (nntp-find-connection-buffer nntp-server-buffer)) |
| 435 | (erase-buffer) |
| 436 | (if (and (not gnus-nov-is-evil) |
| 437 | (not nntp-nov-is-evil) |
| 438 | (nntp-retrieve-headers-with-xover articles fetch-old)) |
| 439 | ;; We successfully retrieved the headers via XOVER. |
| 440 | 'nov |
| 441 | ;; XOVER didn't work, so we do it the hard, slow and inefficient |
| 442 | ;; way. |
| 443 | (let ((number (length articles)) |
| 444 | (count 0) |
| 445 | (received 0) |
| 446 | (last-point (point-min)) |
| 447 | (buf (nntp-find-connection-buffer nntp-server-buffer)) |
| 448 | (nntp-inhibit-erase t) |
| 449 | article) |
| 450 | ;; Send HEAD commands. |
| 451 | (while (setq article (pop articles)) |
| 452 | (nntp-send-command |
| 453 | nil |
| 454 | "HEAD" (if (numberp article) |
| 455 | (int-to-string article) |
| 456 | ;; `articles' is either a list of article numbers |
| 457 | ;; or a list of article IDs. |
| 458 | article)) |
| 459 | (incf count) |
| 460 | ;; Every 400 requests we have to read the stream in |
| 461 | ;; order to avoid deadlocks. |
| 462 | (when (or (null articles) ;All requests have been sent. |
| 463 | (zerop (% count nntp-maximum-request))) |
| 464 | (nntp-accept-response) |
| 465 | (while (progn |
| 466 | (set-buffer buf) |
| 467 | (goto-char last-point) |
| 468 | ;; Count replies. |
| 469 | (while (nntp-next-result-arrived-p) |
| 470 | (setq last-point (point)) |
| 471 | (incf received)) |
| 472 | (< received count)) |
| 473 | ;; If number of headers is greater than 100, give |
| 474 | ;; informative messages. |
| 475 | (and (numberp nntp-large-newsgroup) |
| 476 | (> number nntp-large-newsgroup) |
| 477 | (zerop (% received 20)) |
| 478 | (nnheader-message 6 "NNTP: Receiving headers... %d%%" |
| 479 | (/ (* received 100) number))) |
| 480 | (nntp-accept-response)))) |
| 481 | (and (numberp nntp-large-newsgroup) |
| 482 | (> number nntp-large-newsgroup) |
| 483 | (nnheader-message 6 "NNTP: Receiving headers...done")) |
| 484 | |
| 485 | ;; Now all of replies are received. Fold continuation lines. |
| 486 | (nnheader-fold-continuation-lines) |
| 487 | ;; Remove all "\r"'s. |
| 488 | (nnheader-strip-cr) |
| 489 | (copy-to-buffer nntp-server-buffer (point-min) (point-max)) |
| 490 | 'headers)))) |
| 491 | |
| 492 | (deffoo nntp-retrieve-groups (groups &optional server) |
| 493 | "Retrieve group info on GROUPS." |
| 494 | (nntp-possibly-change-group nil server) |
| 495 | (when (nntp-find-connection-buffer nntp-server-buffer) |
| 496 | (save-excursion |
| 497 | ;; Erase nntp-server-buffer before nntp-inhibit-erase. |
| 498 | (set-buffer nntp-server-buffer) |
| 499 | (erase-buffer) |
| 500 | (set-buffer (nntp-find-connection-buffer nntp-server-buffer)) |
| 501 | ;; The first time this is run, this variable is `try'. So we |
| 502 | ;; try. |
| 503 | (when (eq nntp-server-list-active-group 'try) |
| 504 | (nntp-try-list-active (car groups))) |
| 505 | (erase-buffer) |
| 506 | (let ((count 0) |
| 507 | (received 0) |
| 508 | (last-point (point-min)) |
| 509 | (nntp-inhibit-erase t) |
| 510 | (buf (nntp-find-connection-buffer nntp-server-buffer)) |
| 511 | (command (if nntp-server-list-active-group "LIST ACTIVE" "GROUP"))) |
| 512 | (while groups |
| 513 | ;; Send the command to the server. |
| 514 | (nntp-send-command nil command (pop groups)) |
| 515 | (incf count) |
| 516 | ;; Every 400 requests we have to read the stream in |
| 517 | ;; order to avoid deadlocks. |
| 518 | (when (or (null groups) ;All requests have been sent. |
| 519 | (zerop (% count nntp-maximum-request))) |
| 520 | (nntp-accept-response) |
| 521 | (while (progn |
| 522 | ;; Search `blue moon' in this file for the |
| 523 | ;; reason why set-buffer here. |
| 524 | (set-buffer buf) |
| 525 | (goto-char last-point) |
| 526 | ;; Count replies. |
| 527 | (while (re-search-forward "^[0-9]" nil t) |
| 528 | (incf received)) |
| 529 | (setq last-point (point)) |
| 530 | (< received count)) |
| 531 | (nntp-accept-response)))) |
| 532 | |
| 533 | ;; Wait for the reply from the final command. |
| 534 | (set-buffer buf) |
| 535 | (goto-char (point-max)) |
| 536 | (re-search-backward "^[0-9]" nil t) |
| 537 | (when (looking-at "^[23]") |
| 538 | (while (progn |
| 539 | (set-buffer buf) |
| 540 | (goto-char (point-max)) |
| 541 | (if (not nntp-server-list-active-group) |
| 542 | (not (re-search-backward "\r?\n" (- (point) 3) t)) |
| 543 | (not (re-search-backward "^\\.\r?\n" (- (point) 4) t)))) |
| 544 | (nntp-accept-response))) |
| 545 | |
| 546 | ;; Now all replies are received. We remove CRs. |
| 547 | (set-buffer buf) |
| 548 | (goto-char (point-min)) |
| 549 | (while (search-forward "\r" nil t) |
| 550 | (replace-match "" t t)) |
| 551 | |
| 552 | (if (not nntp-server-list-active-group) |
| 553 | (progn |
| 554 | (copy-to-buffer nntp-server-buffer (point-min) (point-max)) |
| 555 | 'group) |
| 556 | ;; We have read active entries, so we just delete the |
| 557 | ;; superfluous gunk. |
| 558 | (goto-char (point-min)) |
| 559 | (while (re-search-forward "^[.2-5]" nil t) |
| 560 | (delete-region (match-beginning 0) |
| 561 | (progn (forward-line 1) (point)))) |
| 562 | (copy-to-buffer nntp-server-buffer (point-min) (point-max)) |
| 563 | 'active))))) |
| 564 | |
| 565 | (deffoo nntp-retrieve-articles (articles &optional group server) |
| 566 | (nntp-possibly-change-group group server) |
| 567 | (save-excursion |
| 568 | (let ((number (length articles)) |
| 569 | (count 0) |
| 570 | (received 0) |
| 571 | (last-point (point-min)) |
| 572 | (buf (nntp-find-connection-buffer nntp-server-buffer)) |
| 573 | (nntp-inhibit-erase t) |
| 574 | (map (apply 'vector articles)) |
| 575 | (point 1) |
| 576 | article) |
| 577 | (set-buffer buf) |
| 578 | (erase-buffer) |
| 579 | ;; Send ARTICLE command. |
| 580 | (while (setq article (pop articles)) |
| 581 | (nntp-send-command |
| 582 | nil |
| 583 | "ARTICLE" (if (numberp article) |
| 584 | (int-to-string article) |
| 585 | ;; `articles' is either a list of article numbers |
| 586 | ;; or a list of article IDs. |
| 587 | article)) |
| 588 | (incf count) |
| 589 | ;; Every 400 requests we have to read the stream in |
| 590 | ;; order to avoid deadlocks. |
| 591 | (when (or (null articles) ;All requests have been sent. |
| 592 | (zerop (% count nntp-maximum-request))) |
| 593 | (nntp-accept-response) |
| 594 | (while (progn |
| 595 | (set-buffer buf) |
| 596 | (goto-char last-point) |
| 597 | ;; Count replies. |
| 598 | (while (nntp-next-result-arrived-p) |
| 599 | (aset map received (cons (aref map received) (point))) |
| 600 | (setq last-point (point)) |
| 601 | (incf received)) |
| 602 | (< received count)) |
| 603 | ;; If number of headers is greater than 100, give |
| 604 | ;; informative messages. |
| 605 | (and (numberp nntp-large-newsgroup) |
| 606 | (> number nntp-large-newsgroup) |
| 607 | (zerop (% received 20)) |
| 608 | (nnheader-message 6 "NNTP: Receiving articles... %d%%" |
| 609 | (/ (* received 100) number))) |
| 610 | (nntp-accept-response)))) |
| 611 | (and (numberp nntp-large-newsgroup) |
| 612 | (> number nntp-large-newsgroup) |
| 613 | (nnheader-message 6 "NNTP: Receiving articles...done")) |
| 614 | |
| 615 | ;; Now we have all the responses. We go through the results, |
| 616 | ;; wash it and copy it over to the server buffer. |
| 617 | (set-buffer nntp-server-buffer) |
| 618 | (erase-buffer) |
| 619 | (setq last-point (point-min)) |
| 620 | (mapcar |
| 621 | (lambda (entry) |
| 622 | (narrow-to-region |
| 623 | (setq point (goto-char (point-max))) |
| 624 | (progn |
| 625 | (insert-buffer-substring buf last-point (cdr entry)) |
| 626 | (point-max))) |
| 627 | (setq last-point (cdr entry)) |
| 628 | (nntp-decode-text) |
| 629 | (widen) |
| 630 | (cons (car entry) point)) |
| 631 | map)))) |
| 632 | |
| 633 | (defun nntp-try-list-active (group) |
| 634 | (nntp-list-active-group group) |
| 635 | (save-excursion |
| 636 | (set-buffer nntp-server-buffer) |
| 637 | (goto-char (point-min)) |
| 638 | (cond ((or (eobp) |
| 639 | (looking-at "5[0-9]+")) |
| 640 | (setq nntp-server-list-active-group nil)) |
| 641 | (t |
| 642 | (setq nntp-server-list-active-group t))))) |
| 643 | |
| 644 | (deffoo nntp-list-active-group (group &optional server) |
| 645 | "Return the active info on GROUP (which can be a regexp)." |
| 646 | (nntp-possibly-change-group nil server) |
| 647 | (nntp-send-command "^\\.*\r?\n" "LIST ACTIVE" group)) |
| 648 | |
| 649 | (deffoo nntp-request-group-articles (group &optional server) |
| 650 | "Return the list of existing articles in GROUP." |
| 651 | (nntp-possibly-change-group nil server) |
| 652 | (nntp-send-command "^\\.*\r?\n" "LISTGROUP" group)) |
| 653 | |
| 654 | (deffoo nntp-request-article (article &optional group server buffer command) |
| 655 | (nntp-possibly-change-group group server) |
| 656 | (when (nntp-send-command-and-decode |
| 657 | "\r?\n\\.\r?\n" "ARTICLE" |
| 658 | (if (numberp article) (int-to-string article) article)) |
| 659 | (if (and buffer |
| 660 | (not (equal buffer nntp-server-buffer))) |
| 661 | (save-excursion |
| 662 | (set-buffer nntp-server-buffer) |
| 663 | (copy-to-buffer buffer (point-min) (point-max)) |
| 664 | (nntp-find-group-and-number)) |
| 665 | (nntp-find-group-and-number)))) |
| 666 | |
| 667 | (deffoo nntp-request-head (article &optional group server) |
| 668 | (nntp-possibly-change-group group server) |
| 669 | (when (nntp-send-command |
| 670 | "\r?\n\\.\r?\n" "HEAD" |
| 671 | (if (numberp article) (int-to-string article) article)) |
| 672 | (prog1 |
| 673 | (nntp-find-group-and-number) |
| 674 | (nntp-decode-text)))) |
| 675 | |
| 676 | (deffoo nntp-request-body (article &optional group server) |
| 677 | (nntp-possibly-change-group group server) |
| 678 | (nntp-send-command-and-decode |
| 679 | "\r?\n\\.\r?\n" "BODY" |
| 680 | (if (numberp article) (int-to-string article) article))) |
| 681 | |
| 682 | (deffoo nntp-request-group (group &optional server dont-check) |
| 683 | (nntp-possibly-change-group nil server) |
| 684 | (when (nntp-send-command "^[245].*\n" "GROUP" group) |
| 685 | (let ((entry (nntp-find-connection-entry nntp-server-buffer))) |
| 686 | (setcar (cddr entry) group)))) |
| 687 | |
| 688 | (deffoo nntp-close-group (group &optional server) |
| 689 | t) |
| 690 | |
| 691 | (deffoo nntp-server-opened (&optional server) |
| 692 | "Say whether a connection to SERVER has been opened." |
| 693 | (and (nnoo-current-server-p 'nntp server) |
| 694 | nntp-server-buffer |
| 695 | (gnus-buffer-live-p nntp-server-buffer) |
| 696 | (nntp-find-connection nntp-server-buffer))) |
| 697 | |
| 698 | (deffoo nntp-open-server (server &optional defs connectionless) |
| 699 | (nnheader-init-server-buffer) |
| 700 | (if (nntp-server-opened server) |
| 701 | t |
| 702 | (when (or (stringp (car defs)) |
| 703 | (numberp (car defs))) |
| 704 | (setq defs (cons (list 'nntp-port-number (car defs)) (cdr defs)))) |
| 705 | (unless (assq 'nntp-address defs) |
| 706 | (setq defs (append defs (list (list 'nntp-address server))))) |
| 707 | (nnoo-change-server 'nntp server defs) |
| 708 | (unless connectionless |
| 709 | (or (nntp-find-connection nntp-server-buffer) |
| 710 | (nntp-open-connection nntp-server-buffer))))) |
| 711 | |
| 712 | (deffoo nntp-close-server (&optional server) |
| 713 | (nntp-possibly-change-group nil server t) |
| 714 | (let ((process (nntp-find-connection nntp-server-buffer))) |
| 715 | (while process |
| 716 | (when (memq (process-status process) '(open run)) |
| 717 | (ignore-errors |
| 718 | (nntp-send-string process "QUIT") |
| 719 | (unless (eq nntp-open-connection-function 'nntp-open-network-stream) |
| 720 | ;; Ok, this is evil, but when using telnet and stuff |
| 721 | ;; as the connection method, it's important that the |
| 722 | ;; QUIT command actually is sent out before we kill |
| 723 | ;; the process. |
| 724 | (sleep-for 1)))) |
| 725 | (nntp-kill-buffer (process-buffer process)) |
| 726 | (setq process (car (pop nntp-connection-alist)))) |
| 727 | (nnoo-close-server 'nntp))) |
| 728 | |
| 729 | (deffoo nntp-request-close () |
| 730 | (let (process) |
| 731 | (while (setq process (pop nntp-connection-list)) |
| 732 | (when (memq (process-status process) '(open run)) |
| 733 | (ignore-errors |
| 734 | (nntp-send-string process "QUIT") |
| 735 | (unless (eq nntp-open-connection-function 'nntp-open-network-stream) |
| 736 | ;; Ok, this is evil, but when using telnet and stuff |
| 737 | ;; as the connection method, it's important that the |
| 738 | ;; QUIT command actually is sent out before we kill |
| 739 | ;; the process. |
| 740 | (sleep-for 1)))) |
| 741 | (nntp-kill-buffer (process-buffer process))))) |
| 742 | |
| 743 | (deffoo nntp-request-list (&optional server) |
| 744 | (nntp-possibly-change-group nil server) |
| 745 | (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST")) |
| 746 | |
| 747 | (deffoo nntp-request-list-newsgroups (&optional server) |
| 748 | (nntp-possibly-change-group nil server) |
| 749 | (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS")) |
| 750 | |
| 751 | (deffoo nntp-request-newgroups (date &optional server) |
| 752 | (nntp-possibly-change-group nil server) |
| 753 | (save-excursion |
| 754 | (set-buffer nntp-server-buffer) |
| 755 | (prog1 |
| 756 | (nntp-send-command |
| 757 | "^\\.\r?\n" "NEWGROUPS" |
| 758 | (format-time-string "%y%m%d %H%M%S" (date-to-time date))) |
| 759 | (nntp-decode-text)))) |
| 760 | |
| 761 | (deffoo nntp-request-post (&optional server) |
| 762 | (nntp-possibly-change-group nil server) |
| 763 | (when (nntp-send-command "^[23].*\r?\n" "POST") |
| 764 | (nntp-send-buffer "^[23].*\n"))) |
| 765 | |
| 766 | (deffoo nntp-request-type (group article) |
| 767 | 'news) |
| 768 | |
| 769 | (deffoo nntp-asynchronous-p () |
| 770 | t) |
| 771 | |
| 772 | ;;; Hooky functions. |
| 773 | |
| 774 | (defun nntp-send-mode-reader () |
| 775 | "Send the MODE READER command to the nntp server. |
| 776 | This function is supposed to be called from `nntp-server-opened-hook'. |
| 777 | It will make innd servers spawn an nnrpd process to allow actual article |
| 778 | reading." |
| 779 | (nntp-send-command "^.*\n" "MODE READER")) |
| 780 | |
| 781 | (defun nntp-send-authinfo (&optional send-if-force) |
| 782 | "Send the AUTHINFO to the nntp server. |
| 783 | It will look in the \"~/.authinfo\" file for matching entries. If |
| 784 | nothing suitable is found there, it will prompt for a user name |
| 785 | and a password. |
| 786 | |
| 787 | If SEND-IF-FORCE, only send authinfo to the server if the |
| 788 | .authinfo file has the FORCE token." |
| 789 | (let* ((list (gnus-parse-netrc nntp-authinfo-file)) |
| 790 | (alist (gnus-netrc-machine list nntp-address "nntp")) |
| 791 | (force (gnus-netrc-get alist "force")) |
| 792 | (user (or (gnus-netrc-get alist "login") nntp-authinfo-user)) |
| 793 | (passwd (gnus-netrc-get alist "password"))) |
| 794 | (when (or (not send-if-force) |
| 795 | force) |
| 796 | (unless user |
| 797 | (setq user (read-string (format "NNTP (%s) user name: " nntp-address)) |
| 798 | nntp-authinfo-user user)) |
| 799 | (unless (member user '(nil "")) |
| 800 | (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user) |
| 801 | (when t ;???Should check if AUTHINFO succeeded |
| 802 | (nntp-send-command |
| 803 | "^2.*\r?\n" "AUTHINFO PASS" |
| 804 | (or passwd |
| 805 | nntp-authinfo-password |
| 806 | (setq nntp-authinfo-password |
| 807 | (mail-source-read-passwd |
| 808 | (format "NNTP (%s@%s) password: " |
| 809 | user nntp-address)))))))))) |
| 810 | |
| 811 | (defun nntp-send-nosy-authinfo () |
| 812 | "Send the AUTHINFO to the nntp server." |
| 813 | (let ((user (read-string (format "NNTP (%s) user name: " nntp-address)))) |
| 814 | (unless (member user '(nil "")) |
| 815 | (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user) |
| 816 | (when t ;???Should check if AUTHINFO succeeded |
| 817 | (nntp-send-command "^2.*\r?\n" "AUTHINFO PASS" |
| 818 | (mail-source-read-passwd "NNTP (%s@%s) password: " |
| 819 | user nntp-address)))))) |
| 820 | |
| 821 | (defun nntp-send-authinfo-from-file () |
| 822 | "Send the AUTHINFO to the nntp server. |
| 823 | |
| 824 | The authinfo login name is taken from the user's login name and the |
| 825 | password contained in '~/.nntp-authinfo'." |
| 826 | (when (file-exists-p "~/.nntp-authinfo") |
| 827 | (with-temp-buffer |
| 828 | (insert-file-contents "~/.nntp-authinfo") |
| 829 | (goto-char (point-min)) |
| 830 | (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" (user-login-name)) |
| 831 | (nntp-send-command |
| 832 | "^2.*\r?\n" "AUTHINFO PASS" |
| 833 | (buffer-substring (point) (progn (end-of-line) (point))))))) |
| 834 | |
| 835 | ;;; Internal functions. |
| 836 | |
| 837 | (defun nntp-handle-authinfo (process) |
| 838 | "Take care of an authinfo response from the server." |
| 839 | (let ((last nntp-last-command)) |
| 840 | (funcall nntp-authinfo-function) |
| 841 | ;; We have to re-send the function that was interrupted by |
| 842 | ;; the authinfo request. |
| 843 | (save-excursion |
| 844 | (set-buffer nntp-server-buffer) |
| 845 | (erase-buffer)) |
| 846 | (nntp-send-string process last))) |
| 847 | |
| 848 | (defun nntp-make-process-buffer (buffer) |
| 849 | "Create a new, fresh buffer usable for nntp process connections." |
| 850 | (save-excursion |
| 851 | (set-buffer |
| 852 | (generate-new-buffer |
| 853 | (format " *server %s %s %s*" |
| 854 | nntp-address nntp-port-number |
| 855 | (gnus-buffer-exists-p buffer)))) |
| 856 | (mm-enable-multibyte) |
| 857 | (set (make-local-variable 'after-change-functions) nil) |
| 858 | (set (make-local-variable 'nntp-process-wait-for) nil) |
| 859 | (set (make-local-variable 'nntp-process-callback) nil) |
| 860 | (set (make-local-variable 'nntp-process-to-buffer) nil) |
| 861 | (set (make-local-variable 'nntp-process-start-point) nil) |
| 862 | (set (make-local-variable 'nntp-process-decode) nil) |
| 863 | (current-buffer))) |
| 864 | |
| 865 | (defun nntp-open-connection (buffer) |
| 866 | "Open a connection to PORT on ADDRESS delivering output to BUFFER." |
| 867 | (run-hooks 'nntp-prepare-server-hook) |
| 868 | (let* ((pbuffer (nntp-make-process-buffer buffer)) |
| 869 | (timer |
| 870 | (and nntp-connection-timeout |
| 871 | (nnheader-run-at-time |
| 872 | nntp-connection-timeout nil |
| 873 | `(lambda () |
| 874 | (nntp-kill-buffer ,pbuffer))))) |
| 875 | (process |
| 876 | (condition-case () |
| 877 | (let ((coding-system-for-read nntp-coding-system-for-read) |
| 878 | (coding-system-for-write nntp-coding-system-for-write)) |
| 879 | (funcall nntp-open-connection-function pbuffer)) |
| 880 | (error nil) |
| 881 | (quit |
| 882 | (message "Quit opening connection") |
| 883 | (nntp-kill-buffer pbuffer) |
| 884 | (signal 'quit nil) |
| 885 | nil)))) |
| 886 | (when timer |
| 887 | (nnheader-cancel-timer timer)) |
| 888 | (when (and (buffer-name pbuffer) |
| 889 | process) |
| 890 | (process-kill-without-query process) |
| 891 | (nntp-wait-for process "^.*\n" buffer nil t) |
| 892 | (if (memq (process-status process) '(open run)) |
| 893 | (prog1 |
| 894 | (caar (push (list process buffer nil) nntp-connection-alist)) |
| 895 | (push process nntp-connection-list) |
| 896 | (save-excursion |
| 897 | (set-buffer pbuffer) |
| 898 | (nntp-read-server-type) |
| 899 | (erase-buffer) |
| 900 | (set-buffer nntp-server-buffer) |
| 901 | (let ((nnheader-callback-function nil)) |
| 902 | (run-hooks 'nntp-server-opened-hook) |
| 903 | (nntp-send-authinfo t)))) |
| 904 | (nntp-kill-buffer (process-buffer process)) |
| 905 | nil)))) |
| 906 | |
| 907 | (defun nntp-open-network-stream (buffer) |
| 908 | (open-network-stream "nntpd" buffer nntp-address nntp-port-number)) |
| 909 | |
| 910 | (defun nntp-open-ssl-stream (buffer) |
| 911 | (let* ((ssl-program-arguments '("-connect" (concat host ":" service))) |
| 912 | (proc (open-ssl-stream "nntpd" buffer nntp-address nntp-port-number))) |
| 913 | (save-excursion |
| 914 | (set-buffer buffer) |
| 915 | (nntp-wait-for-string "^\r*20[01]") |
| 916 | (beginning-of-line) |
| 917 | (delete-region (point-min) (point)) |
| 918 | proc))) |
| 919 | |
| 920 | (defun nntp-read-server-type () |
| 921 | "Find out what the name of the server we have connected to is." |
| 922 | ;; Wait for the status string to arrive. |
| 923 | (setq nntp-server-type (buffer-string)) |
| 924 | (let ((alist nntp-server-action-alist) |
| 925 | (case-fold-search t) |
| 926 | entry) |
| 927 | ;; Run server-specific commands. |
| 928 | (while alist |
| 929 | (setq entry (pop alist)) |
| 930 | (when (string-match (car entry) nntp-server-type) |
| 931 | (if (and (listp (cadr entry)) |
| 932 | (not (eq 'lambda (caadr entry)))) |
| 933 | (eval (cadr entry)) |
| 934 | (funcall (cadr entry))))))) |
| 935 | |
| 936 | (defun nntp-async-wait (process wait-for buffer decode callback) |
| 937 | (save-excursion |
| 938 | (set-buffer (process-buffer process)) |
| 939 | (unless nntp-inside-change-function |
| 940 | (erase-buffer)) |
| 941 | (setq nntp-process-wait-for wait-for |
| 942 | nntp-process-to-buffer buffer |
| 943 | nntp-process-decode decode |
| 944 | nntp-process-callback callback |
| 945 | nntp-process-start-point (point-max)) |
| 946 | (setq after-change-functions '(nntp-after-change-function)) |
| 947 | (if nntp-async-needs-kluge |
| 948 | (nntp-async-kluge process)))) |
| 949 | |
| 950 | (defun nntp-async-kluge (process) |
| 951 | ;; emacs 20.3 bug: process output with encoding 'binary |
| 952 | ;; doesn't trigger after-change-functions. |
| 953 | (unless nntp-async-timer |
| 954 | (setq nntp-async-timer |
| 955 | (nnheader-run-at-time 1 1 'nntp-async-timer-handler))) |
| 956 | (add-to-list 'nntp-async-process-list process)) |
| 957 | |
| 958 | (defun nntp-async-timer-handler () |
| 959 | (mapcar |
| 960 | (lambda (proc) |
| 961 | (if (memq (process-status proc) '(open run)) |
| 962 | (nntp-async-trigger proc) |
| 963 | (nntp-async-stop proc))) |
| 964 | nntp-async-process-list)) |
| 965 | |
| 966 | (defun nntp-async-stop (proc) |
| 967 | (setq nntp-async-process-list (delq proc nntp-async-process-list)) |
| 968 | (when (and nntp-async-timer (not nntp-async-process-list)) |
| 969 | (nnheader-cancel-timer nntp-async-timer) |
| 970 | (setq nntp-async-timer nil))) |
| 971 | |
| 972 | (defun nntp-after-change-function (beg end len) |
| 973 | (unwind-protect |
| 974 | ;; we only care about insertions at eob |
| 975 | (when (and (eq 0 len) (eq (point-max) end)) |
| 976 | (save-match-data |
| 977 | (let ((proc (get-buffer-process (current-buffer)))) |
| 978 | (when proc |
| 979 | (nntp-async-trigger proc))))) |
| 980 | ;; any throw from after-change-functions will leave it |
| 981 | ;; set to nil. so we reset it here, if necessary. |
| 982 | (when quit-flag |
| 983 | (setq after-change-functions '(nntp-after-change-function))))) |
| 984 | |
| 985 | (defun nntp-async-trigger (process) |
| 986 | (save-excursion |
| 987 | (set-buffer (process-buffer process)) |
| 988 | (when nntp-process-callback |
| 989 | ;; do we have an error message? |
| 990 | (goto-char nntp-process-start-point) |
| 991 | (if (memq (following-char) '(?4 ?5)) |
| 992 | ;; wants credentials? |
| 993 | (if (looking-at "480") |
| 994 | (nntp-handle-authinfo process) |
| 995 | ;; report error message. |
| 996 | (nntp-snarf-error-message) |
| 997 | (nntp-do-callback nil)) |
| 998 | |
| 999 | ;; got what we expect? |
| 1000 | (goto-char (point-max)) |
| 1001 | (when (re-search-backward |
| 1002 | nntp-process-wait-for nntp-process-start-point t) |
| 1003 | (nntp-async-stop process) |
| 1004 | ;; convert it. |
| 1005 | (when (gnus-buffer-exists-p nntp-process-to-buffer) |
| 1006 | (let ((buf (current-buffer)) |
| 1007 | (start nntp-process-start-point) |
| 1008 | (decode nntp-process-decode)) |
| 1009 | (save-excursion |
| 1010 | (set-buffer nntp-process-to-buffer) |
| 1011 | (goto-char (point-max)) |
| 1012 | (save-restriction |
| 1013 | (narrow-to-region (point) (point)) |
| 1014 | (insert-buffer-substring buf start) |
| 1015 | (when decode |
| 1016 | (nntp-decode-text)))))) |
| 1017 | ;; report it. |
| 1018 | (goto-char (point-max)) |
| 1019 | (nntp-do-callback |
| 1020 | (buffer-name (get-buffer nntp-process-to-buffer)))))))) |
| 1021 | |
| 1022 | (defun nntp-do-callback (arg) |
| 1023 | (let ((callback nntp-process-callback) |
| 1024 | (nntp-inside-change-function t)) |
| 1025 | (setq nntp-process-callback nil) |
| 1026 | (funcall callback arg))) |
| 1027 | |
| 1028 | (defun nntp-snarf-error-message () |
| 1029 | "Save the error message in the current buffer." |
| 1030 | (let ((message (buffer-string))) |
| 1031 | (while (string-match "[\r\n]+" message) |
| 1032 | (setq message (replace-match " " t t message))) |
| 1033 | (nnheader-report 'nntp message) |
| 1034 | message)) |
| 1035 | |
| 1036 | (defun nntp-accept-process-output (process &optional timeout) |
| 1037 | "Wait for output from PROCESS and message some dots." |
| 1038 | (save-excursion |
| 1039 | (set-buffer (or (nntp-find-connection-buffer nntp-server-buffer) |
| 1040 | nntp-server-buffer)) |
| 1041 | (let ((len (/ (point-max) 1024)) |
| 1042 | message-log-max) |
| 1043 | (unless (< len 10) |
| 1044 | (setq nntp-have-messaged t) |
| 1045 | (nnheader-message 7 "nntp read: %dk" len))) |
| 1046 | (accept-process-output process (or timeout 1)))) |
| 1047 | |
| 1048 | (defun nntp-accept-response () |
| 1049 | "Wait for output from the process that outputs to BUFFER." |
| 1050 | (nntp-accept-process-output (nntp-find-connection nntp-server-buffer))) |
| 1051 | |
| 1052 | (defun nntp-possibly-change-group (group server &optional connectionless) |
| 1053 | (let ((nnheader-callback-function nil)) |
| 1054 | (when server |
| 1055 | (or (nntp-server-opened server) |
| 1056 | (nntp-open-server server nil connectionless))) |
| 1057 | |
| 1058 | (unless connectionless |
| 1059 | (or (nntp-find-connection nntp-server-buffer) |
| 1060 | (nntp-open-connection nntp-server-buffer)))) |
| 1061 | |
| 1062 | (when group |
| 1063 | (let ((entry (nntp-find-connection-entry nntp-server-buffer))) |
| 1064 | (when (not (equal group (caddr entry))) |
| 1065 | (save-excursion |
| 1066 | (set-buffer (process-buffer (car entry))) |
| 1067 | (erase-buffer) |
| 1068 | (nntp-send-command "^[245].*\n" "GROUP" group) |
| 1069 | (setcar (cddr entry) group) |
| 1070 | (erase-buffer)))))) |
| 1071 | |
| 1072 | (defun nntp-decode-text (&optional cr-only) |
| 1073 | "Decode the text in the current buffer." |
| 1074 | (goto-char (point-min)) |
| 1075 | (while (search-forward "\r" nil t) |
| 1076 | (delete-char -1)) |
| 1077 | (unless cr-only |
| 1078 | ;; Remove trailing ".\n" end-of-transfer marker. |
| 1079 | (goto-char (point-max)) |
| 1080 | (forward-line -1) |
| 1081 | (when (looking-at ".\n") |
| 1082 | (delete-char 2)) |
| 1083 | ;; Delete status line. |
| 1084 | (goto-char (point-min)) |
| 1085 | (while (looking-at "[1-5][0-9][0-9] .*\n") |
| 1086 | ;; For some unknown reason, there is more than one status line. |
| 1087 | (delete-region (point) (progn (forward-line 1) (point)))) |
| 1088 | ;; Remove "." -> ".." encoding. |
| 1089 | (while (search-forward "\n.." nil t) |
| 1090 | (delete-char -1)))) |
| 1091 | |
| 1092 | (defun nntp-encode-text () |
| 1093 | "Encode the text in the current buffer." |
| 1094 | (save-excursion |
| 1095 | ;; Replace "." at beginning of line with "..". |
| 1096 | (goto-char (point-min)) |
| 1097 | (while (re-search-forward "^\\." nil t) |
| 1098 | (insert ".")) |
| 1099 | (goto-char (point-max)) |
| 1100 | ;; Insert newline at the end of the buffer. |
| 1101 | (unless (bolp) |
| 1102 | (insert "\n")) |
| 1103 | ;; Insert `.' at end of buffer (end of text mark). |
| 1104 | (goto-char (point-max)) |
| 1105 | (insert ".\n") |
| 1106 | (goto-char (point-min)) |
| 1107 | (while (not (eobp)) |
| 1108 | (end-of-line) |
| 1109 | (delete-char 1) |
| 1110 | (insert nntp-end-of-line)))) |
| 1111 | |
| 1112 | (defun nntp-retrieve-headers-with-xover (articles &optional fetch-old) |
| 1113 | (set-buffer nntp-server-buffer) |
| 1114 | (erase-buffer) |
| 1115 | (cond |
| 1116 | |
| 1117 | ;; This server does not talk NOV. |
| 1118 | ((not nntp-server-xover) |
| 1119 | nil) |
| 1120 | |
| 1121 | ;; We don't care about gaps. |
| 1122 | ((or (not nntp-nov-gap) |
| 1123 | fetch-old) |
| 1124 | (nntp-send-xover-command |
| 1125 | (if fetch-old |
| 1126 | (if (numberp fetch-old) |
| 1127 | (max 1 (- (car articles) fetch-old)) |
| 1128 | 1) |
| 1129 | (car articles)) |
| 1130 | (car (last articles)) 'wait) |
| 1131 | |
| 1132 | (goto-char (point-min)) |
| 1133 | (when (looking-at "[1-5][0-9][0-9] .*\n") |
| 1134 | (delete-region (point) (progn (forward-line 1) (point)))) |
| 1135 | (while (search-forward "\r" nil t) |
| 1136 | (replace-match "" t t)) |
| 1137 | (goto-char (point-max)) |
| 1138 | (forward-line -1) |
| 1139 | (when (looking-at "\\.") |
| 1140 | (delete-region (point) (progn (forward-line 1) (point))))) |
| 1141 | |
| 1142 | ;; We do it the hard way. For each gap, an XOVER command is sent |
| 1143 | ;; to the server. We do not wait for a reply from the server, we |
| 1144 | ;; just send them off as fast as we can. That means that we have |
| 1145 | ;; to count the number of responses we get back to find out when we |
| 1146 | ;; have gotten all we asked for. |
| 1147 | ((numberp nntp-nov-gap) |
| 1148 | (let ((count 0) |
| 1149 | (received 0) |
| 1150 | last-point |
| 1151 | in-process-buffer-p |
| 1152 | (buf nntp-server-buffer) |
| 1153 | (process-buffer (nntp-find-connection-buffer nntp-server-buffer)) |
| 1154 | first) |
| 1155 | ;; We have to check `nntp-server-xover'. If it gets set to nil, |
| 1156 | ;; that means that the server does not understand XOVER, but we |
| 1157 | ;; won't know that until we try. |
| 1158 | (while (and nntp-server-xover articles) |
| 1159 | (setq first (car articles)) |
| 1160 | ;; Search forward until we find a gap, or until we run out of |
| 1161 | ;; articles. |
| 1162 | (while (and (cdr articles) |
| 1163 | (< (- (nth 1 articles) (car articles)) nntp-nov-gap)) |
| 1164 | (setq articles (cdr articles))) |
| 1165 | |
| 1166 | (setq in-process-buffer-p (stringp nntp-server-xover)) |
| 1167 | (nntp-send-xover-command first (car articles)) |
| 1168 | (setq articles (cdr articles)) |
| 1169 | |
| 1170 | (when (and nntp-server-xover in-process-buffer-p) |
| 1171 | ;; Don't count tried request. |
| 1172 | (setq count (1+ count)) |
| 1173 | |
| 1174 | ;; Every 400 requests we have to read the stream in |
| 1175 | ;; order to avoid deadlocks. |
| 1176 | (when (or (null articles) ;All requests have been sent. |
| 1177 | (zerop (% count nntp-maximum-request))) |
| 1178 | |
| 1179 | (nntp-accept-response) |
| 1180 | ;; On some Emacs versions the preceding function has a |
| 1181 | ;; tendency to change the buffer. Perhaps. It's quite |
| 1182 | ;; difficult to reproduce, because it only seems to happen |
| 1183 | ;; once in a blue moon. |
| 1184 | (set-buffer process-buffer) |
| 1185 | (while (progn |
| 1186 | (goto-char (or last-point (point-min))) |
| 1187 | ;; Count replies. |
| 1188 | (while (re-search-forward "^[0-9][0-9][0-9] .*\n" nil t) |
| 1189 | (incf received)) |
| 1190 | (setq last-point (point)) |
| 1191 | (< received count)) |
| 1192 | (nntp-accept-response) |
| 1193 | (set-buffer process-buffer)) |
| 1194 | (set-buffer buf)))) |
| 1195 | |
| 1196 | (when nntp-server-xover |
| 1197 | (when in-process-buffer-p |
| 1198 | (set-buffer process-buffer) |
| 1199 | ;; Wait for the reply from the final command. |
| 1200 | (goto-char (point-max)) |
| 1201 | (while (not (re-search-backward "^[0-9][0-9][0-9] " nil t)) |
| 1202 | (nntp-accept-response) |
| 1203 | (set-buffer process-buffer) |
| 1204 | (goto-char (point-max))) |
| 1205 | (when (looking-at "^[23]") |
| 1206 | (while (progn |
| 1207 | (goto-char (point-max)) |
| 1208 | (forward-line -1) |
| 1209 | (not (looking-at "^\\.\r?\n"))) |
| 1210 | (nntp-accept-response) |
| 1211 | (set-buffer process-buffer))) |
| 1212 | (set-buffer buf) |
| 1213 | (goto-char (point-max)) |
| 1214 | (insert-buffer-substring process-buffer) |
| 1215 | (set-buffer process-buffer) |
| 1216 | (erase-buffer) |
| 1217 | (set-buffer buf)) |
| 1218 | |
| 1219 | ;; We remove any "." lines and status lines. |
| 1220 | (goto-char (point-min)) |
| 1221 | (while (search-forward "\r" nil t) |
| 1222 | (delete-char -1)) |
| 1223 | (goto-char (point-min)) |
| 1224 | (delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ") |
| 1225 | t)))) |
| 1226 | |
| 1227 | nntp-server-xover) |
| 1228 | |
| 1229 | (defun nntp-send-xover-command (beg end &optional wait-for-reply) |
| 1230 | "Send the XOVER command to the server." |
| 1231 | (let ((range (format "%d-%d" beg end)) |
| 1232 | (nntp-inhibit-erase t)) |
| 1233 | (if (stringp nntp-server-xover) |
| 1234 | ;; If `nntp-server-xover' is a string, then we just send this |
| 1235 | ;; command. |
| 1236 | (if wait-for-reply |
| 1237 | (nntp-send-command-nodelete |
| 1238 | "\r?\n\\.\r?\n" nntp-server-xover range) |
| 1239 | ;; We do not wait for the reply. |
| 1240 | (nntp-send-command-nodelete nil nntp-server-xover range)) |
| 1241 | (let ((commands nntp-xover-commands)) |
| 1242 | ;; `nntp-xover-commands' is a list of possible XOVER commands. |
| 1243 | ;; We try them all until we get at positive response. |
| 1244 | (while (and commands (eq nntp-server-xover 'try)) |
| 1245 | (nntp-send-command-nodelete "\r?\n\\.\r?\n" (car commands) range) |
| 1246 | (save-excursion |
| 1247 | (set-buffer nntp-server-buffer) |
| 1248 | (goto-char (point-min)) |
| 1249 | (and (looking-at "[23]") ; No error message. |
| 1250 | ;; We also have to look at the lines. Some buggy |
| 1251 | ;; servers give back simple lines with just the |
| 1252 | ;; article number. How... helpful. |
| 1253 | (progn |
| 1254 | (forward-line 1) |
| 1255 | (looking-at "[0-9]+\t...")) ; More text after number. |
| 1256 | (setq nntp-server-xover (car commands)))) |
| 1257 | (setq commands (cdr commands))) |
| 1258 | ;; If none of the commands worked, we disable XOVER. |
| 1259 | (when (eq nntp-server-xover 'try) |
| 1260 | (save-excursion |
| 1261 | (set-buffer nntp-server-buffer) |
| 1262 | (erase-buffer) |
| 1263 | (setq nntp-server-xover nil))) |
| 1264 | nntp-server-xover)))) |
| 1265 | |
| 1266 | ;;; Alternative connection methods. |
| 1267 | |
| 1268 | (defun nntp-wait-for-string (regexp) |
| 1269 | "Wait until string arrives in the buffer." |
| 1270 | (let ((buf (current-buffer))) |
| 1271 | (goto-char (point-min)) |
| 1272 | (while (not (re-search-forward regexp nil t)) |
| 1273 | (accept-process-output (nntp-find-connection nntp-server-buffer)) |
| 1274 | (set-buffer buf) |
| 1275 | (goto-char (point-min))))) |
| 1276 | |
| 1277 | (defun nntp-open-telnet (buffer) |
| 1278 | (save-excursion |
| 1279 | (set-buffer buffer) |
| 1280 | (erase-buffer) |
| 1281 | (let ((proc (apply |
| 1282 | 'start-process |
| 1283 | "nntpd" buffer nntp-telnet-command nntp-telnet-switches)) |
| 1284 | (case-fold-search t)) |
| 1285 | (when (memq (process-status proc) '(open run)) |
| 1286 | (nntp-wait-for-string "^r?telnet") |
| 1287 | (process-send-string proc "set escape \^X\n") |
| 1288 | (cond |
| 1289 | ((and nntp-open-telnet-envuser nntp-telnet-user-name) |
| 1290 | (process-send-string proc (concat "open " "-l" nntp-telnet-user-name |
| 1291 | nntp-address "\n"))) |
| 1292 | (t |
| 1293 | (process-send-string proc (concat "open " nntp-address "\n")))) |
| 1294 | (cond |
| 1295 | ((not nntp-open-telnet-envuser) |
| 1296 | (nntp-wait-for-string "^\r*.?login:") |
| 1297 | (process-send-string |
| 1298 | proc (concat |
| 1299 | (or nntp-telnet-user-name |
| 1300 | (setq nntp-telnet-user-name (read-string "login: "))) |
| 1301 | "\n")))) |
| 1302 | (nntp-wait-for-string "^\r*.?password:") |
| 1303 | (process-send-string |
| 1304 | proc (concat |
| 1305 | (or nntp-telnet-passwd |
| 1306 | (setq nntp-telnet-passwd |
| 1307 | (mail-source-read-passwd "Password: "))) |
| 1308 | "\n")) |
| 1309 | (nntp-wait-for-string nntp-telnet-shell-prompt) |
| 1310 | (process-send-string |
| 1311 | proc (concat (mapconcat 'identity nntp-telnet-parameters " ") "\n")) |
| 1312 | (nntp-wait-for-string "^\r*20[01]") |
| 1313 | (beginning-of-line) |
| 1314 | (delete-region (point-min) (point)) |
| 1315 | (process-send-string proc "\^]") |
| 1316 | (nntp-wait-for-string "^r?telnet") |
| 1317 | (process-send-string proc "mode character\n") |
| 1318 | (accept-process-output proc 1) |
| 1319 | (sit-for 1) |
| 1320 | (goto-char (point-min)) |
| 1321 | (forward-line 1) |
| 1322 | (delete-region (point) (point-max))) |
| 1323 | proc))) |
| 1324 | |
| 1325 | (defun nntp-open-rlogin (buffer) |
| 1326 | "Open a connection to SERVER using rsh." |
| 1327 | (let ((proc (if nntp-rlogin-user-name |
| 1328 | (apply 'start-process |
| 1329 | "nntpd" buffer nntp-rlogin-program |
| 1330 | nntp-address "-l" nntp-rlogin-user-name |
| 1331 | nntp-rlogin-parameters) |
| 1332 | (apply 'start-process |
| 1333 | "nntpd" buffer nntp-rlogin-program nntp-address |
| 1334 | nntp-rlogin-parameters)))) |
| 1335 | (save-excursion |
| 1336 | (set-buffer buffer) |
| 1337 | (nntp-wait-for-string "^\r*20[01]") |
| 1338 | (beginning-of-line) |
| 1339 | (delete-region (point-min) (point)) |
| 1340 | proc))) |
| 1341 | |
| 1342 | (defun nntp-find-group-and-number () |
| 1343 | (save-excursion |
| 1344 | (save-restriction |
| 1345 | (set-buffer nntp-server-buffer) |
| 1346 | (narrow-to-region (goto-char (point-min)) |
| 1347 | (or (search-forward "\n\n" nil t) (point-max))) |
| 1348 | (goto-char (point-min)) |
| 1349 | ;; We first find the number by looking at the status line. |
| 1350 | (let ((number (and (looking-at "2[0-9][0-9] +\\([0-9]+\\) ") |
| 1351 | (string-to-int |
| 1352 | (buffer-substring (match-beginning 1) |
| 1353 | (match-end 1))))) |
| 1354 | group newsgroups xref) |
| 1355 | (and number (zerop number) (setq number nil)) |
| 1356 | ;; Then we find the group name. |
| 1357 | (setq group |
| 1358 | (cond |
| 1359 | ;; If there is only one group in the Newsgroups header, |
| 1360 | ;; then it seems quite likely that this article comes |
| 1361 | ;; from that group, I'd say. |
| 1362 | ((and (setq newsgroups (mail-fetch-field "newsgroups")) |
| 1363 | (not (string-match "," newsgroups))) |
| 1364 | newsgroups) |
| 1365 | ;; If there is more than one group in the Newsgroups |
| 1366 | ;; header, then the Xref header should be filled out. |
| 1367 | ;; We hazard a guess that the group that has this |
| 1368 | ;; article number in the Xref header is the one we are |
| 1369 | ;; looking for. This might very well be wrong if this |
| 1370 | ;; article happens to have the same number in several |
| 1371 | ;; groups, but that's life. |
| 1372 | ((and (setq xref (mail-fetch-field "xref")) |
| 1373 | number |
| 1374 | (string-match (format "\\([^ :]+\\):%d" number) xref)) |
| 1375 | (substring xref (match-beginning 1) (match-end 1))) |
| 1376 | (t ""))) |
| 1377 | (when (string-match "\r" group) |
| 1378 | (setq group (substring group 0 (match-beginning 0)))) |
| 1379 | (cons group number))))) |
| 1380 | |
| 1381 | (provide 'nntp) |
| 1382 | |
| 1383 | ;;; nntp.el ends here |