2 -- Implementation of the DCC protocol
6 local c = ctcp._ctcp_quote
7 local irc_debug = libs.debug
9 local socket = libs.socket
13 -- This module implements the DCC protocol. File transfers (DCC SEND) are
14 -- handled, but DCC CHAT is not, as of yet.
22 -- private functions {{{
25 -- Prints a debug message about DCC events similar to irc.debug.warn, etc.
26 -- @param msg Debug message
27 local function debug_dcc(msg)
28 irc_debug._message("DCC", msg, "\027[0;32m")
34 -- Sends a file to a remote user, after that user has accepted our DCC SEND
36 -- @param sock Socket to send the file on
37 -- @param file Lua file object corresponding to the file we want to send
38 -- @param packet_size Size of the packets to send the file in
39 local function send_file(sock, file, packet_size)
42 local packet = file:read(packet_size)
43 if not packet then break end
44 bytes = bytes + packet:len()
48 sock:send(packet, index)
49 local new_bytes, err = sock:receive(4)
51 if err == "timeout" then
58 new_bytes = misc._int_to_str(new_bytes)
61 if new_bytes ~= bytes then
62 index = packet_size - bytes + new_bytes + 1
70 debug_dcc("File completely sent")
73 irc._unregister_socket(sock, 'w')
80 -- Handle the connection attempt by a remote user to get our file. Basically
81 -- just swaps out the server socket we were listening on for a client socket
82 -- that we can send data on
83 -- @param ssock Server socket that the remote user connected to
84 -- @param file Lua file object corresponding to the file we want to send
85 -- @param packet_size Size of the packets to send the file in
86 local function handle_connect(ssock, file, packet_size)
87 debug_dcc("Offer accepted, beginning to send")
88 packet_size = packet_size or 1024
89 local sock = ssock:accept()
92 irc._unregister_socket(ssock, 'r')
93 irc._register_socket(sock, 'w',
94 coroutine.wrap(function(s)
95 return send_file(s, file, packet_size)
103 -- Accepts a file from a remote user which has offered it to us.
104 -- @param sock Socket to receive the file on
105 -- @param file Lua file object corresponding to the file we want to save
106 -- @param packet_size Size of the packets to receive the file in
107 local function accept_file(sock, file, packet_size)
110 local packet, err, partial_packet = sock:receive(packet_size)
111 if not packet and err == "timeout" then packet = partial_packet end
112 if not packet then break end
113 if packet:len() == 0 then break end
114 bytes = bytes + packet:len()
115 sock:send(misc._str_to_int(bytes))
117 coroutine.yield(true)
119 debug_dcc("File completely received")
122 irc._unregister_socket(sock, 'r')
128 -- internal functions {{{
131 -- Accepts a file offer from a remote user. Called when the on_dcc callback
133 -- @param filename Name to save the file as
134 -- @param address IP address of the remote user in low level int form
135 -- @param port Port to connect to at the remote user
136 -- @param packet_size Size of the packets the remote user will be sending
137 function dcc._accept(filename, address, port, packet_size)
138 debug_dcc("Accepting a DCC SEND request from " .. address .. ":" .. port)
139 packet_size = packet_size or 1024
140 local sock = assert(socket.tcp())
141 assert(sock:connect(address, port))
143 local file = assert(io.open(misc._get_unique_filename(filename), "w"))
144 irc._register_socket(sock, 'r',
145 coroutine.wrap(function(s)
146 return accept_file(s, file, packet_size)
152 -- public functions {{{
155 -- Offers a file to a remote user.
156 -- @param nick User to offer the file to
157 -- @param filename Filename to offer
158 -- @param port Port to accept connections on (optional, defaults to
159 -- choosing an available port between FIRST_PORT and LAST_PORT
161 function dcc.send(nick, filename, port)
162 port = port or dcc.FIRST_PORT
165 sock = assert(socket.tcp())
166 err, msg = sock:bind('*', port)
168 until msg ~= "address already in use" and port <= dcc.LAST_PORT + 1
171 assert(sock:listen(1))
172 local ip = misc._ip_str_to_int(irc.get_ip())
173 local file, err = io.open(filename)
179 local size = file:seek("end")
181 irc._register_socket(sock, 'r',
182 coroutine.wrap(function(s)
183 return handle_connect(s, file)
185 filename = misc._basename(filename)
186 if filename:find(" ") then filename = '"' .. filename .. '"' end
187 debug_dcc("Offering " .. filename .. " to " .. nick .. " from " ..
188 irc.get_ip() .. ":" .. port)
189 irc.send("PRIVMSG", nick, c("DCC", "SEND", filename, ip, port, size))