Forked from
gajim / gajim-plugins
1544 commits behind, 519 commits ahead of the upstream repository.
-
Nikolay Yakimov authored
gotr expects fjid to be str, but it can be nbxmpp.JID. Converting to str shouldn't hurt, although could as well call fjid.getStripped() instead of gajim.get_jid_without_resource. Traceback (most recent call last): File "/usr/lib64/python2.7/site-packages/gajim/common/ged.py", line 93, in raise_event if handler(*args, **kwargs): File "/home/livid/.local/share/gajim/plugins/gotr/otrmodule.py", line 564, in handle_incoming_msg appdata={'thread':event.session.thread_id if event.session else None}) File "/home/livid/.local/share/gajim/plugins/gotr/potr/context.py", line 210, in receiveMessage self.crypto.handleAKE(message, appdata=appdata) File "/home/livid/.local/share/gajim/plugins/gotr/potr/crypt.py", line 282, in handleAKE outMsg = self.ake.handleRevealSig(inMsg) File "/home/livid/.local/share/gajim/plugins/gotr/potr/crypt.py", line 420, in handleRevealSig self.onSuccess(self) File "/home/livid/.local/share/gajim/plugins/gotr/potr/crypt.py", line 316, in goEncrypted self.ctx._wentEncrypted() File "/home/livid/.local/share/gajim/plugins/gotr/potr/context.py", line 313, in _wentEncrypted self.setState(STATE_ENCRYPTED) File "/home/livid/.local/share/gajim/plugins/gotr/otrmodule.py", line 175, in setState OtrPlugin.update_otr(self.peer, self.user.accountname) File "/home/livid/.local/share/gajim/plugins/gotr/otrmodule.py", line 521, in update_otr ctrl = cls.get_control(user, acc) File "/home/livid/.local/share/gajim/plugins/gotr/otrmodule.py", line 535, in get_control gajim.get_jid_without_resource(fjid), account) File "/usr/lib64/python2.7/site-packages/gajim/common/gajim.py", line 297, in get_jid_without_resource return jid.split('/')[0] AttributeError: JID instance has no attribute 'split'
Nikolay Yakimov authoredgotr expects fjid to be str, but it can be nbxmpp.JID. Converting to str shouldn't hurt, although could as well call fjid.getStripped() instead of gajim.get_jid_without_resource. Traceback (most recent call last): File "/usr/lib64/python2.7/site-packages/gajim/common/ged.py", line 93, in raise_event if handler(*args, **kwargs): File "/home/livid/.local/share/gajim/plugins/gotr/otrmodule.py", line 564, in handle_incoming_msg appdata={'thread':event.session.thread_id if event.session else None}) File "/home/livid/.local/share/gajim/plugins/gotr/potr/context.py", line 210, in receiveMessage self.crypto.handleAKE(message, appdata=appdata) File "/home/livid/.local/share/gajim/plugins/gotr/potr/crypt.py", line 282, in handleAKE outMsg = self.ake.handleRevealSig(inMsg) File "/home/livid/.local/share/gajim/plugins/gotr/potr/crypt.py", line 420, in handleRevealSig self.onSuccess(self) File "/home/livid/.local/share/gajim/plugins/gotr/potr/crypt.py", line 316, in goEncrypted self.ctx._wentEncrypted() File "/home/livid/.local/share/gajim/plugins/gotr/potr/context.py", line 313, in _wentEncrypted self.setState(STATE_ENCRYPTED) File "/home/livid/.local/share/gajim/plugins/gotr/otrmodule.py", line 175, in setState OtrPlugin.update_otr(self.peer, self.user.accountname) File "/home/livid/.local/share/gajim/plugins/gotr/otrmodule.py", line 521, in update_otr ctrl = cls.get_control(user, acc) File "/home/livid/.local/share/gajim/plugins/gotr/otrmodule.py", line 535, in get_control gajim.get_jid_without_resource(fjid), account) File "/usr/lib64/python2.7/site-packages/gajim/common/gajim.py", line 297, in get_jid_without_resource return jid.split('/')[0] AttributeError: JID instance has no attribute 'split'
otrmodule.py 28.03 KiB
#!/usr/bin/env python
# -*- coding: utf-8 -*-
## otrmodule.py
##
## Copyright 2008-2012 Kjell Braden <afflux@pentabarf.de>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Off-The-Record encryption plugin.
:author: Kjell self.Braden <kb.otr@pentabarf.de>
:since: 2008
:copyright: Copyright 2008-2012 Kjell Braden <afflux@pentabarf.de>
:license: GPL
'''
MINVERSION = (1,0,0,'beta5')
MINCRYPTOVERSION = (2,1,0,'final',0)
IGNORE = True
PASS = False
DEFAULTFLAGS = {
'ALLOW_V1':False,
'ALLOW_V2':True,
'REQUIRE_ENCRYPTION':False,
'SEND_TAG':True,
'WHITESPACE_START_AKE':True,
'ERROR_START_AKE':True,
}
MMS = 1024
PROTOCOL = 'xmpp'
MINVERSION_OUTGOING_MSG_STAZA = "0.16.4"
enc_tip = 'A private chat session <i>is established</i> to this contact ' \
'with this fingerprint'
unused_tip = 'A private chat session is established to this contact using ' \
'<i>another</i> fingerprint'
ended_tip = 'The private chat session to this contact has <i>ended</i>'
inactive_tip = 'Communication to this contact is currently ' \
'<i>unencrypted</i>'
msg_not_send = _('Your message was not send. Either end '
'your private conversation, or restart it')
import logging
import nbxmpp
import os
import pickle
import time
import sys
from pprint import pformat
from distutils.version import LooseVersion
from common import gajim
from common import ged
from common.connection_handlers_events import MessageOutgoingEvent
from plugins import GajimPlugin
from message_control import TYPE_CHAT, MessageControl
from plugins.helpers import log_calls, log
from plugins.plugin import GajimPluginException
import ui
sys.path.insert(0, os.path.dirname(ui.__file__))
from HTMLParser import HTMLParser
from htmlentitydefs import name2codepoint
name2codepoint['apos'] = 0x0027
HAS_CRYPTO = True
try:
import Crypto
if not hasattr(Crypto, 'version_info') \
or Crypto.version_info < MINCRYPTOVERSION:
raise ImportError('PyCrypto not found or too old')
except ImportError:
HAS_CRYPTO = False
HAS_POTR = True
try:
import potr
import potr.crypt
import potr.context
if not hasattr(potr, 'VERSION') or potr.VERSION < MINVERSION:
raise ImportError('old / unsupported python-otr version')
potrrootlog = logging.getLogger('potr')
potrrootlog.handlers = []
potrrootlog.propagate = False
gajimrootlog = logging.getLogger('gajim')
for h in gajimrootlog.handlers:
potrrootlog.addHandler(h)
def get_jid_from_fjid(fjid):
return gajim.get_room_and_nick_from_fjid(str(fjid))[0]
class GajimContext(potr.context.Context):
# self.peer is fjid
# self.jid does not contain resource
__slots__ = ['smpWindow', 'jid']
def __init__(self, account, peer):
super(GajimContext, self).__init__(account, peer)
self.jid = get_jid_from_fjid(peer)
self.trustName = self.jid
self.smpWindow = ui.ContactOtrSmpWindow(self)
def inject(self, msg, appdata=None):
log.debug('inject(appdata=%s)', appdata)
msg = unicode(msg)
account = self.user.accountname
stanza = nbxmpp.Message(to=self.peer, body=msg, typ='chat')
if appdata is not None:
thread_id = appdata.get('thread', None)
if thread_id is not None:
stanza.setThread(thread_id)
add_message_processing_hints(stanza)
gajim.connections[account].connection.send(stanza, now=True)
def setState(self, newstate):
if self.state == potr.context.STATE_ENCRYPTED:
# we were encrypted
if newstate == potr.context.STATE_ENCRYPTED:
# and are still -> it's just a refresh
OtrPlugin.gajim_log(
_('Private conversation with %s refreshed.') % self.peer,
self.user.accountname, self.peer)
elif newstate == potr.context.STATE_FINISHED:
# and aren't anymore -> other side disconnected
OtrPlugin.gajim_log(_('%s has ended his/her private '
'conversation with you. You should do the same.')
% self.peer, self.user.accountname, self.peer)
else:
if newstate == potr.context.STATE_ENCRYPTED:
# we are now encrypted
trust = self.getCurrentTrust()
if trust is None:
fpr = str(self.getCurrentKey())
OtrPlugin.gajim_log(_('New fingerprint for %(peer)s: %(fpr)s')
% {'peer': self.peer, 'fpr': fpr},
self.user.accountname, self.peer)
self.setCurrentTrust('')
trustStr = 'authenticated' if bool(trust) else '*unauthenticated*'
OtrPlugin.gajim_log(
_('%(trustStr)s secured OTR conversation with %(peer)s started')
% {'trustStr': trustStr, 'peer': self.peer},
self.user.accountname, self.peer)
if self.state != potr.context.STATE_PLAINTEXT and \
newstate == potr.context.STATE_PLAINTEXT:
# we are now plaintext
OtrPlugin.gajim_log(
_('Private conversation with %s lost.') % self.peer,
self.user.accountname, self.peer)
super(GajimContext, self).setState(newstate)
OtrPlugin.update_otr(self.peer, self.user.accountname)
self.user.plugin.update_context_list()
def getPolicy(self, key):
ret = self.user.plugin.get_flags(self.user.accountname, self.jid)[key]
log.debug('getPolicy(key=%s) = %s', key, ret)
return ret
class GajimOtrAccount(potr.context.Account):
contextclass = GajimContext
def __init__(self, plugin, accountname):
global PROTOCOL, MMS
self.plugin = plugin
self.accountname = accountname
name = gajim.get_jid_from_account(accountname)
super(GajimOtrAccount, self).__init__(name, PROTOCOL, MMS)
self.keyFilePath = os.path.join(gajim.gajimpaths.data_root, accountname)
def dropPrivkey(self):
try:
os.remove(self.keyFilePath + '.key3')
except IOError, e:
if e.errno != 2:
log.exception('IOError occurred when removing key file for %s',
self.name)
self.privkey = None
def loadPrivkey(self):
try:
with open(self.keyFilePath + '.key3', 'rb') as keyFile:
return potr.crypt.PK.parsePrivateKey(keyFile.read())[0]
except IOError, e:
if e.errno != 2:
log.exception('IOError occurred when loading key file for %s',
self.name)
return None
def savePrivkey(self):
try:
with open(self.keyFilePath + '.key3', 'wb') as keyFile:
keyFile.write(self.getPrivkey().serializePrivateKey())
except IOError, e:
log.exception('IOError occurred when loading key file for %s',
self.name)
def loadTrusts(self, newCtxCb=None):
''' load the fingerprint trustdb '''
# it has the same format as libotr, therefore the
# redundant account / proto field
try:
with open(self.keyFilePath + '.fpr', 'r') as fprFile:
for line in fprFile:
ctx, acc, proto, fpr, trust = line[:-1].split('\t')
if acc != self.name or proto != PROTOCOL:
continue
jid = get_jid_from_fjid(ctx)
self.setTrust(jid, fpr, trust)
except IOError, e:
if e.errno != 2:
log.exception('IOError occurred when loading fpr file for %s',
self.name)
def saveTrusts(self):
try:
with open(self.keyFilePath + '.fpr', 'w') as fprFile:
for uid, trusts in self.trusts.iteritems():
for fpr, trustVal in trusts.iteritems():
fprFile.write('\t'.join(
(uid, self.name, PROTOCOL, fpr, trustVal)))
fprFile.write('\n')
except IOError, e:
log.exception('IOError occurred when loading fpr file for %s',
self.name)
except ImportError:
HAS_POTR = False
def otr_dialog_destroy(widget, *args, **kwargs):
widget.destroy()
class OtrPlugin(GajimPlugin):
otr = None
def init(self):
self.us = {}
if not HAS_POTR:
self.activatable = False
self.available_text = _('Can\'t find potr. Verify this ' \
'plugin\'s integrity.')
return
if not HAS_CRYPTO:
self.activatable = False
self.available_text = _('PyCrypto not installed or too old.')
return
self.config_dialog = ui.OtrPluginConfigDialog(self)
self.events_handlers = {}
self.events_handlers['message-received'] = (ged.PRECORE,
self.handle_incoming_msg)
self.events_handlers['before-change-show'] = (ged.PRECORE,
self.handle_change_show)
if LooseVersion(gajim.config.get('version')) < LooseVersion(MINVERSION_OUTGOING_MSG_STAZA):
self.events_handlers['message-outgoing'] = (ged.OUT_PRECORE,
self.handle_outgoing_msg)
else:
self.events_handlers['stanza-message-outgoing'] = (ged.OUT_PRECORE,
self.handle_outgoing_msg_stanza)
DEFAULTFLAGS['SEND_TAG'] = False
self.gui_extension_points = {
'chat_control' : (self.cc_connect, self.cc_disconnect)
}
for acc in gajim.contacts.get_accounts():
self.us[acc] = GajimOtrAccount(self, acc)
self.us[acc].loadTrusts()
acc = str(acc)
if acc not in self.config or None not in self.config[acc]:
self.config[acc] = {None:DEFAULTFLAGS.copy()}
self.update_context_list()
@log_calls('OtrPlugin')
def activate(self):
if not HAS_CRYPTO or not HAS_POTR or not hasattr(potr, 'VERSION') \
or potr.VERSION < MINVERSION:
raise GajimPluginException(self.available_text)
def get_otr_status(self, account, contact):
ctx = self.us[account].getContext(contact.get_full_jid())
finished = ctx.state == potr.context.STATE_FINISHED
encrypted = finished or ctx.state == potr.context.STATE_ENCRYPTED
trusted = encrypted and bool(ctx.getCurrentTrust())
return (encrypted, trusted, finished)
def cc_connect(self, cc):
def update_otr(print_status=False):
enc_status, authenticated, finished = \
self.get_otr_status(cc.account, cc.contact)
otr_status_text = ''
if finished:
otr_status_text = u'finished OTR connection'
elif authenticated:
otr_status_text = u'authenticated secure OTR connection'
elif enc_status:
otr_status_text = u'*unauthenticated* secure OTR connection'
cc._show_lock_image(enc_status, u'OTR', enc_status, True,
authenticated)
if print_status and otr_status_text:
cc.print_conversation_line(u'[OTR] %s' % otr_status_text,
'status', '', None)
cc.update_otr = update_otr
cc.update_otr(True)
# hijack authentication button with our submenu
def authbutton_cb(widget):
if not cc.gpg_is_active and not (cc.session and
cc.session.enable_encryption):
ui.get_otr_submenu(self, cc).get_submenu().popup(None,
None, None, 0, 0)
else:
cc._on_authentication_button_clicked(widget)
self.overwrite_handler(cc, cc.authentication_button, authbutton_cb)
# hijack context menu
cc.orig_prepare_context_menu = cc.prepare_context_menu
def inject_menu(hide_buttonbar_items=False):
menu = cc.orig_prepare_context_menu(hide_buttonbar_items)
menu.insert(ui.get_otr_submenu(self, cc), 8)
return menu
cc.prepare_context_menu = inject_menu
def cc_disconnect(self, cc):
try:
self.overwrite_handler(cc, cc.authentication_button,
cc._on_authentication_button_clicked)
cc.prepare_context_menu = cc.orig_prepare_context_menu
del cc.update_otr
except AttributeError:
pass
def menu_settings_cb(self, item, control):
ctx = self.us[control.account].getContext(control.contact.get_full_jid())
dlg = ui.ContactOtrWindow(self, ctx)
dlg.run()
dlg.destroy()
def menu_start_cb(self, item, control):
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
account=control.account, jid=control.contact.jid,
message=u'?OTRv?', type_='chat',
resource=control.contact.resource, is_loggable=False))
def menu_end_cb(self, item, control):
fjid = control.contact.get_full_jid()
thread_id = control.session.thread_id if control.session else None
self.us[control.account].getContext(fjid).disconnect(
appdata={'thread':thread_id})
def menu_smp_cb(self, item, control):
ctx = self.us[control.account].getContext(control.contact.get_full_jid())
ctx.smpWindow.show(False)
@staticmethod
def overwrite_handler(window, control, handler):
for id_, v in window.handlers.iteritems():
if v == control:
break
else:
raise LookupError
del window.handlers[id_]
control.disconnect(id_)
id_ = control.connect('clicked', handler)
window.handlers[id_] = control
def set_flags(self, value, account=None, contact=None):
if isinstance(account, unicode):
account = account.encode()
if account not in self.config:
self.config[account] = {None:DEFAULTFLAGS.copy()}
if account is None and contact is not None:
# don't set per-contact options without account
raise Exception("can't set contact flags without account")
config = self.config[account]
config[contact] = value
self.config[account] = config
def get_flags(self, account=None, contact=None, fallback=True):
if isinstance(account, unicode):
account = account.encode()
setting = DEFAULTFLAGS.copy()
if account in self.config:
setting.update(self.config[account][None])
if contact in self.config[account] \
and self.config[account][contact] is not None:
setting.update(self.config[account][contact])
elif not fallback:
return None
return setting
def update_context_list(self):
self.config_dialog.fpr_model.clear()
for us in self.us.itervalues():
usedFpr = set()
for fjid, ctx in us.ctxs.iteritems():
# get active contexts first
key = ctx.getCurrentKey()
if not key:
continue
fpr = key.cfingerprint()
usedFpr.add(fpr)
human_hash = potr.human_hash(fpr)
trust = bool(us.getTrust(ctx.trustName, fpr))
if ctx.state == potr.context.STATE_ENCRYPTED:
state = "encrypted"
tip = enc_tip
elif ctx.state == potr.context.STATE_FINISHED:
state = "finished"
tip = ended_tip
else:
state = 'inactive'
tip = inactive_tip
self.config_dialog.fpr_model.append((fjid, state, trust,
'<tt>%s</tt>' % human_hash, us.name, tip, fpr))
for uid, trusts in us.trusts.iteritems():
for fpr, trust in trusts.iteritems():
if fpr in usedFpr:
continue
state = 'inactive'
tip = inactive_tip
human_hash = potr.human_hash(fpr)
self.config_dialog.fpr_model.append((uid, state, bool(trust),
'<tt>%s</tt>' % human_hash, us.name, tip, fpr))
@classmethod
def gajim_log(cls, msg, account, fjid, no_print=False,
is_status_message=True, thread_id=None):
if not isinstance(fjid, unicode):
fjid = unicode(fjid)
if not isinstance(account, unicode):
account = unicode(account)
resource = gajim.get_resource_from_jid(fjid)
jid = gajim.get_jid_without_resource(fjid)
tim = time.localtime()
if is_status_message is True:
if not no_print:
ctrl = cls.get_control(fjid, account)
if ctrl:
ctrl.print_conversation_line(u'[OTR] %s' % msg, 'status',
'', None)
if gajim.config.should_log(account, jid):
id = gajim.logger.write('chat_msg_recv', fjid,
message=u'[OTR: %s]' % msg, tim=tim)
# gajim.logger.write() only marks a message as unread (and so
# only returns an id) when fjid is a real contact (NOT if it's a
# GC private chat)
if id:
gajim.logger.set_read_messages([id])
else:
session = gajim.connections[account].get_or_create_session(fjid,
thread_id)
session.received_thread_id |= bool(thread_id)
session.last_receive = time.time()
if not session.control:
# look for an existing chat control without a session
ctrl = cls.get_control(fjid, account)
if ctrl:
session.control = ctrl
session.control.set_session(session)
if gajim.config.should_log(account, jid):
msg_id = gajim.logger.write('chat_msg_recv', fjid,
message=u'[OTR: %s]' % msg, tim=tim)
session.roster_message(jid, msg, tim=tim, msg_id=msg_id,
msg_type='chat', resource=resource)
@classmethod
def update_otr(cls, user, acc, print_status=False):
ctrl = cls.get_control(user, acc)
if ctrl:
ctrl.update_otr(print_status)
@staticmethod
def get_control(fjid, account):
# first try to get the window with the full jid
ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
if ctrl:
# got one, be happy
return ctrl
# otherwise try without the resource
ctrl = gajim.interface.msg_win_mgr.get_control(
gajim.get_jid_without_resource(str(fjid)), account)
# but only use it when it's not a GC window
if ctrl and ctrl.TYPE_ID == TYPE_CHAT:
return ctrl
def handle_change_show(self, event):
account = event.conn.name
if event.show == 'offline':
for us in self.us.itervalues():
for fjid, ctx in us.ctxs.iteritems():
if ctx.state == potr.context.STATE_ENCRYPTED:
self.us[account].getContext(fjid).disconnect()
return PASS
def handle_incoming_msg(self, event):
ctx = None
account = event.conn.name
accjid = gajim.get_jid_from_account(account)
if event.encrypted is not False or not event.stanza.getTag('body') \
or not isinstance(event.stanza.getBody(), unicode) \
or event.mtype != 'chat':
return PASS
try:
ctx = self.us[account].getContext(event.fjid)
msgtxt, tlvs = ctx.receiveMessage(event.msgtxt.encode('utf8'),
appdata={'thread':event.session.thread_id if event.session else None})
except potr.context.NotOTRMessage, e:
# received message was not OTR - pass it on
return PASS
except potr.context.UnencryptedMessage, e:
# we are encrypted but got some plaintext
# display it with a warning
tlvs = []
msgtxt = _('The following message received from %(jid)s was '
'*not encrypted*: [%(error)s]') % {'jid': event.fjid,
'error': e.args[0]}
except potr.context.NotEncryptedError, e:
# we got some encrypted data
# but we don't have an encrypted session
self.gajim_log(_('The encrypted message received from %s is '
'unreadable, as you are not currently communicating '
'privately') % event.fjid, account, event.fjid)
return IGNORE
except potr.context.ErrorReceived, e:
# got a protocol error
self.gajim_log(_('We received the following OTR error '
'message from %(jid)s: [%(error)s]') % {'jid': event.fjid,
'error': e.args[0].error},
account, event.fjid)
return IGNORE
except potr.crypt.InvalidParameterError, e:
# received a packet we cannot process (probably tampered or
# sent to wrong session)
self.gajim_log(_('We received an unreadable OTR message '
'from %(jid)s. It has probably been tampered with, '
'or was sent from an older OTR session.')
% {'jid':event.fjid}, account, event.fjid)
return IGNORE
except RuntimeError, e:
# generic library bug?
self.gajim_log(_('The following error occurred when trying to '
'decrypt a message from %(jid)s: [%(error)s]') % {
'jid': event.fjid, 'error': e},
account, event.fjid)
return IGNORE
if ctx is not None:
ctx.smpWindow.handle_tlv(tlvs)
stripper = HTMLStripper()
stripper.feed((msgtxt or '').decode('utf8'))
event.msgtxt = stripper.stripped_data
event.stanza.setBody(event.msgtxt)
if event.stanza.getXHTML():
event.stanza.delChild('html')
event.stanza.setXHTML((msgtxt or '').decode('utf8'))
return PASS
def handle_outgoing_msg_stanza(self, event):
xhtml = event.msg_iq.getXHTML()
body = event.msg_iq.getBody()
encrypted = False
thread_id = event.msg_iq.getThread()
try:
if xhtml:
xhtml = xhtml.encode('utf8')
encrypted_msg = self.us[event.conn.name].\
getContext(event.msg_iq.getTo()).\
sendMessage(potr.context.FRAGMENT_SEND_ALL_BUT_LAST, xhtml,
appdata={'thread': thread_id})
if xhtml != encrypted_msg.strip(): #.strip() because sendMessage() adds whitespaces
encrypted = True
event.msg_iq.delChild('html')
event.msg_iq.setBody(encrypted_msg)
elif body:
body = escape(body).encode('utf8')
encrypted_msg = self.us[event.conn.name].\
getContext(event.msg_iq.getTo()).\
sendMessage(potr.context.FRAGMENT_SEND_ALL_BUT_LAST, body,
appdata={'thread': thread_id})
if body != encrypted_msg.strip():
encrypted = True
event.msg_iq.setBody(encrypted_msg)
except potr.context.NotEncryptedError, e:
if e.args[0] == potr.context.EXC_FINISHED:
self.gajim_log(msg_not_send, event.conn.name, event.msg_iq.getTo())
return IGNORE
else:
raise e
if encrypted:
add_message_processing_hints(event.msg_iq)
return PASS
def handle_outgoing_msg(self, event):
try:
if hasattr(event, 'otrmessage'):
return PASS
xep_200 = bool(event.session) and event.session.enable_encryption
potrrootlog.debug('got event {0} xep_200={1}'.format(pformat(event.__dict__), xep_200))
if xep_200 or not event.message:
return PASS
if event.session:
fjid = event.session.get_to()
else:
fjid = event.jid
if event.resource:
fjid += '/' + event.resource
message = event.xhtml or escape(event.message)
message = message.encode('utf8')
potrrootlog.debug('processing message={0!r} from fjid={1!r}'.format(message, fjid))
try:
newmsg = self.us[event.account].getContext(fjid).sendMessage(
potr.context.FRAGMENT_SEND_ALL_BUT_LAST, message,
appdata={'thread':event.session.thread_id if event.session else None})
potrrootlog.debug('processed message={0!r}'.format(newmsg))
except potr.context.NotEncryptedError, e:
if e.args[0] == potr.context.EXC_FINISHED:
self.gajim_log(msg_not_send, event.account, fjid)
return IGNORE
else:
raise e
if event.xhtml: # if we had html before, replace with new content
event.xhtml = newmsg
stripper = HTMLStripper()
stripper.feed((newmsg or '').decode('utf8'))
event.message = stripper.stripped_data
return PASS
except:
potrrootlog.exception('exception in outgoing message handler, message (hopefully) discarded')
return IGNORE
class HTMLStripper(HTMLParser):
def reset(self):
self.stripped_data = ''
HTMLParser.reset(self)
def handle_data(self, data):
self.stripped_data += data
def handle_starttag(self, tag, attrs):
if tag == 'br':
self.stripped_data += '\n'
def handle_entityref(self, name):
try:
c = unichr(name2codepoint[name])
except KeyError:
c = '&{};'.format(name)
self.stripped_data += c
def handle_charref(self, name):
if name.startswith('x'):
c = unichr(int(name[1:], 16))
else:
c = unichr(int(name))
self.stripped_data += c
def unknown_decl(self, data):
if data.startswith('CDATA['):
self.stripped_data += data[6:]
def feed(self, data):
data = data.replace('\n', '')
HTMLParser.feed(self, data)
def escape(s):
'''Replace special characters "&", "<" and ">" to HTML-safe sequences.
If the optional flag quote is true, the quotation mark character (")
is also translated.'''
s = s.replace("&", "&") # Must be done first!
s = s.replace("<", "<")
s = s.replace(">", ">")
s = s.replace("\n", "<br/>")
return s
def add_message_processing_hints(stanza):
stanza.addChild(name='private', namespace=nbxmpp.NS_CARBONS)
stanza.addChild(name='no-permanent-store', namespace=nbxmpp.NS_MSG_HINTS)
stanza.addChild(name='no-copy', namespace=nbxmpp.NS_MSG_HINTS)