2 -- Implementation of the DCC protocol
5 local irc = require 'irc'
6 local ctcp = require 'irc.ctcp'
7 local c = ctcp._ctcp_quote
8 local irc_debug = require 'irc.debug'
9 local misc = require 'irc.misc'
10 local socket = require 'socket'
11 local coroutine = require 'coroutine'
12 local io = require 'io'
13 local string = require 'string'
17 -- This module implements the DCC protocol. File transfers (DCC SEND) are
18 -- handled, but DCC CHAT is not, as of yet.
26 -- private functions {{{
29 -- Sends a file to a remote user, after that user has accepted our DCC SEND
31 -- @param sock Socket to send the file on
32 -- @param file Lua file object corresponding to the file we want to send
33 -- @param packet_size Size of the packets to send the file in
34 local function send_file(sock, file, packet_size)
37 local packet = file:read(packet_size)
38 if not packet then break end
39 bytes = bytes + packet:len()
42 sock:send(packet, index)
43 local new_bytes = misc._int_to_str(sock:receive(4))
44 if new_bytes ~= bytes then
45 index = packet_size - bytes + new_bytes + 1
54 irc._unregister_socket(sock, 'w')
61 -- Handle the connection attempt by a remote user to get our file. Basically
62 -- just swaps out the server socket we were listening on for a client socket
63 -- that we can send data on
64 -- @param ssock Server socket that the remote user connected to
65 -- @param file Lua file object corresponding to the file we want to send
66 -- @param packet_size Size of the packets to send the file in
67 local function handle_connect(ssock, file, packet_size)
68 packet_size = packet_size or 1024
69 local sock = ssock:accept()
72 irc._unregister_socket(ssock, 'r')
73 irc._register_socket(sock, 'w',
74 coroutine.wrap(function(sock)
75 return send_file(sock, file, packet_size)
83 -- Accepts a file from a remote user which has offered it to us.
84 -- @param sock Socket to receive the file on
85 -- @param file Lua file object corresponding to the file we want to save
86 -- @param packet_size Size of the packets to receive the file in
87 local function accept_file(sock, file, packet_size)
90 local packet, err, partial_packet = sock:receive(packet_size)
91 if not packet and err == "timeout" then packet = partial_packet end
92 if not packet then break end
93 if packet:len() == 0 then break end
94 bytes = bytes + packet:len()
95 sock:send(misc._str_to_int(bytes))
101 irc._unregister_socket(sock, 'r')
107 -- internal functions {{{
110 -- Accepts a file offer from a remote user. Called when the on_dcc callback
112 -- @param filename Name to save the file as
113 -- @param address IP address of the remote user
114 -- @param port Port to connect to at the remote user
115 -- @param packet_size Size of the packets the remote user will be sending
116 function _accept(filename, address, port, packet_size)
117 packet_size = packet_size or 1024
118 local sock = base.assert(socket.tcp())
119 base.assert(sock:connect(misc._ip_int_to_str(address), port))
121 local file = base.assert(io.open(misc._get_unique_filename(filename), "w"))
122 irc._register_socket(sock, 'r',
123 coroutine.wrap(function(sock)
124 return accept_file(sock, file, packet_size)
130 -- public functions {{{
133 -- Offers a file to a remote user.
134 -- @param nick User to offer the file to
135 -- @param filename Filename to offer
136 -- @param port Port to accept connections on (optional, defaults to
137 -- choosing an available port between FIRST_PORT and LAST_PORT
139 function send(nick, filename, port)
140 port = port or FIRST_PORT
141 local sock = base.assert(socket.tcp())
143 err, msg = sock:bind('*', port)
145 until msg ~= "address already in use" and port <= LAST_PORT + 1
146 base.assert(err, msg)
147 base.assert(sock:listen(1))
148 local ip = misc._ip_str_to_int(irc.get_ip())
149 local file = base.assert(io.open(filename))
150 local size = file:seek("end")
152 irc._register_socket(sock, 'r',
153 coroutine.wrap(function(sock)
154 return handle_connect(sock, file)
156 filename = misc._basename(filename)
157 if filename:find(" ") then filename = '"' .. filename .. '"' end
158 irc.send("PRIVMSG", nick, c("DCC", "SEND", filename, ip, port - 1, size))