2 -- Implementation of the DCC protocol
5 local irc = require 'irc'
6 local irc_debug = require 'irc.debug'
7 local misc = require 'irc.misc'
8 local socket = require 'socket'
9 local coroutine = require 'coroutine'
10 local io = require 'io'
11 local string = require 'string'
15 -- This module implements the DCC protocol. File transfers (DCC SEND) are
16 -- handled, but DCC CHAT is not, as of yet.
24 -- private functions {{{
27 -- Sends a file to a remote user, after that user has accepted our DCC SEND
29 -- @param sock Socket to send the file on
30 -- @param file Lua file object corresponding to the file we want to send
31 -- @param packet_size Size of the packets to send the file in
32 local function send_file(sock, file, packet_size)
35 local packet = file:read(packet_size)
36 if not packet then break end
37 bytes = bytes + packet:len()
40 sock:send(packet, index)
41 local new_bytes = misc.int_to_str(sock:receive(4))
42 if new_bytes ~= bytes then
43 index = packet_size - bytes + new_bytes + 1
52 irc._unregister_socket(sock, 'w')
59 -- Handle the connection attempt by a remote user to get our file. Basically
60 -- just swaps out the server socket we were listening on for a client socket
61 -- that we can send data on
62 -- @param ssock Server socket that the remote user connected to
63 -- @param file Lua file object corresponding to the file we want to send
64 -- @param packet_size Size of the packets to send the file in
65 local function handle_connect(ssock, file, packet_size)
66 packet_size = packet_size or 1024
67 local sock = ssock:accept()
70 irc._unregister_socket(ssock, 'r')
71 irc._register_socket(sock, 'w',
72 coroutine.wrap(function(sock)
73 return send_file(sock, file, packet_size)
81 -- Accepts a file from a remote user which has offered it to us.
82 -- @param sock Socket to receive the file on
83 -- @param file Lua file object corresponding to the file we want to save
84 -- @param packet_size Size of the packets to receive the file in
85 local function accept_file(sock, file, packet_size)
88 local packet, err, partial_packet = sock:receive(packet_size)
89 if not packet and err == "timeout" then packet = partial_packet end
90 if not packet then break end
91 if packet:len() == 0 then break end
92 bytes = bytes + packet:len()
93 sock:send(misc.str_to_int(bytes))
99 irc._unregister_socket(sock, 'r')
105 -- public functions {{{
108 -- Offers a file to a remote user.
109 -- @param nick User to offer the file to
110 -- @param filename Filename to offer
111 -- @param port Port to accept connections on (optional, defaults to
112 -- choosing an available port between FIRST_PORT and LAST_PORT
114 function send(nick, filename, port)
115 port = port or FIRST_PORT
116 local sock = base.assert(socket.tcp())
118 err, msg = sock:bind('*', port)
120 until msg ~= "address already in use" and port <= LAST_PORT + 1
121 base.assert(err, msg)
122 base.assert(sock:listen(1))
123 local ip = misc.ip_str_to_int(irc.get_ip())
124 local file = base.assert(io.open(filename))
125 local size = file:seek("end")
127 irc._register_socket(sock, 'r',
128 coroutine.wrap(function(sock)
129 return handle_connect(sock, file)
131 filename = misc.basename(filename)
132 if filename:find(" ") then filename = '"' .. filename .. '"' end
133 irc.send("PRIVMSG", nick, {"DCC SEND " .. filename .. " " ..
134 ip .. " " .. port - 1 .. " " .. size})
139 -- TODO: this shouldn't be a public function
141 -- Accepts a file offer from a remote user. Called when the on_dcc callback
143 -- @param filename Name to save the file as
144 -- @param address IP address of the remote user
145 -- @param port Port to connect to at the remote user
146 -- @param packet_size Size of the packets the remote user will be sending
147 function accept(filename, address, port, packet_size)
148 packet_size = packet_size or 1024
149 local sock = base.assert(socket.tcp())
150 base.assert(sock:connect(misc.ip_int_to_str(address), port))
152 local file = base.assert(io.open(misc.get_unique_filename(filename), "w"))
153 irc._register_socket(sock, 'r',
154 coroutine.wrap(function(sock)
155 return accept_file(sock, file, packet_size)