comparison src/plugins/plugin_xep_0231.py @ 2522:95c31756944c

component file sharing, plugin XEP-0231: thumbnail are now returned by component using Bits of Binary: thumbnails are saved in common cache, and sent blindly on request. There is no access check for thumbnails yet, which is a security/privacy issue, but mitigated by the fact that content id must be known to retrieve the file. Proper access check is planed.
author Goffi <goffi@goffi.org>
date Wed, 14 Mar 2018 08:14:32 +0100
parents 20a5e7db0609
children a201194fc461
comparison
equal deleted inserted replaced
2521:327bbbe793ce 2522:95c31756944c
26 from wokkel import disco, iwokkel 26 from wokkel import disco, iwokkel
27 from zope.interface import implements 27 from zope.interface import implements
28 from twisted.python import failure 28 from twisted.python import failure
29 from twisted.words.protocols.jabber import xmlstream 29 from twisted.words.protocols.jabber import xmlstream
30 from twisted.words.protocols.jabber import jid 30 from twisted.words.protocols.jabber import jid
31 from twisted.words.protocols.jabber import error as jabber_error
32 from twisted.internet import defer
31 from functools import partial 33 from functools import partial
32 import base64 34 import base64
35 import time
33 36
34 37
35 PLUGIN_INFO = { 38 PLUGIN_INFO = {
36 C.PI_NAME: "Bits of Binary", 39 C.PI_NAME: "Bits of Binary",
37 C.PI_IMPORT_NAME: "XEP-0231", 40 C.PI_IMPORT_NAME: "XEP-0231",
38 C.PI_TYPE: "XEP", 41 C.PI_TYPE: "XEP",
42 C.PI_MODES: C.PLUG_MODE_BOTH,
39 C.PI_PROTOCOLS: ["XEP-0231"], 43 C.PI_PROTOCOLS: ["XEP-0231"],
40 C.PI_DEPENDENCIES: ["XEP-0071"],
41 C.PI_MAIN: "XEP_0231", 44 C.PI_MAIN: "XEP_0231",
42 C.PI_HANDLER: "yes", 45 C.PI_HANDLER: "yes",
43 C.PI_DESCRIPTION: _("""Implementation of bits of binary (used for small images/files)""") 46 C.PI_DESCRIPTION: _("""Implementation of bits of binary (used for small images/files)""")
44 } 47 }
45 48
46 NS_BOB = u'urn:xmpp:bob' 49 NS_BOB = u'urn:xmpp:bob'
50 IQ_BOB_REQUEST = C.IQ_GET + '/data[@xmlns="' + NS_BOB + '"]'
47 51
48 52
49 class XEP_0231(object): 53 class XEP_0231(object):
50 54
51 def __init__(self, host): 55 def __init__(self, host):
52 log.info(_(u"plugin Bits of Binary initialization")) 56 log.info(_(u"plugin Bits of Binary initialization"))
53 self.host = host 57 self.host = host
58 host.registerNamespace('bob', NS_BOB)
54 host.trigger.add("xhtml_post_treat", self.XHTMLTrigger) 59 host.trigger.add("xhtml_post_treat", self.XHTMLTrigger)
60 host.bridge.addMethod("bobGetFile", ".plugin",
61 in_sign='sss', out_sign='s',
62 method=self._getFile,
63 async=True)
55 64
56 def dumpData(self, cache, data_elt, cid): 65 def dumpData(self, cache, data_elt, cid):
57 """save file encoded in data_elt to cache 66 """save file encoded in data_elt to cache
58 67
59 @param cache(memory.cache.Cache): cache to use to store the data 68 @param cache(memory.cache.Cache): cache to use to store the data
81 f.write(base64.b64decode(str(data_elt))) 90 f.write(base64.b64decode(str(data_elt)))
82 91
83 return file_path 92 return file_path
84 93
85 def getHandler(self, client): 94 def getHandler(self, client):
86 return XEP_0231_handler() 95 return XEP_0231_handler(self)
87 96
88 def _requestCb(self, iq_elt, cache, cid): 97 def _requestCb(self, iq_elt, cache, cid):
89 for data_elt in iq_elt.elements(NS_BOB, u'data'): 98 for data_elt in iq_elt.elements(NS_BOB, u'data'):
90 if data_elt.getAttribute('cid') == cid: 99 if data_elt.getAttribute('cid') == cid:
91 file_path = self.dumpData(cache, data_elt, cid) 100 file_path = self.dumpData(cache, data_elt, cid)
107 116
108 @param to_jid(jid.JID): jid to request the data to 117 @param to_jid(jid.JID): jid to request the data to
109 @param cid(unicode): content id 118 @param cid(unicode): content id
110 @param cache(memory.cache.Cache, None): cache to use 119 @param cache(memory.cache.Cache, None): cache to use
111 client.cache will be used if None 120 client.cache will be used if None
121 @return D(unicode): path to file with data
112 """ 122 """
113 if cache is None: 123 if cache is None:
114 cache = client.cache 124 cache = client.cache
115 iq_elt = client.IQ('get') 125 iq_elt = client.IQ('get')
116 iq_elt['to'] = to_jid.full() 126 iq_elt['to'] = to_jid.full()
117 data_elt = iq_elt.addElement((NS_BOB, 'data')) 127 data_elt = iq_elt.addElement((NS_BOB, 'data'))
118 data_elt['cid'] = cid 128 data_elt['cid'] = cid
119 d = iq_elt.send() 129 d = iq_elt.send()
120 d.addCallback(self._requestCb, cache, cid) 130 d.addCallback(self._requestCb, cache, cid)
121 d.addErrback(self._requestEb) 131 d.addErrback(self._requestEb)
132 return d
122 133
123 def _setImgEltSrc(self, path, img_elt): 134 def _setImgEltSrc(self, path, img_elt):
124 img_elt[u'src'] = u'file://{}'.format(path) 135 img_elt[u'src'] = u'file://{}'.format(path)
125 136
126 def XHTMLTrigger(self, client, message_elt, body_elt, lang, treat_d): 137 def XHTMLTrigger(self, client, message_elt, body_elt, lang, treat_d):
128 source = img_elt.getAttribute(u'src','') 139 source = img_elt.getAttribute(u'src','')
129 if source.startswith(u'cid:'): 140 if source.startswith(u'cid:'):
130 cid = source[4:] 141 cid = source[4:]
131 file_path = client.cache.getFilePath(cid) 142 file_path = client.cache.getFilePath(cid)
132 if file_path is not None: 143 if file_path is not None:
133 # image is in cache, we change change the url 144 # image is in cache, we change the url
134 img_elt[u'src'] = u'file://{}'.format(file_path) 145 img_elt[u'src'] = u'file://{}'.format(file_path)
135 continue 146 continue
136 else: 147 else:
137 # image is not in cache, is it given locally? 148 # image is not in cache, is it given locally?
138 for data_elt in message_elt.elements(NS_BOB, u'data'): 149 for data_elt in message_elt.elements(NS_BOB, u'data'):
145 # so we use the deferred 156 # so we use the deferred
146 d = self.requestData(client, jid.JID(message_elt['from']), cid) 157 d = self.requestData(client, jid.JID(message_elt['from']), cid)
147 d.addCallback(partial(self._setImgEltSrc, img_elt=img_elt)) 158 d.addCallback(partial(self._setImgEltSrc, img_elt=img_elt))
148 treat_d.addCallback(lambda dummy: d) 159 treat_d.addCallback(lambda dummy: d)
149 160
161 def onComponentRequest(self, iq_elt, client):
162 """cache data is retrieve from common cache for components"""
163 # FIXME: this is a security/privacy issue as no access check is done
164 # but this is mitigated by the fact that the cid must be known.
165 # An access check should be implemented though.
166
167 iq_elt.handled = True
168 data_elt = next(iq_elt.elements(NS_BOB, 'data'))
169 try:
170 cid = data_elt[u'cid']
171 except KeyError:
172 error_elt = jabber_error.StanzaError('not-acceptable').toResponse(iq_elt)
173 client.send(error_elt)
174 return
175
176 metadata = self.host.common_cache.getMetadata(cid)
177 if metadata is None:
178 error_elt = jabber_error.StanzaError('item-not-found').toResponse(iq_elt)
179 client.send(error_elt)
180 return
181
182 with open(metadata['path']) as f:
183 data = f.read()
184
185 result_elt = xmlstream.toResponse(iq_elt, 'result')
186 data_elt = result_elt.addElement((NS_BOB, 'data'), content = data.encode('base64'))
187 data_elt[u'cid'] = cid
188 data_elt[u'type'] = metadata[u'mime_type']
189 data_elt[u'max-age'] = unicode(int(max(0, metadata['eol'] - time.time())))
190 client.send(result_elt)
191
192 def _getFile(self, peer_jid_s, cid, profile):
193 peer_jid = jid.JID(peer_jid_s)
194 assert cid
195 client = self.host.getClient(profile)
196 return self.getFile(client, peer_jid, cid)
197
198 def getFile(self, client, peer_jid, cid, parent_elt=None):
199 """Retrieve a file from it's content-id
200
201 @param peer_jid(jid.JID): jid of the entity offering the data
202 @param cid(unicode): content-id of file data
203 @param parent_elt(domish.Element, None): if file is not in cache,
204 data will be looked after in children of this elements.
205 None to ignore
206 @return D(unicode): path to cached data
207 """
208 file_path = client.cache.getFilePath(cid)
209 if file_path is not None:
210 # file is in cache
211 return defer.succeed(file_path)
212 else:
213 # file not in cache, is it given locally?
214 if parent_elt is not None:
215 for data_elt in parent_elt.elements(NS_BOB, u'data'):
216 if data_elt.getAttribute('cid') == cid:
217 return defer.succeed(self.dumpData(client.cache, data_elt, cid))
218
219 # cid not found locally, we need to request it
220 # so we use the deferred
221 return self.requestData(client, peer_jid, cid)
222
150 223
151 class XEP_0231_handler(xmlstream.XMPPHandler): 224 class XEP_0231_handler(xmlstream.XMPPHandler):
152 implements(iwokkel.IDisco) 225 implements(iwokkel.IDisco)
153 226
227 def __init__(self, plugin_parent):
228 self.plugin_parent = plugin_parent
229 self.host = plugin_parent.host
230
231 def connectionInitialized(self):
232 if self.parent.is_component:
233 self.xmlstream.addObserver(IQ_BOB_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent)
234
154 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 235 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
155 return [disco.DiscoFeature(NS_BOB)] 236 return [disco.DiscoFeature(NS_BOB)]
156 237
157 def getDiscoItems(self, requestor, target, nodeIdentifier=''): 238 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
158 return [] 239 return []