comparison lisp/erc/erc-dcc.el @ 68451:fc745b05e928

Revision: emacs@sv.gnu.org/emacs--devo--0--patch-22 Creator: Michael Olson <mwolson@gnu.org> Install ERC.
author Miles Bader <miles@gnu.org>
date Sun, 29 Jan 2006 13:08:58 +0000
parents
children fe9073ac802e
comparison
equal deleted inserted replaced
68450:a3ba4ef5d590 68451:fc745b05e928
1 ;;; erc-dcc.el --- CTCP DCC module for ERC
2
3 ;; Copyright (C) 1993, 1994, 1995, 1998, 2002, 2003, 2004
4 ;; Free Software Foundation, Inc.
5
6 ;; Author: Ben A. Mesander <ben@gnu.ai.mit.edu>
7 ;; Noah Friedman <friedman@prep.ai.mit.edu>
8 ;; Per Persson <pp@sno.pp.se>
9 ;; Maintainer: mlang@delysid.org
10 ;; Keywords: comm, processes
11 ;; Created: 1994-01-23
12
13 ;; This file is part of GNU Emacs.
14
15 ;; GNU Emacs is free software; you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License as published by
17 ;; the Free Software Foundation; either version 2, or (at your option)
18 ;; any later version.
19
20 ;; GNU Emacs is distributed in the hope that it will be useful,
21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 ;; GNU General Public License for more details.
24
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with GNU Emacs; see the file COPYING. If not, write to the
27 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 ;; Boston, MA 02110-1301, USA.
29
30 ;;; Commentary:
31
32 ;; This file provides Direct Client-to-Client support for the Emacs IRC Client.
33 ;;
34 ;; The original code was taken from zenirc-dcc.el, heavily mangled and
35 ;; rewritten to support the way how ERC operates. Server socket support
36 ;; was added for DCC CHAT and SEND afterwards. Thanks
37 ;; to the original authors for their work.
38 ;;
39 ;; To use this file, put
40 ;; (require 'erc-dcc)
41 ;; in your .emacs.
42 ;;
43 ;; Provided commands
44 ;; /dcc chat nick - Either accept pending chat offer from nick, or offer
45 ;; DCC chat to nick
46 ;; /dcc close type [nick] - Close DCC connection (SEND/GET/CHAT) with nick
47 ;; /dcc get nick [file] - Accept DCC offer from nick
48 ;; /dcc list - List all DCC offers/connections
49 ;; /dcc send nick file - Offer DCC SEND to nick
50 ;;
51 ;; Please note that offering DCC connections (offering chats and sending
52 ;; files) is only supported with Emacs 21.3.50 (CVS).
53
54 ;;; Code:
55
56 (require 'erc)
57 (eval-when-compile
58 (require 'pcomplete))
59
60 (defgroup erc-dcc nil
61 "DCC stands for Direct Client Communication, where you and your
62 friend's client programs connect directly to each other,
63 bypassing IRC servers and their occasional \"lag\" or \"split\"
64 problems. Like /MSG, the DCC chat is completely private.
65
66 Using DCC get and send, you can transfer files directly from and to other
67 IRC users."
68 :group 'erc)
69
70 (defcustom erc-verbose-dcc t
71 "*If non-nil, be verbose about DCC activity reporting."
72 :group 'erc-dcc
73 :type 'boolean)
74
75 (defvar erc-dcc-list nil
76 "List of DCC connections. Looks like:
77 ((:nick \"nick!user@host\" :type GET :peer proc :parent proc :size size :file file)
78 (:nick \"nick!user@host\" :type CHAT :peer proc :parent proc)
79 (:nick \"nick\" :type SEND :peer server-proc :parent parent-proc :file
80 file :sent <marker> :confirmed <marker>))
81
82 :nick - a user or userhost for the peer. combine with :parent to reach them
83
84 :type - the type of DCC connection - SEND for outgoing files, GET for
85 incoming, and CHAT for both directions. To tell which end started
86 the DCC chat, look at :peer
87
88 :peer - the other end of the DCC connection. In the case of outgoing DCCs,
89 this represents a server process until a connection is established
90
91 :parent - the server process where the dcc connection was established.
92 Note that this can be nil or an invalid process since a DCC
93 connection is in general independent from a particular server
94 connection after it was established.
95
96 :file - for outgoing sends, the full path to the file. for incoming sends,
97 the suggested filename or vetted filename
98
99 :size - size of the file, may be nil on incoming DCCs")
100
101 (defun erc-dcc-list-add (type nick peer parent &rest args)
102 "Add a new entry of type TYPE to `erc-dcc-list' and return it."
103 (car
104 (setq erc-dcc-list
105 (cons
106 (append (list :nick nick :type type :peer peer :parent parent) args)
107 erc-dcc-list))))
108
109 ;; This function takes all the usual args as open-network-stream, plus one
110 ;; more: the entry data from erc-dcc-list for this particular process.
111 (defvar erc-dcc-connect-function 'erc-dcc-open-network-stream)
112
113 (defun erc-dcc-open-network-stream (procname buffer addr port entry)
114 (if nil; (fboundp 'open-network-stream-nowait) ;; this currently crashes
115 ;; cvs emacs
116 (open-network-stream-nowait procname buffer addr port)
117 (open-network-stream procname buffer addr port)))
118
119 (erc-define-catalog
120 'english
121 '((dcc-chat-discarded
122 . "DCC: previous chat request from %n (%u@%h) discarded")
123 (dcc-chat-ended . "DCC: chat with %n ended %t: %e")
124 (dcc-chat-no-request . "DCC: chat request from %n not found")
125 (dcc-chat-offered . "DCC: chat offered by %n (%u@%h:%p)")
126 (dcc-chat-offer . "DCC: offering chat to %n")
127 (dcc-chat-accept . "DCC: accepting chat from %n")
128 (dcc-chat-privmsg . "=%n= %m")
129 (dcc-closed . "DCC: Closed %T from %n")
130 (dcc-command-undefined
131 . "DCC: %c undefined subcommand. GET, CHAT and LIST are defined.")
132 (dcc-ctcp-errmsg . "DCC: `%s' is not a DCC subcommand known to this client")
133 (dcc-ctcp-unknown . "DCC: unknown dcc command `%q' from %n (%u@%h)")
134 (dcc-get-bytes-received . "DCC: %f: %b bytes received")
135 (dcc-get-complete
136 . "DCC: file %f transfer complete (%s bytes in %t seconds)")
137 (dcc-get-cmd-aborted . "DCC: Aborted getting %f from %n")
138 (dcc-get-file-too-long
139 . "DCC: %f: File longer than sender claimed; aborting transfer")
140 (dcc-get-notfound . "DCC: %n hasn't offered %f for DCC transfer")
141 (dcc-list-head . "DCC: From Type Active Size Filename")
142 (dcc-list-line . "DCC: -------- ---- ------ ------------ --------")
143 (dcc-list-item . "DCC: %-8n %-4t %-6a %-12s %f")
144 (dcc-list-end . "DCC: End of list.")
145 (dcc-malformed . "DCC: error: %n (%u@%h) sent malformed request: %q")
146 (dcc-privileged-port
147 . "DCC: possibly bogus request: %p is a privileged port.")
148 (dcc-request-bogus . "DCC: bogus dcc `%r' from %n (%u@%h)")
149 (dcc-send-finished . "DCC: SEND of %f to %n finished (size %s)")
150 (dcc-send-offered . "DCC: file %f offered by %n (%u@%h) (size %s)")
151 (dcc-send-offer . "DCC: offering %f to %n")))
152
153 ;;; Misc macros and utility functions
154
155 (defun erc-dcc-member (&rest args)
156 "Return the first matching entry in `erc-dcc-list' which satisfies the
157 constraints given as a plist in ARGS. Returns nil on no match.
158
159 The property :nick is treated specially, if it contains a '!' character,
160 it is treated as a nick!user@host string, and compared with the :nick property
161 value of the individual elements using string-equal. Otherwise it is
162 compared with `erc-nick-equal-p' which is IRC case-insensitive."
163 (let ((list erc-dcc-list)
164 result test)
165 ;; for each element in erc-dcc-list
166 (while (and list (not result))
167 (let ((elt (car list))
168 (prem args)
169 (cont t))
170 ;; loop through the constraints
171 (while (and prem cont)
172 (let ((prop (car prem))
173 (val (cadr prem)))
174 (setq prem (cddr prem)
175 ;; plist-member is a predicate in xemacs
176 test (and (plist-member elt prop)
177 (plist-get elt prop)))
178 ;; if the property exists and is equal, we continue, else, try the
179 ;; next element of the list
180 (or (and (eq prop :nick) (string-match "!" val)
181 test (string-equal test val))
182 (and (eq prop :nick)
183 test val
184 (erc-nick-equal-p
185 (erc-extract-nick test)
186 (erc-extract-nick val)))
187 ;; not a nick
188 (eq test val)
189 (setq cont nil))))
190 (if cont
191 (setq result elt)
192 (setq list (cdr list)))))
193 result))
194
195 ;; msa wrote this nifty little frob to convert an n-byte integer to a packed
196 ;; string.
197 (defun erc-pack-int (value count)
198 (if (> count 0)
199 (concat (erc-pack-int (/ value 256) (1- count))
200 (char-to-string (% value 256)))
201 ""))
202
203 (defun erc-unpack-int (str)
204 "Unpack a 1-4 character packed string into an integer."
205 (let ((len (length str))
206 (num 0)
207 (count 0))
208 (erc-assert (<= len 4)) ;; this isn't going to fit in elisp bounds
209 (while (< count len)
210 (setq num (+ num (lsh (aref str (- len count 1)) (* 8 count))))
211 (setq count (1+ count)))
212 num))
213
214 (defconst erc-dcc-ipv4-regexp
215 (concat "^"
216 (mapconcat #'identity (make-list 4 "\\([0-9]\\{1,3\\}\\)") "\\.")
217 "$"))
218
219 (defun erc-ip-to-decimal (ip)
220 "Convert IP address to its decimal representation.
221 Argument IP is the address as a string. The result is also a string."
222 (interactive "sIP Address: ")
223 (if (not (string-match erc-dcc-ipv4-regexp ip))
224 (error "Not an IP address")
225 (let* ((ips (mapcar
226 (lambda (str)
227 (let ((n (string-to-number str)))
228 (if (and (>= n 0) (< n 256))
229 n
230 (error "%d out of range" n))))
231 (split-string ip "\\.")))
232 (res (+ (* (car ips) 16777216.0)
233 (* (nth 1 ips) 65536.0)
234 (* (nth 2 ips) 256.0)
235 (nth 3 ips))))
236 (if (interactive-p)
237 (message "%s is %.0f" ip res)
238 (format "%.0f" res)))))
239
240 (defun erc-decimal-to-ip (dec)
241 "Convert a decimal representation DEC to an IP address.
242 The result is also a string."
243 (when (stringp dec)
244 (setq dec (string-to-number (concat dec ".0"))))
245 (let* ((first (floor (/ dec 16777216.0)))
246 (first-rest (- dec (* first 16777216.0)))
247 (second (floor (/ first-rest 65536.0)))
248 (second-rest (- first-rest (* second 65536.0)))
249 (third (floor (/ second-rest 256.0)))
250 (third-rest (- second-rest (* third 256.0)))
251 (fourth (floor third-rest)))
252 (format "%s.%s.%s.%s" first second third fourth)))
253
254 ;;; Server code
255
256 (defcustom erc-dcc-host nil
257 "*IP address to use for outgoing DCC offers.
258 Should be set to a string or nil, if nil, automatic detection of the
259 host interface to use will be attempted."
260 :group 'erc-dcc
261 :type (list 'choice (list 'const :tag "Auto-detect" nil)
262 (list 'string :tag "IP-address"
263 :valid-regexp erc-dcc-ipv4-regexp)))
264
265 (defcustom erc-dcc-send-request 'ask
266 "*How to treat incoming DCC Send requests.
267 'ask - Report the Send request, and wait for the user to manually accept it
268 You might want to set `erc-dcc-auto-masks' for this.
269 'auto - Automatically accept the request and begin downloading the file
270 'ignore - Ignore incoming DCC Send requests completely."
271 :group 'erc-dcc
272 :type '(choice (const ask) (const auto) (const ignore)))
273
274 (defun erc-dcc-get-host (proc)
275 "Returns the local IP address used for an open PROCess."
276 (format-network-address (process-contact proc :local) t))
277
278 (defun erc-dcc-host ()
279 "Determine the IP address we are using.
280 If variable `erc-dcc-host' is non-nil, use it. Otherwise call
281 `erc-dcc-get-host' on the erc-server-process."
282 (or erc-dcc-host (erc-dcc-get-host erc-server-process)
283 (error "Unable to determine local address")))
284
285 (defcustom erc-dcc-port-range nil
286 "If nil, any available user port is used for outgoing DCC connections.
287 If set to a cons, it specifies a range of ports to use in the form (min . max)"
288 :group 'erc-dcc
289 :type '(choice
290 (const :tag "Any port" nil)
291 (cons :tag "Port range"
292 (integer :tag "Lower port")
293 (integer :tag "Upper port"))))
294
295 (defcustom erc-dcc-auto-masks nil
296 "List of regexps matching user identifiers whose DCC send offers should be
297 accepted automatically. A user identifier has the form \"nick!login@host\".
298 For instance, to accept all incoming DCC send offers automatically, add the
299 string \".*!.*@.*\" to this list."
300 :group 'erc-dcc
301 :type '(repeat regexp))
302
303 (defun erc-dcc-server (name filter sentinel)
304 "Start listening on a port for an incoming DCC connection. Returns the newly
305 created subprocess, or nil."
306 (let ((port (or (and erc-dcc-port-range (car erc-dcc-port-range)) t))
307 (upper (and erc-dcc-port-range (cdr erc-dcc-port-range)))
308 process)
309 (while (not process)
310 (condition-case err
311 (setq process
312 (make-network-process :name name
313 :buffer nil
314 :host (erc-dcc-host)
315 :service port
316 :nowait t
317 :noquery nil
318 :filter filter
319 :sentinel sentinel
320 :log #'erc-dcc-server-accept
321 :server t))
322 (file-error
323 (unless (and (string= "Cannot bind server socket" (cadr err))
324 (string= "address already in use" (caddr err)))
325 (signal (car err) (cdr err)))
326 (setq port (1+ port))
327 (unless (< port upper)
328 (error "No available ports in erc-dcc-port-range")))))
329 process))
330
331 (defun erc-dcc-server-accept (server client message)
332 "Log an accepted DCC offer, then terminate the listening process and set up
333 the accepted connection."
334 (erc-log (format "(erc-dcc-server-accept): server %s client %s message %s"
335 server client message))
336 (when (and (string-match "^accept from " message)
337 (processp server) (processp client))
338 (let ((elt (erc-dcc-member :peer server)))
339 ;; change the entry in erc-dcc-list from the listening process to the
340 ;; accepted process
341 (setq elt (plist-put elt :peer client))
342 ;; delete the listening process, as we've accepted the connection
343 (delete-process server))))
344
345 ;;; Interactive command handling
346
347 (defcustom erc-dcc-get-default-directory nil
348 "*Default directory for incoming DCC file transfers.
349 If this is nil, then the current value of `default-directory' is used."
350 :group 'erc-dcc
351 :type '(choice (const nil :tag "Default directory") directory))
352
353 ;;;###autoload
354 (defun erc-cmd-DCC (cmd &rest args)
355 "Parser for /dcc command.
356 This figures out the dcc subcommand and calls the appropriate routine to
357 handle it. The function dispatched should be named \"erc-dcc-do-FOO-command\",
358 where FOO is one of CLOSE, GET, SEND, LIST, CHAT, etc."
359 (when cmd
360 (let ((fn (intern-soft (concat "erc-dcc-do-" (upcase cmd) "-command"))))
361 (if fn
362 (apply fn erc-server-process args)
363 (erc-display-message
364 nil 'notice 'active
365 'dcc-command-undefined ?c cmd)
366 (apropos "erc-dcc-do-.*-command")
367 t))))
368
369 ;;;###autoload
370 (defun pcomplete/erc-mode/DCC ()
371 "Provides completion for the /DCC command."
372 (pcomplete-here (append '("chat" "close" "get" "list")
373 (when (fboundp 'make-network-process) '("send"))))
374 (pcomplete-here
375 (case (intern (downcase (pcomplete-arg 1)))
376 (chat (mapcar (lambda (elt) (plist-get elt :nick))
377 (erc-remove-if-not
378 #'(lambda (elt)
379 (eq (plist-get elt :type) 'CHAT))
380 erc-dcc-list)))
381 (close (remove-duplicates
382 (mapcar (lambda (elt) (symbol-name (plist-get elt :type)))
383 erc-dcc-list) :test 'string=))
384 (get (mapcar #'erc-dcc-nick
385 (erc-remove-if-not
386 #'(lambda (elt)
387 (eq (plist-get elt :type) 'GET))
388 erc-dcc-list)))
389 (send (pcomplete-erc-all-nicks))))
390 (pcomplete-here
391 (case (intern (downcase (pcomplete-arg 2)))
392 (get (mapcar (lambda (elt) (plist-get elt :file))
393 (erc-remove-if-not
394 #'(lambda (elt)
395 (and (eq (plist-get elt :type) 'GET)
396 (erc-nick-equal-p (erc-extract-nick
397 (plist-get elt :nick))
398 (pcomplete-arg 1))))
399 erc-dcc-list)))
400 (close (mapcar #'erc-dcc-nick
401 (erc-remove-if-not
402 #'(lambda (elt)
403 (eq (plist-get elt :type)
404 (intern (upcase (pcomplete-arg 1)))))
405 erc-dcc-list)))
406 (send (pcomplete-entries)))))
407
408 (defun erc-dcc-do-CHAT-command (proc &optional nick)
409 (when nick
410 (let ((elt (erc-dcc-member :nick nick :type 'CHAT :parent proc)))
411 (if (and elt (not (processp (plist-get elt :peer))))
412 ;; accept an existing chat offer
413 ;; FIXME: perhaps /dcc accept like other clients?
414 (progn (erc-dcc-chat-accept elt erc-server-process)
415 (erc-display-message
416 nil 'notice 'active
417 'dcc-chat-accept ?n nick)
418 t)
419 (erc-dcc-chat nick erc-server-process)
420 (erc-display-message
421 nil 'notice 'active
422 'dcc-chat-offer ?n nick)
423 t))))
424
425 (defun erc-dcc-do-CLOSE-command (proc &optional type nick)
426 "/dcc close type nick
427 type and nick are optional."
428 ;; FIXME, should also work if only nick is specified
429 (when (string-match (concat "^\\s-*\\(\\S-+\\)? *\\("
430 erc-valid-nick-regexp "\\)?\\s-*$") line)
431 (let ((type (when (match-string 1 line)
432 (intern (upcase (match-string 1 line)))))
433 (nick (match-string 2 line))
434 (ret t))
435 (while ret
436 (if nick
437 (setq ret (erc-dcc-member :type type :nick nick))
438 (setq ret (erc-dcc-member :type type)))
439 (when ret
440 ;; found a match - delete process if it exists.
441 (and (processp (plist-get ret :peer))
442 (delete-process (plist-get ret :peer)))
443 (setq erc-dcc-list (delq ret erc-dcc-list))
444 (erc-display-message
445 nil 'notice 'active
446 'dcc-closed
447 ?T (plist-get ret :type)
448 ?n (erc-extract-nick (plist-get ret :nick))))))
449 t))
450
451 (defun erc-dcc-do-GET-command (proc nick &optional file)
452 (let* ((elt (erc-dcc-member :nick nick :type 'GET))
453 (filename (or file (plist-get elt :file) "unknown")))
454 (if elt
455 (let* ((file (read-file-name
456 (format "Local filename (default %s): "
457 (file-name-nondirectory filename))
458 (or erc-dcc-get-default-directory
459 default-directory)
460 (expand-file-name (file-name-nondirectory filename)
461 (or erc-dcc-get-default-directory
462 default-directory)))))
463 (cond ((file-exists-p file)
464 (if (yes-or-no-p (format "File %s exists. Overwrite? "
465 file))
466 (erc-dcc-get-file elt file proc)
467 (erc-display-message
468 nil '(notice error) proc
469 'dcc-get-cmd-aborted
470 ?n nick ?f filename)))
471 (t
472 (erc-dcc-get-file elt file proc))))
473 (erc-display-message
474 nil '(notice error) 'active
475 'dcc-get-notfound ?n nick ?f filename))))
476
477 (defun erc-dcc-do-LIST-command (proc)
478 "This is the handler for the /dcc list command.
479 It lists the current state of `erc-dcc-list' in an easy to read manner."
480 (let ((alist erc-dcc-list)
481 size elt)
482 (erc-display-message
483 nil 'notice 'active
484 'dcc-list-head)
485 (erc-display-message
486 nil 'notice 'active
487 'dcc-list-line)
488 (while alist
489 (setq elt (car alist)
490 alist (cdr alist))
491
492 (setq size (or (and (plist-member elt :size)
493 (plist-get elt :size))
494 ""))
495 (setq size
496 (cond ((null size) "")
497 ((numberp size) (number-to-string size))
498 ((string= size "") "unknown")))
499 (erc-display-message
500 nil 'notice 'active
501 'dcc-list-item
502 ?n (erc-dcc-nick elt)
503 ?t (plist-get elt :type)
504 ?a (if (processp (plist-get elt :peer))
505 (process-status (plist-get elt :peer))
506 "no")
507 ?s (concat size
508 (if (and (eq 'GET (plist-get elt :type))
509 (plist-member elt :file)
510 (buffer-live-p (get-buffer (plist-get elt :file)))
511 (plist-member elt :size))
512 (concat " (" (number-to-string
513 (* 100
514 (/ (buffer-size
515 (get-buffer (plist-get elt :file)))
516 (plist-get elt :size))))
517 "%)")))
518 ?f (or (and (plist-member elt :file) (plist-get elt :file)) "")))
519 (erc-display-message
520 nil 'notice 'active
521 'dcc-list-end)
522 t))
523
524 (defun erc-dcc-do-SEND-command (proc nick file)
525 "Offer FILE to NICK by sending a ctcp dcc send message."
526 (if (file-exists-p file)
527 (progn
528 (erc-display-message
529 nil 'notice 'active
530 'dcc-send-offer ?n nick ?f file)
531 (erc-dcc-send-file nick file) t)
532 (erc-display-message nil '(notice error) proc "File not found") t))
533
534 ;;; Server message handling (i.e. messages from remote users)
535
536 ;;;###autoload
537 (defvar erc-ctcp-query-DCC-hook '(erc-ctcp-query-DCC)
538 "Hook variable for CTCP DCC queries")
539
540 (defvar erc-dcc-query-handler-alist
541 '(("SEND" . erc-dcc-handle-ctcp-send)
542 ("CHAT" . erc-dcc-handle-ctcp-chat)))
543
544 ;;;###autoload
545 (defun erc-ctcp-query-DCC (proc nick login host to query)
546 "The function called when a CTCP DCC request is detected by the client.
547 It examines the DCC subcommand, and calls the appropriate routine for
548 that subcommand."
549 (let* ((cmd (cadr (split-string query " ")))
550 (handler (cdr (assoc cmd erc-dcc-query-handler-alist))))
551 (if handler
552 (funcall handler proc query nick login host to)
553 ;; FIXME: Send a ctcp error notice to the remote end?
554 (erc-display-message
555 nil '(notice error) proc
556 'dcc-ctcp-unknown
557 ?q query ?n nick ?u login ?h host))))
558
559 (defconst erc-dcc-ctcp-query-send-regexp
560 "^DCC SEND \\([^ ]+\\) \\([0-9]+\\) \\([0-9]+\\) *\\([0-9]*\\)")
561
562 (defun erc-dcc-handle-ctcp-send (proc query nick login host to)
563 "This is called if a CTCP DCC SEND subcommand is sent to the client.
564 It extracts the information about the dcc request and adds it to
565 `erc-dcc-list'."
566 (unless (eq erc-dcc-send-request 'ignore)
567 (cond
568 ((not (erc-current-nick-p to))
569 ;; DCC SEND requests must be sent to you, and you alone.
570 (erc-display-message
571 nil 'notice proc
572 'dcc-request-bogus
573 ?r "SEND" ?n nick ?u login ?h host))
574 ((string-match erc-dcc-ctcp-query-send-regexp query)
575 (let ((filename (match-string 1 query))
576 (ip (erc-decimal-to-ip (match-string 2 query)))
577 (port (match-string 3 query))
578 (size (match-string 4 query)))
579 ;; FIXME: a warning really should also be sent
580 ;; if the ip address != the host the dcc sender is on.
581 (erc-display-message
582 nil 'notice proc
583 'dcc-send-offered
584 ?f filename ?n nick ?u login ?h host
585 ?s (if (string= size "") "unknown" size))
586 (and (< (string-to-number port) 1025)
587 (erc-display-message
588 nil 'notice proc
589 'dcc-privileged-port
590 ?p port))
591 (erc-dcc-list-add
592 'GET (format "%s!%s@%s" nick login host)
593 nil proc
594 :ip ip :port port :file filename
595 :size (string-to-number size))
596 (if (and (eq erc-dcc-send-request 'auto)
597 (erc-dcc-auto-mask-p (format "\"%s!%s@%s\"" nick login host)))
598 (erc-dcc-get-file (car erc-dcc-list) filename proc))))
599 (t
600 (erc-display-message
601 nil 'notice proc
602 'dcc-malformed
603 ?n nick ?u login ?h host ?q query)))))
604
605 (defun erc-dcc-auto-mask-p (spec)
606 "Takes a full SPEC of a user in the form \"nick!login@host\" and
607 matches against all the regexp's in `erc-dcc-auto-masks'. If any
608 match, returns that regexp and nil otherwise."
609 (let ((lst erc-dcc-auto-masks))
610 (while (and lst
611 (not (string-match (car lst) spec)))
612 (setq lst (cdr lst)))
613 (and lst (car lst))))
614
615 (defconst erc-dcc-ctcp-query-chat-regexp
616 "^DCC CHAT +chat +\\([0-9]+\\) +\\([0-9]+\\)")
617
618 (defcustom erc-dcc-chat-request 'ask
619 "*How to treat incoming DCC Chat requests.
620 'ask - Report the Chat request, and wait for the user to manually accept it
621 'auto - Automatically accept the request and open a new chat window
622 'ignore - Ignore incoming DCC chat requests completely."
623 :group 'erc-dcc
624 :type '(choice (const ask) (const auto) (const ignore)))
625
626 (defun erc-dcc-handle-ctcp-chat (proc query nick login host to)
627 (unless (eq erc-dcc-chat-request 'ignore)
628 (cond
629 (;; DCC CHAT requests must be sent to you, and you alone.
630 (not (erc-current-nick-p to))
631 (erc-display-message
632 nil '(notice error) proc
633 'dcc-request-bogus ?r "CHAT" ?n nick ?u login ?h host))
634 ((string-match erc-dcc-ctcp-query-chat-regexp query)
635 ;; We need to use let* here, since erc-dcc-member might clutter
636 ;; the match value.
637 (let* ((ip (erc-decimal-to-ip (match-string 1 query)))
638 (port (match-string 2 query))
639 (elt (erc-dcc-member :nick nick :type 'CHAT)))
640 ;; FIXME: A warning really should also be sent if the ip
641 ;; address != the host the dcc sender is on.
642 (erc-display-message
643 nil 'notice proc
644 'dcc-chat-offered
645 ?n nick ?u login ?h host ?p port)
646 (and (< (string-to-number port) 1025)
647 (erc-display-message
648 nil 'notice proc
649 'dcc-privileged-port ?p port))
650 (cond (elt
651 ;; XXX: why are we updating ip/port on the existing connection?
652 (setq elt (plist-put (plist-put elt :port port) :ip ip))
653 (erc-display-message
654 nil 'notice proc
655 'dcc-chat-discarded ?n nick ?u login ?h host))
656 (t
657 (erc-dcc-list-add
658 'CHAT (format "%s!%s@%s" nick login host)
659 nil proc
660 :ip ip :port port)))
661 (if (eq erc-dcc-chat-request 'auto)
662 (erc-dcc-chat-accept (erc-dcc-member :nick nick :type 'CHAT)
663 proc))))
664 (t
665 (erc-display-message
666 nil '(notice error) proc
667 'dcc-malformed ?n nick ?u login ?h host ?q query)))))
668
669
670 (defvar erc-dcc-entry-data nil
671 "Holds the `erc-dcc-list' entry for this DCC connection.")
672 (make-variable-buffer-local 'erc-dcc-entry-data)
673
674 ;;; SEND handling
675
676 (defcustom erc-dcc-block-size 1024
677 "*Block size to use for DCC SEND sessions."
678 :group 'erc-dcc
679 :type 'integer)
680
681 (defcustom erc-dcc-pump-bytes nil
682 "*If set to an integer, keep sending until that number of bytes are
683 unconfirmed."
684 :group 'erc-dcc
685 :type '(choice (const nil) integer))
686
687 (defsubst erc-dcc-get-parent (proc)
688 (plist-get (erc-dcc-member :peer proc) :parent))
689
690 (defun erc-dcc-send-block (proc)
691 "Send one block of data.
692 PROC is the process-object of the DCC connection. Returns the number of
693 bytes sent."
694 (let* ((elt (erc-dcc-member :peer proc))
695 (confirmed-marker (plist-get elt :sent))
696 (sent-marker (plist-get elt :sent)))
697 (with-current-buffer (process-buffer proc)
698 (when erc-verbose-dcc
699 (erc-display-message
700 nil 'notice (erc-dcc-get-parent proc)
701 (format "DCC: Confirmed %d, sent %d, sending block now"
702 (- confirmed-marker (point-min))
703 (- sent-marker (point-min)))))
704 (let* ((end (min (+ sent-marker erc-dcc-block-size)
705 (point-max)))
706 (string (buffer-substring-no-properties sent-marker end)))
707 (when (< sent-marker end)
708 (set-marker sent-marker end)
709 (process-send-string proc string))
710 (length string)))))
711
712 (defun erc-dcc-send-filter (proc string)
713 (erc-assert (= (% (length string) 4) 0))
714 (let* ((size (erc-unpack-int (substring string (- (length string) 4))))
715 (elt (erc-dcc-member :peer proc))
716 (parent (plist-get elt :parent))
717 (sent-marker (plist-get elt :sent))
718 (confirmed-marker (plist-get elt :confirmed)))
719 (with-current-buffer (process-buffer proc)
720 (set-marker confirmed-marker (+ (point-min) size))
721 (cond
722 ((and (= confirmed-marker sent-marker)
723 (= confirmed-marker (point-max)))
724 (erc-display-message
725 nil 'notice parent
726 'dcc-send-finished
727 ?n (plist-get elt :nick)
728 ?f buffer-file-name
729 ?s (number-to-string (- sent-marker (point-min))))
730 (setq erc-dcc-list (delete elt erc-dcc-list))
731 (set-buffer-modified-p nil)
732 (kill-buffer (current-buffer))
733 (delete-process proc))
734 ((<= confirmed-marker sent-marker)
735 (while (and (< (- sent-marker confirmed-marker)
736 (or erc-dcc-pump-bytes
737 erc-dcc-block-size))
738 (> (erc-dcc-send-block proc) 0))))
739 ((> confirmed-marker sent-marker)
740 (erc-display-message
741 nil 'notice parent
742 (format "DCC: Client confirmed too much!"))
743 (delete-process proc))))))
744
745 (defcustom erc-dcc-send-connect-hook
746 '((lambda (proc)
747 (erc-display-message
748 nil 'notice (erc-dcc-get-parent proc)
749 (format "DCC: SEND connect from %s"
750 (format-network-address (process-contact proc :remote)))))
751 erc-dcc-send-block)
752 "*Hook run whenever the remote end of a DCC SEND offer connected to your
753 listening port."
754 :group 'erc-dcc
755 :type 'hook)
756
757 (defun erc-dcc-nick (plist)
758 "Extract the nickname portion of the :nick property value in PLIST."
759 (erc-extract-nick (plist-get plist :nick)))
760
761 (defun erc-dcc-send-sentinel (proc event)
762 (let* ((elt (erc-dcc-member :peer proc))
763 (buf (marker-buffer (plist-get elt :sent))))
764 (cond
765 ((string-match "^open from " event)
766 (when elt
767 (with-current-buffer buf
768 (set-process-buffer proc buf)
769 (setq erc-dcc-entry-data elt))
770 (run-hook-with-args 'erc-dcc-send-connect-hook proc))))))
771
772 (defun erc-dcc-find-file (file)
773 (with-current-buffer (generate-new-buffer (file-name-nondirectory file))
774 (insert-file-contents-literally file)
775 (setq buffer-file-name file)
776 (current-buffer)))
777
778 (defun erc-dcc-file-to-name (file)
779 (with-temp-buffer
780 (insert (file-name-nondirectory file))
781 (subst-char-in-region (point-min) (point-max) ? ?_ t)
782 (buffer-string)))
783
784 (defun erc-dcc-send-file (nick file &optional pproc)
785 "Open a socket for incoming connections, and send a CTCP send request to the
786 other client."
787 (interactive "sNick: \nfFile: ")
788 (when (null pproc) (if (processp erc-server-process)
789 (setq pproc erc-server-process)
790 (error "Can not find parent process")))
791 (if (featurep 'make-network-process)
792 (let* ((buffer (erc-dcc-find-file file))
793 (size (buffer-size buffer))
794 (start (with-current-buffer buffer
795 (set-marker (make-marker) (point-min))))
796 (sproc (erc-dcc-server "dcc-send"
797 'erc-dcc-send-filter
798 'erc-dcc-send-sentinel))
799 (contact (process-contact sproc)))
800 (erc-dcc-list-add
801 'SEND nick sproc pproc
802 :file file :size size
803 :sent start :confirmed (copy-marker start))
804 (process-send-string
805 pproc (format "PRIVMSG %s :\C-aDCC SEND %s %s %d %d\C-a\n"
806 nick (erc-dcc-file-to-name file)
807 (erc-ip-to-decimal (nth 0 contact))
808 (nth 1 contact)
809 size)))
810 (error "`make-network-process' not supported by your emacs.")))
811
812 ;;; GET handling
813
814 (defvar erc-dcc-byte-count nil)
815 (make-variable-buffer-local 'erc-dcc-byte-count)
816
817 (defun erc-dcc-get-file (entry file parent-proc)
818 "This function does the work of setting up a transfer from the remote client
819 to the local one over a tcp connection. This involves setting up a process
820 filter and a process sentinel, and making the connection."
821 (let* ((buffer (generate-new-buffer (file-name-nondirectory file)))
822 proc)
823 (with-current-buffer buffer
824 (fundamental-mode)
825 ;; This is necessary to have the buffer saved as-is in GNU
826 ;; Emacs.
827 ;; XEmacs change: We don't have `set-buffer-multibyte', setting
828 ;; coding system to 'binary below takes care of us.
829 (when (fboundp 'set-buffer-multibyte)
830 (set-buffer-multibyte nil))
831
832 (setq mode-line-process '(":%s")
833 buffer-file-type t
834 buffer-read-only t)
835 (set-visited-file-name file)
836
837 (setq erc-server-process parent-proc
838 erc-dcc-entry-data entry)
839 (setq erc-dcc-byte-count 0)
840 (setq proc
841 (funcall erc-dcc-connect-function
842 "dcc-get" buffer
843 (plist-get entry :ip)
844 (string-to-number (plist-get entry :port))
845 entry))
846 (set-process-buffer proc buffer)
847 ;; The following two lines make saving as-is work under Windows
848 (set-process-coding-system proc 'binary 'binary)
849 (set-buffer-file-coding-system 'binary t)
850
851 (set-process-filter proc 'erc-dcc-get-filter)
852 (set-process-sentinel proc 'erc-dcc-get-sentinel)
853 (setq entry (plist-put entry :start-time (erc-current-time)))
854 (setq entry (plist-put entry :peer proc)))))
855
856 (defun erc-dcc-get-filter (proc str)
857 "This is the process filter for transfers from other clients to this one.
858 It reads incoming bytes from the network and stores them in the DCC
859 buffer, and sends back the replies after each block of data per the DCC
860 protocol spec. Well not really. We write back a reply after each read,
861 rather than every 1024 byte block, but nobody seems to care."
862 (with-current-buffer (process-buffer proc)
863 (setq buffer-read-only nil) ;; FIXME
864 (goto-char (point-max))
865 (insert (string-make-unibyte str))
866
867 (setq erc-dcc-byte-count (+ (length str) erc-dcc-byte-count))
868 (erc-assert (= erc-dcc-byte-count (1- (point-max))))
869 (and erc-verbose-dcc
870 (erc-display-message
871 nil 'notice erc-server-process
872 'dcc-get-bytes-received
873 ?f (file-name-nondirectory buffer-file-name)
874 ?b (number-to-string erc-dcc-byte-count)))
875 (cond
876 ((and (> (plist-get erc-dcc-entry-data :size) 0)
877 (> erc-dcc-byte-count (plist-get erc-dcc-entry-data :size)))
878 (erc-display-message
879 nil '(error notice) 'active
880 'dcc-get-file-too-long
881 ?f (file-name-nondirectory buffer-file-name))
882 (delete-process proc))
883 (t
884 (process-send-string
885 proc (erc-pack-int erc-dcc-byte-count 4))))))
886
887
888 (defun erc-dcc-get-sentinel (proc event)
889 "This is the process sentinel for CTCP DCC SEND connections.
890 It shuts down the connection and notifies the user that the
891 transfer is complete."
892 ;; FIXME, we should look at EVENT, and also check size.
893 (with-current-buffer (process-buffer proc)
894 (delete-process proc)
895 (setq buffer-read-only nil)
896 (setq erc-dcc-list (delete erc-dcc-entry-data erc-dcc-list))
897 (erc-display-message
898 nil 'notice erc-server-process
899 'dcc-get-complete
900 ?f (file-name-nondirectory buffer-file-name)
901 ?s (number-to-string (buffer-size))
902 ?t (format "%.0f"
903 (erc-time-diff (plist-get erc-dcc-entry-data :start-time)
904 (erc-current-time))))
905 (save-buffer))
906 (kill-buffer (process-buffer proc))
907 (delete-process proc))
908
909 ;;; CHAT handling
910
911 (defcustom erc-dcc-chat-buffer-name-format "DCC-CHAT-%s"
912 "*Format to use for DCC Chat buffer names."
913 :group 'erc-dcc
914 :type 'string)
915
916 (defcustom erc-dcc-chat-mode-hook nil
917 "*Hook calls when `erc-dcc-chat-mode' finished setting up the buffer."
918 :group 'erc-dcc
919 :type 'hook)
920
921 (defcustom erc-dcc-chat-connect-hook nil
922 ""
923 :group 'erc-dcc
924 :type 'hook)
925
926 (defcustom erc-dcc-chat-exit-hook nil
927 ""
928 :group 'erc-dcc
929 :type 'hook)
930
931 (defun erc-cmd-CREQ (line &optional force)
932 "Set or get the DCC chat request flag.
933 Possible values are: ask, auto, ignore."
934 (when (string-match "^\\s-*\\(auto\\|ask\\|ignore\\)?$" line)
935 (let ((cmd (match-string 1 line)))
936 (if (stringp cmd)
937 (erc-display-message
938 nil 'notice 'active
939 (format "Set DCC Chat requests to %S"
940 (setq erc-dcc-chat-request (intern cmd))))
941 (erc-display-message nil 'notice 'active
942 (format "DCC Chat requests are set to %S"
943 erc-dcc-chat-request)))
944 t)))
945
946 (defun erc-cmd-SREQ (line &optional force)
947 "Set or get the DCC send request flag.
948 Possible values are: ask, auto, ignore."
949 (when (string-match "^\\s-*\\(auto\\|ask\\|ignore\\)?$" line)
950 (let ((cmd (match-string 1 line)))
951 (if (stringp cmd)
952 (erc-display-message
953 nil 'notice 'active
954 (format "Set DCC Send requests to %S"
955 (setq erc-dcc-send-request (intern cmd))))
956 (erc-display-message nil 'notice 'active
957 (format "DCC Send requests are set to %S"
958 erc-dcc-send-request)))
959 t)))
960
961 (defun pcomplete/erc-mode/CREQ ()
962 (pcomplete-here '("auto" "ask" "ignore")))
963 (defalias 'pcomplete/erc-mode/SREQ 'pcomplete/erc-mode/CREQ)
964
965 (defvar erc-dcc-chat-filter-hook '(erc-dcc-chat-parse-output)
966 "*Hook to run after doing parsing (and possible insertion) of DCC messages.")
967
968 (defvar erc-dcc-chat-mode-map
969 (let ((map (make-sparse-keymap)))
970 (define-key map (kbd "RET") 'erc-send-current-line)
971 (define-key map "\t" 'erc-complete-word)
972 map)
973 "Keymap for `erc-dcc-mode'.")
974
975 (defun erc-dcc-chat-mode ()
976 "Major mode for wasting time via DCC chat."
977 (interactive)
978 (kill-all-local-variables)
979 (setq mode-line-process '(":%s")
980 mode-name "DCC-Chat"
981 major-mode 'erc-dcc-chat-mode
982 erc-send-input-line-function 'erc-dcc-chat-send-input-line
983 erc-default-recipients '(dcc))
984 (use-local-map erc-dcc-chat-mode-map)
985 (run-hooks 'erc-dcc-chat-mode-hook))
986
987 (defun erc-dcc-chat-send-input-line (recipient line &optional force)
988 "Send LINE to the remote end.
989 Argument RECIPIENT should always be the symbol dcc, and force
990 is ignored."
991 ;; FIXME: We need to get rid of all force arguments one day!
992 (if (eq recipient 'dcc)
993 (process-send-string
994 (get-buffer-process (current-buffer)) line)
995 (error "erc-dcc-chat-send-input-line in %s" (current-buffer))))
996
997 (defun erc-dcc-chat (nick &optional pproc)
998 "Open a socket for incoming connections, and send a chat request to the
999 other client."
1000 (interactive "sNick: ")
1001 (when (null pproc) (if (processp erc-server-process)
1002 (setq pproc erc-server-process)
1003 (error "Can not find parent process")))
1004 (let* ((sproc (erc-dcc-server "dcc-chat-out"
1005 'erc-dcc-chat-filter
1006 'erc-dcc-chat-sentinel))
1007 (contact (process-contact sproc)))
1008 (erc-dcc-list-add 'OCHAT nick sproc pproc)
1009 (process-send-string pproc
1010 (format "PRIVMSG %s :\C-aDCC CHAT chat %s %d\C-a\n"
1011 nick
1012 (erc-ip-to-decimal (nth 0 contact)) (nth 1 contact)))))
1013
1014 (defvar erc-dcc-from)
1015 (make-variable-buffer-local 'erc-dcc-from)
1016
1017 (defvar erc-dcc-unprocessed-output)
1018 (make-variable-buffer-local 'erc-dcc-unprocessed-output)
1019
1020 (defun erc-dcc-chat-setup (entry)
1021 "Setup a DCC chat buffer, returning the buffer."
1022 (let* ((nick (erc-extract-nick (plist-get entry :nick)))
1023 (buffer (generate-new-buffer
1024 (format erc-dcc-chat-buffer-name-format nick)))
1025 (proc (plist-get entry :peer))
1026 (parent-proc (plist-get entry :parent)))
1027 (erc-setup-buffer buffer)
1028 ;; buffer is now the current buffer.
1029 (erc-dcc-chat-mode)
1030 (setq erc-server-process parent-proc)
1031 (setq erc-dcc-from nick)
1032 (setq erc-dcc-entry-data entry)
1033 (setq erc-dcc-unprocessed-output "")
1034 (setq erc-insert-marker (set-marker (make-marker) (point-max)))
1035 (erc-display-prompt buffer (point-max))
1036 (set-process-buffer proc buffer)
1037 (add-hook 'kill-buffer-hook 'erc-dcc-chat-buffer-killed nil t)
1038 (run-hook-with-args 'erc-dcc-chat-connect-hook proc)
1039 buffer))
1040
1041 (defun erc-dcc-chat-accept (entry parent-proc)
1042 "Accept an incoming DCC connection and open a DCC window"
1043 (let* ((nick (erc-extract-nick (plist-get entry :nick)))
1044 buffer proc)
1045 (setq proc
1046 (funcall erc-dcc-connect-function
1047 "dcc-chat" nil
1048 (plist-get entry :ip)
1049 (string-to-number (plist-get entry :port))
1050 entry))
1051 ;; XXX: connected, should we kill the ip/port properties?
1052 (setq entry (plist-put entry :peer proc))
1053 (setq entry (plist-put entry :parent parent-proc))
1054 (set-process-filter proc 'erc-dcc-chat-filter)
1055 (set-process-sentinel proc 'erc-dcc-chat-sentinel)
1056 (setq buffer (erc-dcc-chat-setup entry))))
1057
1058 (defun erc-dcc-chat-filter (proc str)
1059 (let ((orig-buffer (current-buffer)))
1060 (unwind-protect
1061 (progn
1062 (set-buffer (process-buffer proc))
1063 (setq erc-dcc-unprocessed-output
1064 (concat erc-dcc-unprocessed-output str))
1065 (run-hook-with-args 'erc-dcc-chat-filter-hook proc
1066 erc-dcc-unprocessed-output))
1067 (set-buffer orig-buffer))))
1068
1069 (defun erc-dcc-chat-parse-output (proc str)
1070 (save-match-data
1071 (let ((posn 0)
1072 line)
1073 (while (string-match "\n" str posn)
1074 (setq line (substring str posn (match-beginning 0)))
1075 (setq posn (match-end 0))
1076 (erc-display-message
1077 nil nil proc
1078 'dcc-chat-privmsg ?n (erc-propertize erc-dcc-from 'face
1079 'erc-nick-default-face) ?m line))
1080 (setq erc-dcc-unprocessed-output (substring str posn)))))
1081
1082 (defun erc-dcc-chat-buffer-killed ()
1083 (erc-dcc-chat-close "killed buffer"))
1084
1085 (defun erc-dcc-chat-close (&optional event)
1086 "Close a DCC chat, removing any associated processes and tidying up
1087 `erc-dcc-list'"
1088 (let ((proc (plist-get erc-dcc-entry-data :peer))
1089 (evt (or event "")))
1090 (when proc
1091 (setq erc-dcc-list (delq erc-dcc-entry-data erc-dcc-list))
1092 (run-hook-with-args 'erc-dcc-chat-exit-hook proc)
1093 (delete-process proc)
1094 (erc-display-message
1095 nil 'notice erc-server-process
1096 'dcc-chat-ended ?n erc-dcc-from ?t (current-time-string) ?e evt)
1097 (setq erc-dcc-entry-data (plist-put erc-dcc-entry-data :peer nil)))))
1098
1099 (defun erc-dcc-chat-sentinel (proc event)
1100 (let ((buf (current-buffer))
1101 (elt (erc-dcc-member :peer proc)))
1102 ;; the sentinel is also notified when the connection is opened, so don't
1103 ;; immediately kill it again
1104 ;(message "buf %s elt %S evt %S" buf elt event)
1105 (unwind-protect
1106 (if (string-match "^open from" event)
1107 (erc-dcc-chat-setup elt)
1108 (erc-dcc-chat-close event))
1109 (set-buffer buf))))
1110
1111 (defun erc-dcc-no-such-nick (proc parsed)
1112 "Detect and handle no-such-nick replies from the IRC server."
1113 (let* ((elt (erc-dcc-member :nick (second (erc-response.command-args parsed))
1114 :parent proc))
1115 (peer (plist-get elt :peer)))
1116 (when (or (and (processp peer) (not (eq (process-status peer) 'open)))
1117 elt)
1118 ;; Since we already created an entry before sending the CTCP
1119 ;; message, we now remove it, if it doesn't point to a process
1120 ;; which is already open.
1121 (setq erc-dcc-list (delq elt erc-dcc-list))
1122 (if (processp peer) (delete-process peer)))
1123 nil))
1124
1125 (add-hook 'erc-server-401-functions 'erc-dcc-no-such-nick)
1126
1127 (provide 'erc-dcc)
1128
1129 ;;; erc-dcc.el ends here
1130 ;;
1131 ;; Local Variables:
1132 ;; indent-tabs-mode: nil
1133 ;; End:
1134
1135 ;; arch-tag: cda5a6b3-c510-4dbe-b699-84cccfa04edb