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()
43 sock:send(packet, index)
44 local new_bytes, err = sock:receive(4)
46 if err == "timeout" then
53 new_bytes = misc._int_to_str(new_bytes)
56 if new_bytes ~= bytes then
57 index = packet_size - bytes + new_bytes + 1
67 irc._unregister_socket(sock, 'w')
74 -- Handle the connection attempt by a remote user to get our file. Basically
75 -- just swaps out the server socket we were listening on for a client socket
76 -- that we can send data on
77 -- @param ssock Server socket that the remote user connected to
78 -- @param file Lua file object corresponding to the file we want to send
79 -- @param packet_size Size of the packets to send the file in
80 local function handle_connect(ssock, file, packet_size)
81 packet_size = packet_size or 1024
82 local sock = ssock:accept()
85 irc._unregister_socket(ssock, 'r')
86 irc._register_socket(sock, 'w',
87 coroutine.wrap(function(sock)
88 return send_file(sock, file, packet_size)
96 -- Accepts a file from a remote user which has offered it to us.
97 -- @param sock Socket to receive the file on
98 -- @param file Lua file object corresponding to the file we want to save
99 -- @param packet_size Size of the packets to receive the file in
100 local function accept_file(sock, file, packet_size)
103 local packet, err, partial_packet = sock:receive(packet_size)
104 if not packet and err == "timeout" then packet = partial_packet end
105 if not packet then break end
106 if packet:len() == 0 then break end
107 bytes = bytes + packet:len()
108 sock:send(misc._str_to_int(bytes))
110 coroutine.yield(true)
114 irc._unregister_socket(sock, 'r')
120 -- internal functions {{{
123 -- Accepts a file offer from a remote user. Called when the on_dcc callback
125 -- @param filename Name to save the file as
126 -- @param address IP address of the remote user in low level int form
127 -- @param port Port to connect to at the remote user
128 -- @param packet_size Size of the packets the remote user will be sending
129 function _accept(filename, address, port, packet_size)
130 packet_size = packet_size or 1024
131 local sock = base.assert(socket.tcp())
132 base.assert(sock:connect(misc._ip_int_to_str(address), port))
134 local file = base.assert(io.open(misc._get_unique_filename(filename), "w"))
135 irc._register_socket(sock, 'r',
136 coroutine.wrap(function(sock)
137 return accept_file(sock, file, packet_size)
143 -- public functions {{{
146 -- Offers a file to a remote user.
147 -- @param nick User to offer the file to
148 -- @param filename Filename to offer
149 -- @param port Port to accept connections on (optional, defaults to
150 -- choosing an available port between FIRST_PORT and LAST_PORT
152 function send(nick, filename, port)
153 port = port or FIRST_PORT
154 local sock = base.assert(socket.tcp())
156 err, msg = sock:bind('*', port)
158 until msg ~= "address already in use" and port <= LAST_PORT + 1
159 base.assert(err, msg)
160 base.assert(sock:listen(1))
161 local ip = misc._ip_str_to_int(irc.get_ip())
162 local file = base.assert(io.open(filename))
163 local size = file:seek("end")
165 irc._register_socket(sock, 'r',
166 coroutine.wrap(function(sock)
167 return handle_connect(sock, file)
169 filename = misc._basename(filename)
170 if filename:find(" ") then filename = '"' .. filename .. '"' end
171 irc.send("PRIVMSG", nick, c("DCC", "SEND", filename, ip, port - 1, size))