1 |
1 |
2 -- IN-BAND BYTESTREAMS (XEP-0047) |
2 local lm = require 'lm' |
|
3 local ibb = require 'lm.ibb' |
3 |
4 |
4 -- TODO bidirectionality |
5 local mc_incoming_files = { } |
5 -- thus, on stream accept we can add our sid to incoming files structure, |
|
6 -- as if we received and accepted incoming request. |
|
7 -- message stanzas |
|
8 |
6 |
9 -- library |
7 ibb.handler ( |
|
8 function ( from, accept, reject ) |
|
9 local fid = #mc_incoming_files + 1 |
|
10 mc_incoming_files[fid] = { |
|
11 from = from, |
|
12 accept = |
|
13 function ( name ) |
|
14 mc_incoming_files[fid].name = name |
|
15 accept ( |
|
16 function ( data ) |
|
17 local h = io.open ( mc_incoming_files[fid].name, 'w' ) |
|
18 if not h then |
|
19 print ( 'Cannot open output file: ' .. mc_incoming_files[fid].name ) |
|
20 return |
|
21 end |
|
22 h:write ( data ) |
|
23 h:close () |
|
24 print ( 'Stream ' .. fid .. ' successfully saved to ' .. mc_incoming_files[fid].name ) |
|
25 mc_incoming_files[fid] = nil |
|
26 end, |
|
27 function ( mesg ) |
|
28 main.print_info ( from, 'Stream error: ' .. mesg ) |
|
29 mc_incoming_files[fid] = nil -- XXX |
|
30 end ) |
|
31 end, |
|
32 reject = |
|
33 function () |
|
34 reject () |
|
35 print ( 'Stream ' .. fid .. ' rejected' ) |
|
36 mc_incoming_files[fid] = nil |
|
37 end, |
|
38 } |
|
39 main.print_info ( from, from .. ' wants you to receive stream. Use /ibb [accept|reject] ' .. fid .. ' to process his request.' ) |
|
40 end ) |
10 |
41 |
11 local lm = require 'lm' |
42 local ibb_sid = 0 |
12 local iq = require 'iq' |
|
13 local base64 = require 'base64' |
|
14 |
43 |
15 -- |
44 main.command ( 'ibb', |
16 |
45 function ( args ) |
17 local F = { } |
46 local action = args[1] |
18 local M = { } |
47 if action == 'send' then |
19 M.__index = M |
48 local who |
20 local O = { |
49 if args.t then |
21 handler = |
50 who = args.t |
22 function ( accept, reject ) |
51 else |
23 reject () |
52 who = main.full_jid () |
24 end, |
53 end |
25 } |
54 local fname = args[2] |
26 |
55 local conn = lm.connection.bless ( main.connection () ) |
27 function F.new ( conn, to, bs, sid ) |
56 local sid = ibb_sid |
28 local obj = { |
57 ibb_sid = ibb_sid + 1 |
29 conn = conn, |
58 local stream = ibb.new ( conn, who, 4096, sid ) |
30 to = to, |
59 stream:open ( |
31 sbs = bs, |
|
32 bs = math.floor ( bs * 3 / 4 ), |
|
33 sid = sid, |
|
34 seq = 0, |
|
35 } |
|
36 setmetatable ( obj, M ) |
|
37 return obj |
|
38 end |
|
39 |
|
40 function M.open ( obj, success, fail ) |
|
41 iq.send ( obj.conn, obj.to, 'set', |
|
42 { |
|
43 open = { sid = obj.sid, ['block-size'] = obj.sbs, xmlns = 'http://jabber.org/protocol/ibb' } |
|
44 }, success, fail ) |
|
45 end |
|
46 |
|
47 function M.send ( obj, data, success, fail ) |
|
48 if data and data ~= '' then |
|
49 local start = 0 |
|
50 while start < data:len () do |
|
51 local chunk = base64.encode ( data:sub ( start, obj.bs ) ) |
|
52 local cseq = obj.seq -- local instance |
|
53 iq.send ( obj.conn, obj.to, 'set', |
|
54 { |
|
55 data = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq, |
|
56 chunk, |
|
57 }, |
|
58 }, |
|
59 function () |
60 function () |
60 success ( cseq ) |
61 main.print_info ( who, 'Stream accepted' ) |
|
62 local noerr = true |
|
63 local h = io.open ( fname, 'r' ) |
|
64 if not h then |
|
65 print ( 'Cannot open file ' .. fname ) |
|
66 return |
|
67 end |
|
68 local data = h:read ( '*a' ) -- In fact, it is better to read it in chunks :/ |
|
69 h:close () |
|
70 local fail = |
|
71 function ( mesg ) |
|
72 noerr = false |
|
73 main.print_info ( who, 'Stream error: ' .. mesg ) |
|
74 end |
|
75 stream:send ( data, |
|
76 function ( seq ) |
|
77 main.print_info ( who, 'Delivery notification of chunk #' .. seq ) |
|
78 end, fail ) |
|
79 if noerr then |
|
80 stream:close ( |
|
81 function () |
|
82 main.print_info ( who, 'Stream finalizing notification' ) |
|
83 end, fail ) |
|
84 end |
|
85 if noerr then |
|
86 main.print_info ( who, 'Stream sent' ) |
|
87 else |
|
88 main.print_info ( who, 'Stream error occured' ) |
|
89 end |
61 end, |
90 end, |
62 function ( mesg ) |
91 function ( mesg ) |
63 noerr = false |
92 main.print_info ( who, 'Stream initiation error: ' .. mesg ) |
64 fail ( mesg ) |
|
65 end ) |
93 end ) |
66 start = start + obj.bs |
94 elseif action == 'accept' then |
67 obj.seq = obj.seq + 1 |
95 local id = tonumber(args[2]) |
|
96 if mc_incoming_files[id] then |
|
97 mc_incoming_files[id].accept ( args[3] ) |
|
98 end |
|
99 elseif action == 'reject' then |
|
100 local id = tonumber(args[2]) |
|
101 if mc_incoming_files[id] then |
|
102 mc_incoming_files[id].reject () |
|
103 end |
|
104 else |
|
105 local text = '' |
|
106 for sid, data in pairs ( mc_incoming_files ) do |
|
107 text = text .. '\n' .. sid .. ': ' .. data.from .. ' --> ' .. ( data.name or '?' ) |
|
108 end |
|
109 if text ~= '' then |
|
110 print ( 'List of incoming streams:' .. text ) |
|
111 else |
|
112 print ( 'No streams' ) |
|
113 end |
68 end |
114 end |
69 end |
115 end, true, { "send", "accept", "reject" } ) |
70 end |
|
71 |
116 |
72 function M.close ( obj, success, fail ) |
|
73 iq.send ( obj.conn, obj.to, 'set', |
|
74 { |
|
75 close = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb' }, |
|
76 }, success, fail ) |
|
77 end |
|
78 |
117 |
79 function F.handler ( handler ) |
118 commands_help['ibb'] = "[[-t target_jid] send filename | accept sid filename | reject sid]\n\nRequests, accepts or rejects sending file via in-band bytestream." |
80 O.handler = handler |
|
81 end |
|
82 |
119 |
83 local ibb_files = {} |
120 local ibb_handler = lm.message_handler.new ( ibb.iq_handler ) |
|
121 local ibb_handler_registered = false |
84 |
122 |
85 function F.iq_handler ( conn, mess ) |
123 hooks_d['hook-post-connect'].ibb = |
86 local mtype, smtype = mess:type () |
124 function ( args ) |
87 if smtype ~= 'set' then |
125 lm.connection.bless( main.connection () ):handler ( ibb_handler, 'iq', 'normal' ) |
88 return false |
126 ibb_handler_registered = true |
|
127 hooks_d['hook-post-connect'].ibb = nil |
|
128 hooks_d['hook-quit'].ibb = |
|
129 function ( args ) |
|
130 if ibb_handler_registered then |
|
131 lm.connection.bless( main.connection () ):handler ( ibb_handler, 'iq' ) |
|
132 end |
|
133 end |
89 end |
134 end |
90 |
135 |
91 local child = mess:child () |
136 main.add_feature ( 'http://jabber.org/protocol/ibb' ) |
92 if not child or child:attribute ( 'xmlns' ) ~= 'http://jabber.org/protocol/ibb' then |
|
93 return false |
|
94 end |
|
95 |
|
96 local id = mess:attribute ( 'id' ) |
|
97 local from = mess:attribute ( 'from' ) |
|
98 local action = child:name () |
|
99 local sid = child:attribute ( 'sid' ) |
|
100 |
|
101 if action == 'open' then |
|
102 if not ibb_files[sid] then |
|
103 O.handler ( from, |
|
104 function ( success, fail ) |
|
105 ibb_files[sid] = { from = from, success = success, fail = fail } |
|
106 conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } ) |
|
107 end, |
|
108 function () |
|
109 conn:send ( |
|
110 lm.message.create { to = from, mtype = 'iq-error', id = id, |
|
111 error = { code = '405', type = 'cancel', |
|
112 ['not-allowed'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }, |
|
113 }, |
|
114 } ) |
|
115 end ) |
|
116 else |
|
117 conn:send ( |
|
118 lm.message.create { to = from, mtype = 'iq-error', id = id, |
|
119 error = { code = '409', type = 'cancel', |
|
120 conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }, |
|
121 }, |
|
122 } ) |
|
123 end |
|
124 elseif action == 'data' then |
|
125 local seq = child:attribute ( 'seq' ) |
|
126 if ibb_files[sid] and from == ibb_files[sid].from and not ibb_files[sid][tonumber(seq)+1] then |
|
127 local data = child:value () |
|
128 conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } ) |
|
129 ibb_files[sid][tonumber(seq)+1] = data |
|
130 -- XXX ibb_files[sid].success ( seq ) |
|
131 else |
|
132 if ibb_files[sid] then |
|
133 ibb_files[sid].fail ( 'conflict' ) |
|
134 ibb_files[sid] = nil -- invalidate session |
|
135 conn:send ( |
|
136 lm.message.create { to = from, mtype = 'iq-error', id = id, |
|
137 error = { code = '409', type = 'cancel', |
|
138 conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }, |
|
139 }, |
|
140 } ) |
|
141 else |
|
142 conn:send ( |
|
143 lm.message.create { to = from, mtype = 'iq-error', id = id, |
|
144 error = { code = '404', type = 'cancel', -- XXX: check |
|
145 ['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }, |
|
146 }, |
|
147 } ) |
|
148 end |
|
149 end |
|
150 elseif action == 'close' then |
|
151 if ibb_files[sid] and from == ibb_files[sid].from then |
|
152 local data = '' |
|
153 for seq, chunk in ipairs ( ibb_files[sid] ) do |
|
154 data = data .. chunk |
|
155 end |
|
156 local decoded = base64.decode ( data ) |
|
157 ibb_files[sid].success ( decoded ) |
|
158 conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } ) |
|
159 ibb_files[sid] = nil |
|
160 else |
|
161 if ibb_files[sid] then |
|
162 ibb_files[sid].fail ( 'conflict' ) |
|
163 ibb_files[sid] = nil -- invalidate session |
|
164 conn:send ( |
|
165 lm.message.create { to = from, mtype = 'iq-error', id = id, |
|
166 error = { code = '409', type = 'cancel', |
|
167 conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }, |
|
168 }, |
|
169 } ) |
|
170 else |
|
171 conn:send ( |
|
172 lm.message.create { to = from, mtype = 'iq-error', id = id, |
|
173 error = { code = '404', type = 'cancel', -- XXX: check |
|
174 ['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }, |
|
175 }, |
|
176 } ) |
|
177 end |
|
178 end |
|
179 else |
|
180 return false |
|
181 end |
|
182 |
|
183 return true |
|
184 end |
|
185 |
|
186 return F |
|
187 |
137 |
188 -- vim: se ts=4: -- |
138 -- vim: se ts=4: -- |