| 65 | | def node_to_dict(self, node): |
| 66 | | dict = {} |
| 67 | | |
| 68 | | for info in node.getChildren(): |
| 69 | | name = info.getName() |
| 70 | | if name in ('ADR', 'TEL', 'EMAIL'): # we can have several |
| 71 | | if not dict.has_key(name): |
| 72 | | dict[name] = [] |
| 73 | | entry = {} |
| 74 | | for c in info.getChildren(): |
| 75 | | entry[c.getName()] = c.getData() |
| 76 | | dict[name].append(entry) |
| 77 | | elif info.getChildren() == []: |
| 78 | | dict[name] = info.getData() |
| 79 | | else: |
| 80 | | dict[name] = {} |
| 81 | | for c in info.getChildren(): |
| 82 | | dict[name][c.getName()] = c.getData() |
| 83 | | |
| 84 | | return dict |
| 85 | | |
| 86 | | def save_vcard_to_hd(self, full_jid, card): |
| 87 | | jid, nick = gajim.get_room_and_nick_from_fjid(full_jid) |
| 88 | | puny_jid = helpers.sanitize_filename(jid) |
| 89 | | path = os.path.join(gajim.VCARD_PATH, puny_jid) |
| 90 | | if jid in self.room_jids or os.path.isdir(path): |
| 91 | | # remove room_jid file if needed |
| 92 | | if os.path.isfile(path): |
| 93 | | os.remove(path) |
| 94 | | # create folder if needed |
| 95 | | if not os.path.isdir(path): |
| 96 | | os.mkdir(path, 0700) |
| 97 | | puny_nick = helpers.sanitize_filename(nick) |
| 98 | | path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) |
| 99 | | else: |
| 100 | | path_to_file = path |
| 101 | | fil = open(path_to_file, 'w') |
| 102 | | fil.write(str(card)) |
| 103 | | fil.close() |
| 104 | | |
| 105 | | def get_cached_vcard(self, fjid, is_fake_jid = False): |
| 106 | | '''return the vcard as a dict |
| 107 | | return {} if vcard was too old |
| 108 | | return None if we don't have cached vcard''' |
| 109 | | jid, nick = gajim.get_room_and_nick_from_fjid(fjid) |
| 110 | | puny_jid = helpers.sanitize_filename(jid) |
| 111 | | if is_fake_jid: |
| 112 | | puny_nick = helpers.sanitize_filename(nick) |
| 113 | | path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) |
| 114 | | else: |
| 115 | | path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid) |
| 116 | | if not os.path.isfile(path_to_file): |
| 117 | | return None |
| 118 | | # We have the vcard cached |
| 119 | | f = open(path_to_file) |
| 120 | | c = f.read() |
| 121 | | f.close() |
| 122 | | card = common.xmpp.Node(node = c) |
| 123 | | vcard = self.node_to_dict(card) |
| 124 | | if vcard.has_key('PHOTO'): |
| 125 | | if not isinstance(vcard['PHOTO'], dict): |
| 126 | | del vcard['PHOTO'] |
| 127 | | elif vcard['PHOTO'].has_key('SHA'): |
| 128 | | cached_sha = vcard['PHOTO']['SHA'] |
| 129 | | if self.vcard_shas.has_key(jid) and self.vcard_shas[jid] != \ |
| 130 | | cached_sha: |
| 131 | | # user change his vcard so don't use the cached one |
| 132 | | return {} |
| 133 | | vcard['jid'] = jid |
| 134 | | vcard['resource'] = gajim.get_resource_from_jid(fjid) |
| 135 | | return vcard |
| 136 | | |
| 143 | | class ConnectionBytestream: |
| 144 | | def __init__(self): |
| 145 | | self.files_props = {} |
| 146 | | |
| 147 | | def is_transfer_stopped(self, file_props): |
| 148 | | if file_props.has_key('error') and file_props['error'] != 0: |
| 149 | | return True |
| 150 | | if file_props.has_key('completed') and file_props['completed']: |
| 151 | | return True |
| 152 | | if file_props.has_key('connected') and file_props['connected'] == False: |
| 153 | | return True |
| 154 | | if not file_props.has_key('stopped') or not file_props['stopped']: |
| 155 | | return False |
| 156 | | return True |
| 157 | | |
| 158 | | def send_success_connect_reply(self, streamhost): |
| 159 | | ''' send reply to the initiator of FT that we |
| 160 | | made a connection |
| 161 | | ''' |
| 162 | | if streamhost is None: |
| 163 | | return None |
| 164 | | iq = common.xmpp.Iq(to = streamhost['initiator'], typ = 'result', |
| 165 | | frm = streamhost['target']) |
| 166 | | iq.setAttr('id', streamhost['id']) |
| 167 | | query = iq.setTag('query') |
| 168 | | query.setNamespace(common.xmpp.NS_BYTESTREAM) |
| 169 | | stream_tag = query.setTag('streamhost-used') |
| 170 | | stream_tag.setAttr('jid', streamhost['jid']) |
| 171 | | self.connection.send(iq) |
| 172 | | |
| 173 | | def remove_transfers_for_contact(self, contact): |
| 174 | | ''' stop all active transfer for contact ''' |
| 175 | | for file_props in self.files_props.values(): |
| 176 | | if self.is_transfer_stopped(file_props): |
| 177 | | continue |
| 178 | | receiver_jid = unicode(file_props['receiver']).split('/')[0] |
| 179 | | if contact.jid == receiver_jid: |
| 180 | | file_props['error'] = -5 |
| 181 | | self.remove_transfer(file_props) |
| 182 | | self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, '')) |
| 183 | | sender_jid = unicode(file_props['sender']) |
| 184 | | if contact.jid == sender_jid: |
| 185 | | file_props['error'] = -3 |
| 186 | | self.remove_transfer(file_props) |
| 187 | | |
| 188 | | def remove_all_transfers(self): |
| 189 | | ''' stops and removes all active connections from the socks5 pool ''' |
| 190 | | for file_props in self.files_props.values(): |
| 191 | | self.remove_transfer(file_props, remove_from_list = False) |
| 192 | | del(self.files_props) |
| 193 | | self.files_props = {} |
| 194 | | |
| 195 | | def remove_transfer(self, file_props, remove_from_list = True): |
| 196 | | if file_props is None: |
| 197 | | return |
| 198 | | self.disconnect_transfer(file_props) |
| 199 | | sid = file_props['sid'] |
| 200 | | gajim.socks5queue.remove_file_props(self.name, sid) |
| 201 | | |
| 202 | | if remove_from_list: |
| 203 | | if self.files_props.has_key('sid'): |
| 204 | | del(self.files_props['sid']) |
| 205 | | |
| 206 | | def disconnect_transfer(self, file_props): |
| 207 | | if file_props is None: |
| 208 | | return |
| 209 | | if file_props.has_key('hash'): |
| 210 | | gajim.socks5queue.remove_sender(file_props['hash']) |
| 211 | | |
| 212 | | if file_props.has_key('streamhosts'): |
| 213 | | for host in file_props['streamhosts']: |
| 214 | | if host.has_key('idx') and host['idx'] > 0: |
| 215 | | gajim.socks5queue.remove_receiver(host['idx']) |
| 216 | | gajim.socks5queue.remove_sender(host['idx']) |
| 217 | | |
| | 68 | class ConnectionBytestream(connection_handlers.ConnectionBytestream): |
| 277 | | def send_file_rejection(self, file_props): |
| 278 | | ''' informs sender that we refuse to download the file ''' |
| 279 | | # user response to ConfirmationDialog may come after we've disconneted |
| 280 | | if not self.connection or self.connected < 2: |
| 281 | | return |
| 282 | | iq = common.xmpp.Protocol(name = 'iq', |
| 283 | | to = unicode(file_props['sender']), typ = 'error') |
| 284 | | iq.setAttr('id', file_props['request-id']) |
| 285 | | err = common.xmpp.ErrorNode(code = '403', typ = 'cancel', name = |
| 286 | | 'forbidden', text = 'Offer Declined') |
| 287 | | iq.addChild(node=err) |
| 288 | | self.connection.send(iq) |
| 289 | | |
| 290 | | def send_file_approval(self, file_props): |
| 291 | | ''' send iq, confirming that we want to download the file ''' |
| 292 | | # user response to ConfirmationDialog may come after we've disconneted |
| 293 | | if not self.connection or self.connected < 2: |
| 294 | | return |
| 295 | | iq = common.xmpp.Protocol(name = 'iq', |
| 296 | | to = unicode(file_props['sender']), typ = 'result') |
| 297 | | iq.setAttr('id', file_props['request-id']) |
| 298 | | si = iq.setTag('si') |
| 299 | | si.setNamespace(common.xmpp.NS_SI) |
| 300 | | if file_props.has_key('offset') and file_props['offset']: |
| 301 | | file_tag = si.setTag('file') |
| 302 | | file_tag.setNamespace(common.xmpp.NS_FILE) |
| 303 | | range_tag = file_tag.setTag('range') |
| 304 | | range_tag.setAttr('offset', file_props['offset']) |
| 305 | | feature = si.setTag('feature') |
| 306 | | feature.setNamespace(common.xmpp.NS_FEATURE) |
| 307 | | _feature = common.xmpp.DataForm(typ='submit') |
| 308 | | feature.addChild(node=_feature) |
| 309 | | field = _feature.setField('stream-method') |
| 310 | | field.delAttr('type') |
| 311 | | field.setValue(common.xmpp.NS_BYTESTREAM) |
| 312 | | self.connection.send(iq) |
| 313 | | |
| 346 | | |
| 347 | | def _result_socks5_sid(self, sid, hash_id): |
| 348 | | ''' store the result of sha message from auth. ''' |
| 349 | | if not self.files_props.has_key(sid): |
| 350 | | return |
| 351 | | file_props = self.files_props[sid] |
| 352 | | file_props['hash'] = hash_id |
| 353 | | return |
| 354 | | |
| 355 | | def _connect_error(self, to, _id, sid, code = 404): |
| 356 | | ''' cb, when there is an error establishing BS connection, or |
| 357 | | when connection is rejected''' |
| 358 | | msg_dict = { |
| 359 | | 404: 'Could not connect to given hosts', |
| 360 | | 405: 'Cancel', |
| 361 | | 406: 'Not acceptable', |
| 362 | | } |
| 363 | | msg = msg_dict[code] |
| 364 | | iq = None |
| 365 | | iq = common.xmpp.Protocol(name = 'iq', to = to, |
| 366 | | typ = 'error') |
| 367 | | iq.setAttr('id', _id) |
| 368 | | err = iq.setTag('error') |
| 369 | | err.setAttr('code', unicode(code)) |
| 370 | | err.setData(msg) |
| 371 | | self.connection.send(iq) |
| 372 | | if code == 404: |
| 373 | | file_props = gajim.socks5queue.get_file_props(self.name, sid) |
| 374 | | if file_props is not None: |
| 375 | | self.disconnect_transfer(file_props) |
| 376 | | file_props['error'] = -3 |
| 377 | | self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg)) |
| 378 | | |
| 379 | | def _proxy_auth_ok(self, proxy): |
| 380 | | '''cb, called after authentication to proxy server ''' |
| 381 | | file_props = self.files_props[proxy['sid']] |
| 382 | | iq = common.xmpp.Protocol(name = 'iq', to = proxy['initiator'], |
| 383 | | typ = 'set') |
| 384 | | auth_id = "au_" + proxy['sid'] |
| 385 | | iq.setID(auth_id) |
| 386 | | query = iq.setTag('query') |
| 387 | | query.setNamespace(common.xmpp.NS_BYTESTREAM) |
| 388 | | query.setAttr('sid', proxy['sid']) |
| 389 | | activate = query.setTag('activate') |
| 390 | | activate.setData(file_props['proxy_receiver']) |
| 391 | | iq.setID(auth_id) |
| 392 | | self.connection.send(iq) |
| 393 | | |
| 394 | | # register xmpppy handlers for bytestream and FT stanzas |
| 395 | | def _bytestreamErrorCB(self, con, iq_obj): |
| 396 | | gajim.log.debug('_bytestreamErrorCB') |
| 397 | | id = unicode(iq_obj.getAttr('id')) |
| 398 | | frm = unicode(iq_obj.getFrom()) |
| 399 | | query = iq_obj.getTag('query') |
| 400 | | gajim.proxy65_manager.error_cb(frm, query) |
| 401 | | jid = unicode(iq_obj.getFrom()) |
| 402 | | id = id[3:] |
| 403 | | if not self.files_props.has_key(id): |
| 404 | | return |
| 405 | | file_props = self.files_props[id] |
| 406 | | file_props['error'] = -4 |
| 407 | | self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) |
| 408 | | raise common.xmpp.NodeProcessed |
| 409 | | |
| | 160 | |
| 630 | | # List of IDs we are waiting answers for {id: (type_of_request, data), } |
| 631 | | self.awaiting_answers = {} |
| 632 | | # List of IDs that will produce a timeout is answer doesn't arrive |
| 633 | | # {time_of_the_timeout: (id, message to send to gui), } |
| 634 | | self.awaiting_timeouts = {} |
| 635 | | # keep the jids we auto added (transports contacts) to not send the |
| 636 | | # SUBSCRIBED event to gui |
| 637 | | self.automatically_added = [] |
| 638 | | # keep track of sessions this connection has with other JIDs |
| 639 | | self.sessions = {} |
| | 381 | connection_handlers.ConnectionHandlersBase.__init__(self) |
| | 382 | |
| 702 | | delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None |
| 703 | | msg_id = None |
| 704 | | composing_xep = None |
| 705 | | xtags = msg.getTags('x') |
| 706 | | # chatstates - look for chatstate tags in a message if not delayed |
| 707 | | if not delayed: |
| 708 | | composing_xep = False |
| 709 | | children = msg.getChildren() |
| 710 | | for child in children: |
| 711 | | if child.getNamespace() == 'http://jabber.org/protocol/chatstates': |
| 712 | | chatstate = child.getName() |
| 713 | | composing_xep = 'XEP-0085' |
| 714 | | break |
| 715 | | # No JEP-0085 support, fallback to JEP-0022 |
| 716 | | if not chatstate: |
| 717 | | chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT) |
| 718 | | if chatstate_child: |
| 719 | | chatstate = 'active' |
| 720 | | composing_xep = 'XEP-0022' |
| 721 | | if not msgtxt and chatstate_child.getTag('composing'): |
| 722 | | chatstate = 'composing' |
| 723 | | # JEP-0172 User Nickname |
| 724 | | user_nick = msg.getTagData('nick') |
| 725 | | if not user_nick: |
| 726 | | user_nick = '' |
| 741 | | error_msg = msg.getError() |
| 742 | | if not error_msg: |
| 743 | | error_msg = msgtxt |
| 744 | | msgtxt = None |
| 745 | | if self.name not in no_log_for: |
| 746 | | gajim.logger.write('error', frm, error_msg, tim = tim, |
| 747 | | subject = subject) |
| 748 | | self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, |
| 749 | | tim)) |
| 750 | | elif mtype == 'chat': # it's type 'chat' |
| 751 | | if not msg.getTag('body') and chatstate is None: #no <body> |
| 752 | | return |
| 753 | | if msg.getTag('body') and self.name not in no_log_for and jid not in\ |
| 754 | | no_log_for and msgtxt: |
| 755 | | msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim, |
| 756 | | subject = subject) |
| 757 | | self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject, |
| 758 | | chatstate, msg_id, composing_xep, user_nick, msghtml, session, |
| 759 | | form_node)) |
| 760 | | elif mtype == 'normal': # it's single message |
| 761 | | if self.name not in no_log_for and jid not in no_log_for and msgtxt: |
| 762 | | gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim, |
| 763 | | subject = subject) |
| 764 | | if invite: |
| 765 | | self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal', |
| 766 | | subject, chatstate, msg_id, composing_xep, user_nick, msghtml, |
| 767 | | session, form_node)) |
| | 459 | self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject) |
| | 460 | else: |
| | 461 | # XXX this shouldn't be hardcoded |
| | 462 | if isinstance(session, ChatControlSession): |
| | 463 | session.received(frm, msgtxt, tim, encrypted, subject, msg) |
| | 464 | else: |
| | 465 | session.received(msg) |
| 769 | | |
| 770 | | def _FeatureNegCB(self, con, stanza, session): |
| 771 | | gajim.log.debug('FeatureNegCB') |
| 772 | | feature = stanza.getTag(name='feature', namespace=common.xmpp.NS_FEATURE) |
| 773 | | form = common.xmpp.DataForm(node=feature.getTag('x')) |
| 774 | | |
| 775 | | if form['FORM_TYPE'] == 'urn:xmpp:ssn': |
| 776 | | self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form)) |
| 777 | | else: |
| 778 | | reply = stanza.buildReply() |
| 779 | | reply.setType('error') |
| 780 | | |
| 781 | | reply.addChild(feature) |
| 782 | | reply.addChild(node=xmpp.ErrorNode('service-unavailable', typ='cancel')) |
| 783 | | |
| 784 | | con.send(reply) |
| 785 | | |
| 786 | | raise common.xmpp.NodeProcessed |
| 787 | | |
| 788 | | def _InitE2ECB(self, con, stanza, session): |
| 789 | | gajim.log.debug('InitE2ECB') |
| 790 | | init = stanza.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT) |
| 791 | | form = common.xmpp.DataForm(node=init.getTag('x')) |
| 792 | | |
| 793 | | self.dispatch('SESSION_NEG', (stanza.getFrom |