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 -- internal functions {{{
108 -- Accepts a file offer from a remote user. Called when the on_dcc callback
110 -- @param filename Name to save the file as
111 -- @param address IP address of the remote user
112 -- @param port Port to connect to at the remote user
113 -- @param packet_size Size of the packets the remote user will be sending
114 function _accept(filename, address, port, packet_size)
115 packet_size = packet_size or 1024
116 local sock = base.assert(socket.tcp())
117 base.assert(sock:connect(misc._ip_int_to_str(address), port))
119 local file = base.assert(io.open(misc._get_unique_filename(filename), "w"))
120 irc._register_socket(sock, 'r',
121 coroutine.wrap(function(sock)
122 return accept_file(sock, file, packet_size)
128 -- public functions {{{
131 -- Offers a file to a remote user.
132 -- @param nick User to offer the file to
133 -- @param filename Filename to offer
134 -- @param port Port to accept connections on (optional, defaults to
135 -- choosing an available port between FIRST_PORT and LAST_PORT
137 function send(nick, filename, port)
138 port = port or FIRST_PORT
139 local sock = base.assert(socket.tcp())
141 err, msg = sock:bind('*', port)
143 until msg ~= "address already in use" and port <= LAST_PORT + 1
144 base.assert(err, msg)
145 base.assert(sock:listen(1))
146 local ip = misc._ip_str_to_int(irc.get_ip())
147 local file = base.assert(io.open(filename))
148 local size = file:seek("end")
150 irc._register_socket(sock, 'r',
151 coroutine.wrap(function(sock)
152 return handle_connect(sock, file)
154 filename = misc._basename(filename)
155 if filename:find(" ") then filename = '"' .. filename .. '"' end
156 irc.send("PRIVMSG", nick, {"DCC SEND " .. filename .. " " ..
157 ip .. " " .. port - 1 .. " " .. size})