]> git.lizzy.rs Git - luairc.git/blob - src/irc/dcc.lua
split between internal/public functions in dcc.lua
[luairc.git] / src / irc / dcc.lua
1 ---
2 -- Implementation of the DCC protocol
3 -- initialization {{{
4 local base =      _G
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'
12 -- }}}
13
14 ---
15 -- This module implements the DCC protocol. File transfers (DCC SEND) are
16 -- handled, but DCC CHAT is not, as of yet.
17 module 'irc.dcc'
18
19 -- defaults {{{
20 FIRST_PORT = 1028
21 LAST_PORT = 5000
22 -- }}}
23
24 -- private functions {{{
25 -- send_file {{{
26 --
27 -- Sends a file to a remote user, after that user has accepted our DCC SEND
28 -- invitation
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)
33     local bytes = 0
34     while true do
35         local packet = file:read(packet_size)
36         if not packet then break end
37         bytes = bytes + packet:len()
38         local index = 1
39         while true do
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
44             else
45                 break
46             end
47         end
48         coroutine.yield(true)
49     end
50     file:close()
51     sock:close()
52     irc._unregister_socket(sock, 'w')
53     return true
54 end
55 -- }}}
56
57 -- handle_connect {{{
58 --
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()
68     sock:settimeout(0.1)
69     ssock:close()
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)
74                          end))
75     return true
76 end
77 -- }}}
78
79 -- accept_file {{{
80 --
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)
86     local bytes = 0
87     while true do
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))
94         file:write(packet)
95         coroutine.yield(true)
96     end
97     file:close()
98     sock:close()
99     irc._unregister_socket(sock, 'r')
100     return true
101 end
102 -- }}}
103 -- }}}
104
105 -- internal functions {{{
106 -- _accept {{{
107 --
108 -- Accepts a file offer from a remote user. Called when the on_dcc callback
109 -- retuns true.
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))
118     sock:settimeout(0.1)
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)
123                          end))
124 end
125 -- }}}
126 -- }}}
127
128 -- public functions {{{
129 -- send {{{
130 ---
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
136 --                 above)
137 function send(nick, filename, port)
138     port = port or FIRST_PORT
139     local sock = base.assert(socket.tcp())
140     repeat
141         err, msg = sock:bind('*', port)
142         port = port + 1
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")
149     file:seek("set")
150     irc._register_socket(sock, 'r',
151                          coroutine.wrap(function(sock)
152                              return handle_connect(sock, file)
153                          end))
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})
158 end
159 -- }}}
160 -- }}}