Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndré Apitzsch <git@apitzsch.eu>2017-06-21 00:09:12 +0300
committerAndré Apitzsch <git@apitzsch.eu>2017-07-02 18:25:47 +0300
commit125ce523e44f11620de2cb09c55bce95d03f0993 (patch)
tree59fcf5f02b403d48c61da8707324cc7e19a4dd7f /src/common
parent68a57e7c917244be40d569700f09ab67f11db45a (diff)
Rename src directory
Diffstat (limited to 'src/common')
-rw-r--r--src/common/__init__.py0
-rw-r--r--src/common/account.py35
-rw-r--r--src/common/atom.py176
-rw-r--r--src/common/caps_cache.py440
-rw-r--r--src/common/check_X509.py183
-rw-r--r--src/common/check_paths.py364
-rw-r--r--src/common/commands.py495
-rw-r--r--src/common/config.py826
-rw-r--r--src/common/configpaths.py197
-rw-r--r--src/common/connection.py2982
-rw-r--r--src/common/connection_handlers.py2297
-rw-r--r--src/common/connection_handlers_events.py2834
-rw-r--r--src/common/contacts.py868
-rw-r--r--src/common/crypto.py151
-rw-r--r--src/common/dataforms.py754
-rw-r--r--src/common/dbus_support.py189
-rw-r--r--src/common/defs.py44
-rw-r--r--src/common/dh.py233
-rw-r--r--src/common/events.py454
-rw-r--r--src/common/exceptions.py149
-rw-r--r--src/common/file_props.py166
-rwxr-xr-xsrc/common/fuzzyclock.py70
-rw-r--r--src/common/gajim.py486
-rw-r--r--src/common/ged.py102
-rw-r--r--src/common/gpg.py150
-rw-r--r--src/common/helpers.py1531
-rw-r--r--src/common/i18n.py105
-rw-r--r--src/common/idle.py99
-rw-r--r--src/common/jingle.py233
-rw-r--r--src/common/jingle_content.py240
-rw-r--r--src/common/jingle_ft.py413
-rw-r--r--src/common/jingle_ftstates.py229
-rw-r--r--src/common/jingle_rtp.py477
-rw-r--r--src/common/jingle_session.py833
-rw-r--r--src/common/jingle_transport.py449
-rw-r--r--src/common/jingle_xtls.py299
-rw-r--r--src/common/location_listener.py152
-rw-r--r--src/common/logger.py1193
-rw-r--r--src/common/logging_helpers.py196
-rw-r--r--src/common/message_archiving.py459
-rw-r--r--src/common/multimedia_helpers.py111
-rw-r--r--src/common/nec.py171
-rw-r--r--src/common/optparser.py1001
-rw-r--r--src/common/passwords.py175
-rw-r--r--src/common/pep.py473
-rw-r--r--src/common/protocol/__init__.py3
-rw-r--r--src/common/protocol/bytestream.py1023
-rw-r--r--src/common/protocol/caps.py118
-rw-r--r--src/common/proxy65_manager.py487
-rw-r--r--src/common/pubsub.py226
-rw-r--r--src/common/resolver.py157
-rw-r--r--src/common/rst_xhtml_generator.py169
-rw-r--r--src/common/sleepy.py145
-rw-r--r--src/common/socks5.py1474
-rw-r--r--src/common/stanza_session.py1213
-rw-r--r--src/common/zeroconf/__init__.py0
-rw-r--r--src/common/zeroconf/client_zeroconf.py864
-rw-r--r--src/common/zeroconf/connection_handlers_zeroconf.py114
-rw-r--r--src/common/zeroconf/connection_zeroconf.py384
-rw-r--r--src/common/zeroconf/roster_zeroconf.py159
-rw-r--r--src/common/zeroconf/zeroconf.py63
-rw-r--r--src/common/zeroconf/zeroconf_avahi.py482
-rw-r--r--src/common/zeroconf/zeroconf_bonjour.py335
63 files changed, 0 insertions, 30900 deletions
diff --git a/src/common/__init__.py b/src/common/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/src/common/__init__.py
+++ /dev/null
diff --git a/src/common/account.py b/src/common/account.py
deleted file mode 100644
index 976b6bf43..000000000
--- a/src/common/account.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/contacts.py
-##
-## Copyright (C) 2009 Stephan Erb <steve-e AT h3c.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/>.
-##
-
-class Account(object):
-
- def __init__(self, name, contacts, gc_contacts):
- self.name = name
- self.contacts = contacts
- self.gc_contacts = gc_contacts
-
- def change_contact_jid(self, old_jid, new_jid):
- self.contacts.change_contact_jid(old_jid, new_jid)
-
- def __repr__(self):
- return self.name
-
- def __hash__(self):
- return hash(self.name)
diff --git a/src/common/atom.py b/src/common/atom.py
deleted file mode 100644
index 7cf606832..000000000
--- a/src/common/atom.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/common/atom.py
-##
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## 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/>.
-##
-
-"""
-Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports
-and services. Very simple. Actually implements only atom:entry. Implement more features
-if you need
-"""
-
-# suggestion: rewrite functions that return dates to return standard python time tuples,
-# exteneded to contain timezone
-
-import nbxmpp
-import time
-
-class PersonConstruct(nbxmpp.Node, object):
- """
- Not used for now, as we don't need authors/contributors in pubsub.com feeds.
- They rarely exist there
- """
-
- def __init__(self, node):
- ''' Create person construct from node. '''
- nbxmpp.Node.__init__(self, node=node)
-
- def get_name(self):
- return self.getTagData('name')
-
- name = property(get_name, None, None,
- '''Conveys a human-readable name for the person. Should not be None,
- although some badly generated atom feeds don't put anything here
- (this is non-standard behavior, still pubsub.com sometimes does that.)''')
-
- def get_uri(self):
- return self.getTagData('uri')
-
- uri = property(get_uri, None, None,
- '''Conveys an IRI associated with the person. Might be None when not set.''')
-
- def get_email(self):
- return self.getTagData('email')
-
- email = property(get_email, None, None,
- '''Conveys an e-mail address associated with the person. Might be None when
- not set.''')
-
-class Entry(nbxmpp.Node, object):
- def __init__(self, node=None):
- nbxmpp.Node.__init__(self, 'entry', node=node)
-
- def __repr__(self):
- return '<Atom:Entry object of id="%r">' % self.getAttr('id')
-
-class OldEntry(nbxmpp.Node, object):
- """
- Parser for feeds from pubsub.com. They use old Atom 0.3 format with their
- extensions
- """
-
- def __init__(self, node=None):
- ''' Create new Atom 0.3 entry object. '''
- nbxmpp.Node.__init__(self, 'entry', node=node)
-
- def __repr__(self):
- return '<Atom0.3:Entry object of id="%r">' % self.getAttr('id')
-
- def get_feed_title(self):
- """
- Return title of feed, where the entry was created. The result is the feed
- name concatenated with source-feed title
- """
- if self.parent is not None:
- main_feed = self.parent.getTagData('title')
- else:
- main_feed = None
-
- if self.getTag('feed') is not None:
- source_feed = self.getTag('feed').getTagData('title')
- else:
- source_feed = None
-
-
- if main_feed is not None and source_feed is not None:
- return '%s: %s' % (main_feed, source_feed)
- elif main_feed is not None:
- return main_feed
- elif source_feed is not None:
- return source_feed
- else:
- return ''
-
- feed_title = property(get_feed_title, None, None,
- ''' Title of feed. It is built from entry''s original feed title and title of feed
- which delivered this entry. ''')
-
- def get_feed_link(self):
- """
- Get source link
- """
- try:
- return self.getTag('feed').getTags('link', {'rel':'alternate'})[1].getData()
- except Exception:
- return None
-
- feed_link = property(get_feed_link, None, None,
- ''' Link to main webpage of the feed. ''')
-
- def get_title(self):
- """
- Get an entry's title
- """
- return self.getTagData('title')
-
- title = property(get_title, None, None,
- ''' Entry's title. ''')
-
- def get_uri(self):
- """
- Get the uri the entry points to (entry's first link element with
- rel='alternate' or without rel attribute)
- """
- for element in self.getTags('link'):
- if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue
- try:
- return element.attrs['href']
- except AttributeError:
- pass
- return None
-
- uri = property(get_uri, None, None,
- ''' URI that is pointed by the entry. ''')
-
- def get_updated(self):
- """
- Get the time the entry was updated last time
-
- This should be standarized, but pubsub.com sends it in human-readable
- format. We won't try to parse it. (Atom 0.3 uses the word «modified» for
- that).
-
- If there's no time given in the entry, we try with <published>
- and <issued> elements.
- """
- for name in ('updated', 'modified', 'published', 'issued'):
- date = self.getTagData(name)
- if date is not None: break
-
- if date is None:
- # it is not in the standard format
- return time.asctime()
-
- return date
-
- updated = property(get_updated, None, None,
- ''' Last significant modification time. ''')
-
- feed_tagline = ''
diff --git a/src/common/caps_cache.py b/src/common/caps_cache.py
deleted file mode 100644
index 6a1fc7d15..000000000
--- a/src/common/caps_cache.py
+++ /dev/null
@@ -1,440 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/caps_cache.py
-##
-## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-## Copyright (C) 2008-2009 Stephan Erb <steve-e AT h3c.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/>.
-##
-
-"""
-Module containing all XEP-115 (Entity Capabilities) related classes
-
-Basic Idea:
-CapsCache caches features to hash relationships. The cache is queried
-through ClientCaps objects which are hold by contact instances.
-"""
-
-import base64
-import hashlib
-
-import logging
-log = logging.getLogger('gajim.c.caps_cache')
-
-from nbxmpp import (NS_XHTML_IM, NS_ESESSION, NS_CHATSTATES,
- NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO,
- NS_JINGLE_FILE_TRANSFER_5)
-# Features where we cannot safely assume that the other side supports them
-FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_ESESSION,
- NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO,
- NS_JINGLE_FILE_TRANSFER_5]
-
-# Query entry status codes
-NEW = 0
-QUERIED = 1
-CACHED = 2 # got the answer
-FAKED = 3 # allow NullClientCaps to behave as it has a cached item
-
-################################################################################
-### Public API of this module
-################################################################################
-
-capscache = None
-def initialize(logger):
- """
- Initialize this module
- """
- global capscache
- capscache = CapsCache(logger)
-
-def client_supports(client_caps, requested_feature):
- lookup_item = client_caps.get_cache_lookup_strategy()
- cache_item = lookup_item(capscache)
-
- supported_features = cache_item.features
- if requested_feature in supported_features:
- return True
- elif not supported_features and cache_item.status in (NEW, QUERIED, FAKED):
- # assume feature is supported, if we don't know yet, what the client
- # is capable of
- return requested_feature not in FEATURE_BLACKLIST
- else:
- return False
-
-def create_suitable_client_caps(node, caps_hash, hash_method, fjid=None):
- """
- Create and return a suitable ClientCaps object for the given node,
- caps_hash, hash_method combination.
- """
- if not node or not caps_hash:
- if fjid:
- client_caps = NoClientCaps(fjid)
- else:
- # improper caps, ignore client capabilities.
- client_caps = NullClientCaps()
- elif not hash_method:
- client_caps = OldClientCaps(caps_hash, node)
- else:
- client_caps = ClientCaps(caps_hash, node, hash_method)
- return client_caps
-
-def compute_caps_hash(identities, features, dataforms=None, hash_method='sha-1'):
- """
- Compute caps hash according to XEP-0115, V1.5
-
- dataforms are nbxmpp.DataForms objects as common.dataforms don't allow several
- values without a field type list-multi
- """
- if dataforms is None:
- dataforms = []
- def sort_identities_func(i1, i2):
- cat1 = i1['category']
- cat2 = i2['category']
- if cat1 < cat2:
- return -1
- if cat1 > cat2:
- return 1
- type1 = i1.get('type', '')
- type2 = i2.get('type', '')
- if type1 < type2:
- return -1
- if type1 > type2:
- return 1
- lang1 = i1.get('xml:lang', '')
- lang2 = i2.get('xml:lang', '')
- if lang1 < lang2:
- return -1
- if lang1 > lang2:
- return 1
- return 0
-
- def sort_dataforms_func(d1, d2):
- f1 = d1.getField('FORM_TYPE')
- f2 = d2.getField('FORM_TYPE')
- if f1 and f2 and (f1.getValue() < f2.getValue()):
- return -1
- return 1
-
- S = ''
- from functools import cmp_to_key
- identities.sort(key=cmp_to_key(sort_identities_func))
- for i in identities:
- c = i['category']
- type_ = i.get('type', '')
- lang = i.get('xml:lang', '')
- name = i.get('name', '')
- S += '%s/%s/%s/%s<' % (c, type_, lang, name)
- features.sort()
- for f in features:
- S += '%s<' % f
- dataforms.sort(key=cmp_to_key(sort_dataforms_func))
- for dataform in dataforms:
- # fields indexed by var
- fields = {}
- for f in dataform.getChildren():
- fields[f.getVar()] = f
- form_type = fields.get('FORM_TYPE')
- if form_type:
- S += form_type.getValue() + '<'
- del fields['FORM_TYPE']
- for var in sorted(fields.keys()):
- S += '%s<' % var
- values = sorted(fields[var].getValues())
- for value in values:
- S += '%s<' % value
-
- if hash_method == 'sha-1':
- hash_ = hashlib.sha1(S.encode('utf-8'))
- elif hash_method == 'md5':
- hash_ = hashlib.md5(S.encode('utf-8'))
- else:
- return ''
- return base64.b64encode(hash_.digest()).decode('utf-8')
-
-
-################################################################################
-### Internal classes of this module
-################################################################################
-
-class AbstractClientCaps(object):
- """
- Base class representing a client and its capabilities as advertised by a
- caps tag in a presence
- """
- def __init__(self, caps_hash, node):
- self._hash = caps_hash
- self._node = node
- self._hash_method = None
-
- def get_discover_strategy(self):
- return self._discover
-
- def _discover(self, connection, jid):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
-
- def get_cache_lookup_strategy(self):
- return self._lookup_in_cache
-
- def _lookup_in_cache(self, caps_cache):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
-
- def get_hash_validation_strategy(self):
- return self._is_hash_valid
-
- def _is_hash_valid(self, identities, features, dataforms):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
-
-
-class ClientCaps(AbstractClientCaps):
- """
- The current XEP-115 implementation
- """
- def __init__(self, caps_hash, node, hash_method):
- AbstractClientCaps.__init__(self, caps_hash, node)
- assert hash_method != 'old'
- self._hash_method = hash_method
-
- def _lookup_in_cache(self, caps_cache):
- return caps_cache[(self._hash_method, self._hash)]
-
- def _discover(self, connection, jid):
- connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
-
- def _is_hash_valid(self, identities, features, dataforms):
- computed_hash = compute_caps_hash(identities, features,
- dataforms=dataforms, hash_method=self._hash_method)
- return computed_hash == self._hash
-
-
-class OldClientCaps(AbstractClientCaps):
- """
- Old XEP-115 implemtation. Kept around for background competability
- """
- def __init__(self, caps_hash, node):
- AbstractClientCaps.__init__(self, caps_hash, node)
- self._hash_method = 'old'
-
- def _lookup_in_cache(self, caps_cache):
- return caps_cache[('old', self._node + '#' + self._hash)]
-
- def _discover(self, connection, jid):
- connection.discoverInfo(jid)
-
- def _is_hash_valid(self, identities, features, dataforms):
- return True
-
-class NoClientCaps(AbstractClientCaps):
- """
- For clients that don't support XEP-0115
- """
- def __init__(self, fjid):
- AbstractClientCaps.__init__(self, fjid, fjid)
- self._hash_method = 'no'
-
- def _lookup_in_cache(self, caps_cache):
- return caps_cache[('no', self._node)]
-
- def _discover(self, connection, jid):
- connection.discoverInfo(jid)
-
- def _is_hash_valid(self, identities, features, dataforms):
- return True
-
-class NullClientCaps(AbstractClientCaps):
- """
- This is a NULL-Object to streamline caps handling if a client has not
- advertised any caps or has advertised them in an improper way
-
- Assumes (almost) everything is supported.
- """
- _instance = None
- def __new__(cls, *args, **kwargs):
- """
- Make it a singleton.
- """
- if not cls._instance:
- cls._instance = super(NullClientCaps, cls).__new__(
- cls, *args, **kwargs)
- return cls._instance
-
- def __init__(self):
- AbstractClientCaps.__init__(self, None, None)
- self._hash_method = 'dummy'
-
- def _lookup_in_cache(self, caps_cache):
- # lookup something which does not exist to get a new CacheItem created
- cache_item = caps_cache[('dummy', '')]
- # Mark the item as cached so that protocol/caps.py does not update it
- cache_item.status = FAKED
- return cache_item
-
- def _discover(self, connection, jid):
- pass
-
- def _is_hash_valid(self, identities, features, dataforms):
- return False
-
-
-class CapsCache(object):
- """
- This object keeps the mapping between caps data and real disco features they
- represent, and provides simple way to query that info
- """
- def __init__(self, logger=None):
- # our containers:
- # __cache is a dictionary mapping: pair of hash method and hash maps
- # to CapsCacheItem object
- # __CacheItem is a class that stores data about particular
- # client (hash method/hash pair)
- self.__cache = {}
-
- class CacheItem(object):
- # __names is a string cache; every string long enough is given
- # another object, and we will have plenty of identical long
- # strings. therefore we can cache them
- __names = {}
-
- def __init__(self, hash_method, hash_, logger):
- # cached into db
- self.hash_method = hash_method
- self.hash = hash_
- self._features = []
- self._identities = []
- self._logger = logger
-
- self.status = NEW
- self._recently_seen = False
-
- def _get_features(self):
- return self._features
-
- def _set_features(self, value):
- self._features = []
- for feature in value:
- self._features.append(self.__names.setdefault(feature, feature))
-
- features = property(_get_features, _set_features)
-
- def _get_identities(self):
- list_ = []
- for i in self._identities:
- # transforms it back in a dict
- d = dict()
- d['category'] = i[0]
- if i[1]:
- d['type'] = i[1]
- if i[2]:
- d['xml:lang'] = i[2]
- if i[3]:
- d['name'] = i[3]
- list_.append(d)
- return list_
-
- def _set_identities(self, value):
- self._identities = []
- for identity in value:
- # dict are not hashable, so transform it into a tuple
- t = (identity['category'], identity.get('type'),
- identity.get('xml:lang'), identity.get('name'))
- self._identities.append(self.__names.setdefault(t, t))
-
- identities = property(_get_identities, _set_identities)
-
- def set_and_store(self, identities, features):
- self.identities = identities
- self.features = features
- if self.hash_method != 'no':
- self._logger.add_caps_entry(self.hash_method, self.hash,
- identities, features)
- self.status = CACHED
-
- def update_last_seen(self):
- if not self._recently_seen:
- self._recently_seen = True
- if self.hash_method != 'no':
- self._logger.update_caps_time(self.hash_method,
- self.hash)
-
- def is_valid(self):
- """
- Returns True if identities and features for this cache item
- are known.
- """
- return self.status in (CACHED, FAKED)
-
- self.__CacheItem = CacheItem
- self.logger = logger
-
- def initialize_from_db(self):
- self._remove_outdated_caps()
- for hash_method, hash_, identities, features in \
- self.logger.iter_caps_data():
- x = self[(hash_method, hash_)]
- x.identities = identities
- x.features = features
- x.status = CACHED
-
- def _remove_outdated_caps(self):
- """
- Remove outdated values from the db
- """
- self.logger.clean_caps_table()
-
- def __getitem__(self, caps):
- if caps in self.__cache:
- return self.__cache[caps]
-
- hash_method, hash_ = caps
-
- x = self.__CacheItem(hash_method, hash_, self.logger)
- self.__cache[(hash_method, hash_)] = x
- return x
-
- def query_client_of_jid_if_unknown(self, connection, jid, client_caps):
- """
- Start a disco query to determine caps (node, ver, exts). Won't query if
- the data is already in cache
- """
- lookup_cache_item = client_caps.get_cache_lookup_strategy()
- q = lookup_cache_item(self)
-
- if q.status == NEW:
- # do query for bare node+hash pair
- # this will create proper object
- q.status = QUERIED
- discover = client_caps.get_discover_strategy()
- discover(connection, jid)
- else:
- q.update_last_seen()
-
- def forget_caps(self, client_caps):
- hash_method = client_caps._hash_method
- hash = client_caps._hash
- key = (hash_method, hash)
- if key in self.__cache:
- del self.__cache[key]
diff --git a/src/common/check_X509.py b/src/common/check_X509.py
deleted file mode 100644
index 4aec0a7e9..000000000
--- a/src/common/check_X509.py
+++ /dev/null
@@ -1,183 +0,0 @@
-import logging
-log = logging.getLogger('gajim.c.check_X509')
-
-try:
- import OpenSSL.SSL
- import OpenSSL.crypto
- ver = OpenSSL.__version__
- if ver < '0.12':
- raise ImportError
- from pyasn1.type import univ, constraint, char, namedtype, tag
- from pyasn1.codec.der.decoder import decode
- from common.helpers import prep, InvalidFormat
-
- MAX = 64
- oid_xmppaddr = '1.3.6.1.5.5.7.8.5'
- oid_dnssrv = '1.3.6.1.5.5.7.8.7'
-
-
-
- class DirectoryString(univ.Choice):
- componentType = namedtype.NamedTypes(
- namedtype.NamedType(
- 'teletexString', char.TeletexString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'printableString', char.PrintableString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'universalString', char.UniversalString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'utf8String', char.UTF8String().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'bmpString', char.BMPString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'ia5String', char.IA5String().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'gString', univ.OctetString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- )
-
- class AttributeValue(DirectoryString):
- pass
-
- class AttributeType(univ.ObjectIdentifier):
- pass
-
- class AttributeTypeAndValue(univ.Sequence):
- componentType = namedtype.NamedTypes(
- namedtype.NamedType('type', AttributeType()),
- namedtype.NamedType('value', AttributeValue()),
- )
-
- class RelativeDistinguishedName(univ.SetOf):
- componentType = AttributeTypeAndValue()
-
- class RDNSequence(univ.SequenceOf):
- componentType = RelativeDistinguishedName()
-
- class Name(univ.Choice):
- componentType = namedtype.NamedTypes(
- namedtype.NamedType('', RDNSequence()),
- )
-
- class GeneralName(univ.Choice):
- componentType = namedtype.NamedTypes(
- namedtype.NamedType('otherName', univ.Sequence().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatConstructed, 0x0))),
- namedtype.NamedType('rfc822Name', char.IA5String().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 1))),
- namedtype.NamedType('dNSName', char.IA5String().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 2))),
- namedtype.NamedType('x400Address', univ.Sequence().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatConstructed, 0x3))),
- namedtype.NamedType('directoryName', Name().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatConstructed, 0x4))),
- namedtype.NamedType('ediPartyName', univ.Sequence().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatConstructed, 0x5))),
- namedtype.NamedType('uniformResourceIdentifier',
- char.IA5String().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 6))),
- namedtype.NamedType('iPAddress', univ.OctetString().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 7))),
- namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 8))),
- )
-
- class GeneralNames(univ.SequenceOf):
- componentType = GeneralName()
- sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
-
- def _parse_asn1(asn1):
- obj = decode(asn1, asn1Spec=GeneralNames())[0]
- r = {}
- for o in obj:
- name = o.getName()
- if name == 'dNSName':
- if name not in r:
- r[name] = []
- r[name].append(str(o.getComponent()))
- if name == 'otherName':
- if name not in r:
- r[name] = {}
- tag = str(tuple(o.getComponent())[0])
- val = str(tuple(o.getComponent())[1])
- if tag not in r[name]:
- r[name][tag] = []
- r[name][tag].append(val)
- if name == 'uniformResourceIdentifier':
- r['uniformResourceIdentifier'] = True
- return r
-
- def check_certificate(cert, domain):
- cnt = cert.get_extension_count()
- if '.' in domain:
- compared_domain = domain.split('.', 1)[1]
- else:
- compared_domain = ''
- srv_domain = '_xmpp-client.' + domain
- compared_srv_domain = '_xmpp-client.' + compared_domain
- for i in range(0, cnt):
- ext = cert.get_extension(i)
- if ext.get_short_name() == b'subjectAltName':
- try:
- r = _parse_asn1(ext.get_data())
- except:
- log.error('Wrong data in certificate: subjectAltName=%s' % \
- ext.get_data())
- continue
- if 'otherName' in r:
- if oid_xmppaddr in r['otherName']:
- for host in r['otherName'][oid_xmppaddr]:
- try:
- host = prep(None, host, None)
- except InvalidFormat:
- continue
- if host == domain:
- return True
- if oid_dnssrv in r['otherName']:
- for host in r['otherName'][oid_dnssrv]:
- if host.startswith('_xmpp-client.*.'):
- if host.replace('*.', '', 1) == compared_srv_domain:
- return True
- continue
- if host == srv_domain:
- return True
- if 'dNSName' in r:
- for host in r['dNSName']:
- if host.startswith('*.'):
- if host[2:] == compared_domain:
- return True
- continue
- if host == domain:
- return True
- if r:
- return False
- break
-
- subject = cert.get_subject()
- if subject.commonName == domain:
- return True
- return False
-except ImportError:
- log.warning('Import of PyOpenSSL or pyasn1 failed. Cannot correctly check '
- 'SSL certificate')
-
- def check_certificate(cert, domain):
- subject = cert.get_subject()
- if subject.commonName == domain:
- return True
- return False
diff --git a/src/common/check_paths.py b/src/common/check_paths.py
deleted file mode 100644
index 98d1a9cc1..000000000
--- a/src/common/check_paths.py
+++ /dev/null
@@ -1,364 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/check_paths.py
-##
-## Copyright (C) 2005-2006 Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org>
-##
-## 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/>.
-##
-
-import os
-import shutil
-import sys
-
-from common import gajim
-from common import logger
-
-# DO NOT MOVE ABOVE OF import gajim
-import sqlite3 as sqlite
-
-def create_log_db():
- print(_('creating logs database'))
- con = sqlite.connect(logger.LOG_DB_PATH)
- os.chmod(logger.LOG_DB_PATH, 0o600) # rw only for us
- cur = con.cursor()
- # create the tables
- # kind can be
- # status, gcstatus, gc_msg, (we only recv for those 3),
- # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent
- # to meet all our needs
- # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code
- # jids.jid text column will be JID if TC-related, room_jid if GC-related,
- # ROOM_JID/nick if pm-related.
- # also check optparser.py, which updates databases on gajim updates
- cur.executescript(
- '''
- CREATE TABLE jids(
- jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid TEXT UNIQUE,
- type INTEGER
- );
-
- CREATE TABLE unread_messages(
- message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER,
- shown BOOLEAN default 0
- );
-
- CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
-
- CREATE TABLE logs(
- log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER,
- contact_name TEXT,
- time INTEGER,
- kind INTEGER,
- show INTEGER,
- message TEXT,
- subject TEXT,
- additional_data TEXT DEFAULT '{}',
- stanza_id TEXT,
- mam_id TEXT,
- encryption TEXT,
- encryption_state TEXT,
- marker INTEGER
- );
-
- CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC);
- '''
- )
-
- con.commit()
- con.close()
-
-def create_cache_db():
- print(_('creating cache database'))
- con = sqlite.connect(logger.CACHE_DB_PATH)
- os.chmod(logger.CACHE_DB_PATH, 0o600) # rw only for us
- cur = con.cursor()
- cur.executescript(
- '''
- CREATE TABLE transports_cache (
- transport TEXT UNIQUE,
- type INTEGER
- );
-
- CREATE TABLE caps_cache (
- hash_method TEXT,
- hash TEXT,
- data BLOB,
- last_seen INTEGER);
-
- CREATE TABLE rooms_last_message_time(
- jid_id INTEGER PRIMARY KEY UNIQUE,
- time INTEGER
- );
-
- CREATE TABLE IF NOT EXISTS roster_entry(
- account_jid_id INTEGER,
- jid_id INTEGER,
- name TEXT,
- subscription INTEGER,
- ask BOOLEAN,
- PRIMARY KEY (account_jid_id, jid_id)
- );
-
- CREATE TABLE IF NOT EXISTS roster_group(
- account_jid_id INTEGER,
- jid_id INTEGER,
- group_name TEXT,
- PRIMARY KEY (account_jid_id, jid_id, group_name)
- );
- '''
- )
-
- con.commit()
- con.close()
-
-def split_db():
- print('spliting database')
- if os.name == 'nt':
- try:
- OLD_LOG_DB_FOLDER = os.path.join(os.environ['appdata'], 'Gajim')
- except KeyError:
- OLD_LOG_DB_FOLDER = '.'
- else:
- OLD_LOG_DB_FOLDER = os.path.expanduser('~/.gajim')
-
- tmp = logger.CACHE_DB_PATH
- logger.CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'cache.db')
- create_cache_db()
- back = os.getcwd()
- os.chdir(OLD_LOG_DB_FOLDER)
- con = sqlite.connect('logs.db')
- os.chdir(back)
- cur = con.cursor()
- cur.execute('''SELECT name FROM sqlite_master WHERE type = 'table';''')
- tables = cur.fetchall() # we get [('jids',), ('unread_messages',), ...
- tables = [t[0] for t in tables]
- cur.execute("ATTACH DATABASE '%s' AS cache" % logger.CACHE_DB_PATH)
- for table in ('caps_cache', 'rooms_last_message_time', 'roster_entry',
- 'roster_group', 'transports_cache'):
- if table not in tables:
- continue
- try:
- cur.executescript(
- 'INSERT INTO cache.%s SELECT * FROM %s;' % (table, table))
- con.commit()
- cur.executescript('DROP TABLE %s;' % table)
- con.commit()
- except sqlite.OperationalError as e:
- print('error moving table %s to cache.db: %s' % (table, str(e)),
- file=sys.stderr)
- con.close()
- logger.CACHE_DB_PATH = tmp
-
-def check_and_possibly_move_config():
- LOG_DB_PATH = logger.LOG_DB_PATH
- CACHE_DB_PATH = logger.CACHE_DB_PATH
- vars = {}
- vars['VCARD_PATH'] = gajim.VCARD_PATH
- vars['AVATAR_PATH'] = gajim.AVATAR_PATH
- vars['MY_EMOTS_PATH'] = gajim.MY_EMOTS_PATH
- vars['MY_ICONSETS_PATH'] = gajim.MY_ICONSETS_PATH
- vars['MY_MOOD_ICONSETS_PATH'] = gajim.MY_MOOD_ICONSETS_PATH
- vars['MY_ACTIVITY_ICONSETS_PATH'] = gajim.MY_ACTIVITY_ICONSETS_PATH
- from common import configpaths
- MY_DATA = configpaths.gajimpaths['MY_DATA']
- MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
- MY_CACHE = configpaths.gajimpaths['MY_CACHE']
-
- if os.path.exists(LOG_DB_PATH):
- # File already exists
- return
-
- if os.name == 'nt':
- try:
- OLD_LOG_DB_FOLDER = os.path.join(os.environ['appdata'], 'Gajim')
- except KeyError:
- OLD_LOG_DB_FOLDER = '.'
- else:
- OLD_LOG_DB_FOLDER = os.path.expanduser('~/.gajim')
- if not os.path.exists(OLD_LOG_DB_FOLDER):
- return
- OLD_LOG_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'logs.db')
- OLD_CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'cache.db')
- vars['OLD_VCARD_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'vcards')
- vars['OLD_AVATAR_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'avatars')
- vars['OLD_MY_EMOTS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'emoticons')
- vars['OLD_MY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'iconsets')
- vars['OLD_MY_MOOD_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'moods')
- vars['OLD_MY_ACTIVITY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER,
- 'activities')
- OLD_CONFIG_FILES = []
- OLD_DATA_FILES = []
- for f in os.listdir(OLD_LOG_DB_FOLDER):
- if f == 'config' or f.startswith('config.'):
- OLD_CONFIG_FILES.append(f)
- if f == 'secrets' or f.startswith('secrets.'):
- OLD_DATA_FILES.append(f)
- if f == 'cacerts.pem':
- OLD_DATA_FILES.append(f)
-
- if not os.path.exists(OLD_LOG_DB_PATH):
- return
-
- if not os.path.exists(OLD_CACHE_DB_PATH):
- # split database
- split_db()
-
- to_move = {}
- to_move[OLD_LOG_DB_PATH] = LOG_DB_PATH
- to_move[OLD_CACHE_DB_PATH] = CACHE_DB_PATH
-
- for folder in ('VCARD_PATH', 'AVATAR_PATH', 'MY_EMOTS_PATH',
- 'MY_ICONSETS_PATH', 'MY_MOOD_ICONSETS_PATH', 'MY_ACTIVITY_ICONSETS_PATH'):
- src = vars['OLD_' + folder]
- dst = vars[folder]
- to_move[src] = dst
-
- # move config files
- for f in OLD_CONFIG_FILES:
- src = os.path.join(OLD_LOG_DB_FOLDER, f)
- dst = os.path.join(MY_CONFIG, f)
- to_move[src] = dst
-
- # Move data files (secrets, cacert.pem)
- for f in OLD_DATA_FILES:
- src = os.path.join(OLD_LOG_DB_FOLDER, f)
- dst = os.path.join(MY_DATA, f)
- to_move[src] = dst
-
- for src, dst in to_move.items():
- if os.path.exists(dst):
- continue
- if not os.path.exists(src):
- continue
- print(_('moving %s to %s') % (src, dst))
- shutil.move(src, dst)
- gajim.logger.init_vars()
- gajim.logger.attach_cache_database()
-
-def check_and_possibly_create_paths():
- LOG_DB_PATH = logger.LOG_DB_PATH
- LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH)
-
- CACHE_DB_PATH = logger.CACHE_DB_PATH
- CACHE_DB_FOLDER, CACHE_DB_FILE = os.path.split(CACHE_DB_PATH)
-
- VCARD_PATH = gajim.VCARD_PATH
- AVATAR_PATH = gajim.AVATAR_PATH
- from common import configpaths
- MY_DATA = configpaths.gajimpaths['MY_DATA']
- MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
- MY_CACHE = configpaths.gajimpaths['MY_CACHE']
- XTLS_CERTS = configpaths.gajimpaths['MY_PEER_CERTS']
- LOCAL_XTLS_CERTS = configpaths.gajimpaths['MY_CERT']
-
- PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR
-
- if not os.path.exists(MY_DATA):
- create_path(MY_DATA)
- elif os.path.isfile(MY_DATA):
- print(_('%s is a file but it should be a directory') % MY_DATA)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(MY_CONFIG):
- create_path(MY_CONFIG)
- elif os.path.isfile(MY_CONFIG):
- print(_('%s is a file but it should be a directory') % MY_CONFIG)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(MY_CACHE):
- create_path(MY_CACHE)
- elif os.path.isfile(MY_CACHE):
- print(_('%s is a file but it should be a directory') % MY_CACHE)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(VCARD_PATH):
- create_path(VCARD_PATH)
- elif os.path.isfile(VCARD_PATH):
- print(_('%s is a file but it should be a directory') % VCARD_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(AVATAR_PATH):
- create_path(AVATAR_PATH)
- elif os.path.isfile(AVATAR_PATH):
- print(_('%s is a file but it should be a directory') % AVATAR_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(LOG_DB_FOLDER):
- create_path(LOG_DB_FOLDER)
- elif os.path.isfile(LOG_DB_FOLDER):
- print(_('%s is a file but it should be a directory') % LOG_DB_FOLDER)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(PLUGINS_CONFIG_PATH):
- create_path(PLUGINS_CONFIG_PATH)
- elif os.path.isfile(PLUGINS_CONFIG_PATH):
- print(_('%s is a file but it should be a directory') % PLUGINS_CONFIG_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(CACHE_DB_FOLDER):
- create_path(CACHE_DB_FOLDER)
- elif os.path.isfile(CACHE_DB_FOLDER):
- print(_('%s is a file but it should be a directory') % CACHE_DB_FOLDER)
- print(_('Gajim will now exit'))
- sys.exit()
-
- check_and_possibly_move_config()
-
- if not os.path.exists(LOG_DB_PATH):
- if os.path.exists(CACHE_DB_PATH):
- os.remove(CACHE_DB_PATH)
- create_log_db()
- gajim.logger.init_vars()
- elif os.path.isdir(LOG_DB_PATH):
- print(_('%s is a directory but should be a file') % LOG_DB_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(CACHE_DB_PATH):
- create_cache_db()
- gajim.logger.attach_cache_database()
- elif os.path.isdir(CACHE_DB_PATH):
- print(_('%s is a directory but should be a file') % CACHE_DB_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(XTLS_CERTS):
- create_path(XTLS_CERTS)
- if not os.path.exists(LOCAL_XTLS_CERTS):
- create_path(LOCAL_XTLS_CERTS)
-
-def create_path(directory):
- head, tail = os.path.split(directory)
- if not os.path.exists(head):
- create_path(head)
- if os.path.exists(directory):
- return
- print(('creating %s directory') % directory)
- os.mkdir(directory, 0o700)
diff --git a/src/common/commands.py b/src/common/commands.py
deleted file mode 100644
index 0eeb57c8e..000000000
--- a/src/common/commands.py
+++ /dev/null
@@ -1,495 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/commands.py
-##
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.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/>.
-##
-
-import nbxmpp
-from common import helpers
-from common import dataforms
-from common import gajim
-from common.connection_handlers_events import MessageOutgoingEvent
-
-import logging
-log = logging.getLogger('gajim.c.commands')
-
-class AdHocCommand:
- commandnode = 'command'
- commandname = 'The Command'
- commandfeatures = (nbxmpp.NS_DATA,)
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- This returns True if that command should be visible and invokable for
- others
-
- samejid - True when command is invoked by an entity with the same bare
- jid.
- """
- return True
-
- def __init__(self, conn, jid, sessionid):
- self.connection = conn
- self.jid = jid
- self.sessionid = sessionid
-
- def buildResponse(self, request, status = 'executing', defaultaction = None,
- actions = None):
- assert status in ('executing', 'completed', 'canceled')
-
- response = request.buildReply('result')
- cmd = response.getTag('command', namespace=nbxmpp.NS_COMMANDS)
- cmd.setAttr('sessionid', self.sessionid)
- cmd.setAttr('node', self.commandnode)
- cmd.setAttr('status', status)
- if defaultaction is not None or actions is not None:
- if defaultaction is not None:
- assert defaultaction in ('cancel', 'execute', 'prev', 'next',
- 'complete')
- attrs = {'action': defaultaction}
- else:
- attrs = {}
-
- cmd.addChild('actions', attrs, actions)
- return response, cmd
-
- def badRequest(self, stanza):
- self.connection.connection.send(nbxmpp.Error(stanza,
- nbxmpp.NS_STANZAS + ' bad-request'))
-
- def cancel(self, request):
- response = self.buildResponse(request, status = 'canceled')[0]
- self.connection.connection.send(response)
- return False # finish the session
-
-class ChangeStatusCommand(AdHocCommand):
- commandnode = 'change-status'
- commandname = _('Change status information')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- # first query...
- response, cmd = self.buildResponse(request, defaultaction = 'execute',
- actions = ['execute'])
-
- cmd.addChild(node = dataforms.SimpleDataForm(
- title = _('Change status'),
- instructions = _('Set the presence type and description'),
- fields = [
- dataforms.Field('list-single',
- var = 'presence-type',
- label = 'Type of presence:',
- options = [
- ('chat', _('Free for chat')),
- ('online', _('Online')),
- ('away', _('Away')),
- ('xa', _('Extended away')),
- ('dnd', _('Do not disturb')),
- ('offline', _('Offline - disconnect'))],
- value = 'online',
- required = True),
- dataforms.Field('text-multi',
- var = 'presence-desc',
- label = _('Presence description:'))]))
-
- self.connection.connection.send(response)
-
- # for next invocation
- self.execute = self.changestatus
-
- return True # keep the session
-
- def changestatus(self, request):
- # check if the data is correct
- try:
- form = dataforms.SimpleDataForm(extend = request.getTag('command').\
- getTag('x'))
- except Exception:
- self.badRequest(request)
- return False
-
- try:
- presencetype = form['presence-type'].value
- if not presencetype in \
- ('chat', 'online', 'away', 'xa', 'dnd', 'offline'):
- self.badRequest(request)
- return False
- except Exception: # KeyError if there's no presence-type field in form or
- # AttributeError if that field is of wrong type
- self.badRequest(request)
- return False
-
- try:
- presencedesc = form['presence-desc'].value
- except Exception: # same exceptions as in last comment
- presencedesc = ''
-
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('The status has been changed.'))
-
- # if going offline, we need to push response so it won't go into
- # queue and disappear
- self.connection.connection.send(response, now = presencetype == 'offline')
-
- # send new status
- gajim.interface.roster.send_status(self.connection.name, presencetype,
- presencedesc)
-
- return False # finish the session
-
-def find_current_groupchats(account):
- import message_control
- rooms = []
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\
- values():
- acct = gc_control.account
- # check if account is the good one
- if acct != account:
- continue
- room_jid = gc_control.room_jid
- nick = gc_control.nick
- if room_jid in gajim.gc_connected[acct] and \
- gajim.gc_connected[acct][room_jid]:
- rooms.append((room_jid, nick,))
- return rooms
-
-
-class LeaveGroupchatsCommand(AdHocCommand):
- commandnode = 'leave-groupchats'
- commandname = _('Leave Groupchats')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- # first query...
- response, cmd = self.buildResponse(request, defaultaction = 'execute',
- actions=['execute'])
- options = []
- account = self.connection.name
- for gc in find_current_groupchats(account):
- options.append(('%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \
- {'nickname': gc[1], 'room_jid': gc[0]}))
- if not len(options):
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('You have not joined a groupchat.'))
-
- self.connection.connection.send(response)
- return False
-
- cmd.addChild(node=dataforms.SimpleDataForm(
- title = _('Leave Groupchats'),
- instructions = _('Choose the groupchats you want to leave'),
- fields=[
- dataforms.Field('list-multi',
- var = 'groupchats',
- label = _('Groupchats'),
- options = options,
- required = True)]))
-
- self.connection.connection.send(response)
-
- # for next invocation
- self.execute = self.leavegroupchats
-
- return True # keep the session
-
- def leavegroupchats(self, request):
- # check if the data is correct
- try:
- form = dataforms.SimpleDataForm(extend = request.getTag('command').\
- getTag('x'))
- except Exception:
- self.badRequest(request)
- return False
-
- try:
- gc = form['groupchats'].values
- except Exception: # KeyError if there's no groupchats in form
- self.badRequest(request)
- return False
- account = self.connection.name
- try:
- for room_jid in gc:
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- account)
- if not gc_control:
- gc_control = gajim.interface.minimized_controls[account]\
- [room_jid]
- gc_control.shutdown()
- gajim.interface.roster.remove_groupchat(room_jid, account)
- continue
- gc_control.parent_win.remove_tab(gc_control, None, force = True)
- except Exception: # KeyError if there's no such room opened
- self.badRequest(request)
- return False
- response, cmd = self.buildResponse(request, status = 'completed')
- note = _('You left the following groupchats:')
- for room_jid in gc:
- note += '\n\t' + room_jid
- cmd.addChild('note', {}, note)
-
- self.connection.connection.send(response)
- return False
-
-
-class ForwardMessagesCommand(AdHocCommand):
- # http://www.xmpp.org/extensions/xep-0146.html#forward
- commandnode = 'forward-messages'
- commandname = _('Forward unread messages')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- account = self.connection.name
- # Forward messages
- events = gajim.events.get_events(account, types=['chat', 'normal',
- 'printed_chat'])
- j, resource = gajim.get_room_and_nick_from_fjid(self.jid)
- for jid in events:
- for event in events[jid]:
- ev_typ = event.type_
- if ev_typ == 'printed_chat':
- ev_typ = 'chat'
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=account, jid=j, message=event.message, type_=ev_typ,
- subject=event.subject, resource=resource, forward_from=jid,
- delayed=event.time_))
-
- # Inform other client of completion
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('All unread messages have been forwarded.'))
-
- self.connection.connection.send(response)
-
- return False # finish the session
-
-class FwdMsgThenDisconnectCommand(AdHocCommand):
- commandnode = 'fwd-msd-disconnect'
- commandname = _('Forward unread message then disconnect')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- account = self.connection.name
- # Forward messages
- events = gajim.events.get_events(account, types=['chat', 'normal'])
- j, resource = gajim.get_room_and_nick_from_fjid(self.jid)
- for jid in events:
- for event in events[jid]:
- ev_typ = event.type_
- if ev_typ == 'printed_chat':
- ev_typ = 'chat'
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=account, jid=j, message=event.message, type_=ev_typ,
- subject=event.subject, resource=resource, forward_from=jid,
- delayed=event.time_, now=True))
-
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('The status has been changed.'))
-
- # if going offline, we need to push response so it won't go into
- # queue and disappear
- self.connection.connection.send(response, now = True)
-
- # send new status
- gajim.interface.roster.send_status(self.connection.name, 'offline', '')
- # finish the session
- return False
-
-class ConnectionCommands:
- """
- This class depends on that it is a part of Connection() class
- """
-
- def __init__(self):
- # a list of all commands exposed: node -> command class
- self.__commands = {}
- if gajim.config.get('remote_commands'):
- for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand,
- LeaveGroupchatsCommand, FwdMsgThenDisconnectCommand):
- self.__commands[cmdobj.commandnode] = cmdobj
-
- # a list of sessions; keys are tuples (jid, sessionid, node)
- self.__sessions = {}
-
- def getOurBareJID(self):
- return gajim.get_jid_from_account(self.name)
-
- def isSameJID(self, jid):
- """
- Test if the bare jid given is the same as our bare jid
- """
- return nbxmpp.JID(jid).getStripped() == self.getOurBareJID()
-
- def commandListQuery(self, con, iq_obj):
- iq = iq_obj.buildReply('result')
- jid = helpers.get_full_jid_from_iq(iq_obj)
- q = iq.getTag('query')
- # buildReply don't copy the node attribute. Re-add it
- q.setAttr('node', nbxmpp.NS_COMMANDS)
-
- for node, cmd in self.__commands.items():
- if cmd.isVisibleFor(self.isSameJID(jid)):
- q.addChild('item', {
- # TODO: find the jid
- 'jid': self.getOurBareJID() + '/' + self.server_resource,
- 'node': node,
- 'name': cmd.commandname})
-
- self.connection.send(iq)
-
- def commandInfoQuery(self, con, iq_obj):
- """
- Send disco#info result for query for command (JEP-0050, example 6.).
- Return True if the result was sent, False if not
- """
- try:
- jid = helpers.get_full_jid_from_iq(iq_obj)
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % iq_obj.getFrom())
- return
- node = iq_obj.getTagAttr('query', 'node')
-
- if node not in self.__commands: return False
-
- cmd = self.__commands[node]
- if cmd.isVisibleFor(self.isSameJID(jid)):
- iq = iq_obj.buildReply('result')
- q = iq.getTag('query')
- q.addChild('identity', attrs = {'type': 'command-node',
- 'category': 'automation',
- 'name': cmd.commandname})
- q.addChild('feature', attrs = {'var': nbxmpp.NS_COMMANDS})
- for feature in cmd.commandfeatures:
- q.addChild('feature', attrs = {'var': feature})
-
- self.connection.send(iq)
- return True
-
- return False
-
- def commandItemsQuery(self, con, iq_obj):
- """
- Send disco#items result for query for command. Return True if the result
- was sent, False if not.
- """
- jid = helpers.get_full_jid_from_iq(iq_obj)
- node = iq_obj.getTagAttr('query', 'node')
-
- if node not in self.__commands: return False
-
- cmd = self.__commands[node]
- if cmd.isVisibleFor(self.isSameJID(jid)):
- iq = iq_obj.buildReply('result')
- self.connection.send(iq)
- return True
-
- return False
-
- def _CommandExecuteCB(self, con, iq_obj):
- jid = helpers.get_full_jid_from_iq(iq_obj)
-
- cmd = iq_obj.getTag('command')
- if cmd is None: return
-
- node = cmd.getAttr('node')
- if node is None: return
-
- sessionid = cmd.getAttr('sessionid')
- if sessionid is None:
- # we start a new command session... only if we are visible for the jid
- # and command exist
- if node not in self.__commands.keys():
- self.connection.send(
- nbxmpp.Error(iq_obj, nbxmpp.NS_STANZAS + ' item-not-found'))
- raise nbxmpp.NodeProcessed
-
- newcmd = self.__commands[node]
- if not newcmd.isVisibleFor(self.isSameJID(jid)):
- return
-
- # generate new sessionid
- sessionid = self.connection.getAnID()
-
- # create new instance and run it
- obj = newcmd(conn = self, jid = jid, sessionid = sessionid)
- rc = obj.execute(iq_obj)
- if rc:
- self.__sessions[(jid, sessionid, node)] = obj
- raise nbxmpp.NodeProcessed
- else:
- # the command is already running, check for it
- magictuple = (jid, sessionid, node)
- if magictuple not in self.__sessions:
- # we don't have this session... ha!
- return
-
- action = cmd.getAttr('action')
- obj = self.__sessions[magictuple]
-
- try:
- if action == 'cancel':
- rc = obj.cancel(iq_obj)
- elif action == 'prev':
- rc = obj.prev(iq_obj)
- elif action == 'next':
- rc = obj.next(iq_obj)
- elif action == 'execute' or action is None:
- rc = obj.execute(iq_obj)
- elif action == 'complete':
- rc = obj.complete(iq_obj)
- else:
- # action is wrong. stop the session, send error
- raise AttributeError
- except AttributeError:
- # the command probably doesn't handle invoked action...
- # stop the session, return error
- del self.__sessions[magictuple]
- return
-
- # delete the session if rc is False
- if not rc:
- del self.__sessions[magictuple]
-
- raise nbxmpp.NodeProcessed
diff --git a/src/common/config.py b/src/common/config.py
deleted file mode 100644
index fe254558d..000000000
--- a/src/common/config.py
+++ /dev/null
@@ -1,826 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/config.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005 Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Alex Mauer <hawke AT hawkesnest.net>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 James Newton <redshodan AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-
-import re
-from common import defs
-from gi.repository import GLib
-from enum import IntEnum, unique
-
-@unique
-class Option(IntEnum):
- TYPE = 0
- VAL = 1
- DESC = 2
- # If Option.RESTART is True - we need restart to use our changed option
- # Option.DESC also should be there
- RESTART = 3
-
-opt_int = [ 'integer', 0 ]
-opt_str = [ 'string', 0 ]
-opt_bool = [ 'boolean', 0 ]
-opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()$' ]
-opt_one_window_types = ['never', 'always', 'always_with_roster', 'peracct', 'pertype']
-opt_show_roster_on_startup = ['always', 'never', 'last_state']
-opt_treat_incoming_messages = ['', 'chat', 'normal']
-
-class Config:
-
- DEFAULT_ICONSET = 'dcraven'
- DEFAULT_MOOD_ICONSET = 'default'
- DEFAULT_ACTIVITY_ICONSET = 'default'
- DEFAULT_OPENWITH = 'xdg-open'
- DEFAULT_BROWSER = 'firefox'
- DEFAULT_MAILAPP = 'mozilla-thunderbird -compose'
- DEFAULT_FILE_MANAGER = 'xffm'
-
- __options = ({
- # name: [ type, default_value, help_string ]
- 'verbose': [ opt_bool, False, '', True ],
- 'autopopup': [ opt_bool, False ],
- 'notify_on_signin': [ opt_bool, True ],
- 'notify_on_signout': [ opt_bool, False ],
- 'notify_on_new_message': [ opt_bool, True ],
- 'autopopupaway': [ opt_bool, False ],
- 'autopopup_chat_opened': [ opt_bool, False, _('Show desktop notification even when a chat window is opened for this contact and does not have focus') ],
- 'sounddnd': [ opt_bool, False, _('Play sound when user is busy')],
- 'use_notif_daemon': [ opt_bool, True, _('Use D-Bus and Notification-Daemon to show notifications') ],
- 'showoffline': [ opt_bool, False ],
- 'show_only_chat_and_online': [ opt_bool, False, _('Show only online and free for chat contacts in roster.')],
- 'show_transports_group': [ opt_bool, True ],
- 'autoaway': [ opt_bool, True ],
- 'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ],
- 'autoaway_message': [ opt_str, _('$S (Away as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoawaytime.') ],
- 'autoxa': [ opt_bool, True ],
- 'autoxatime': [ opt_int, 15, _('Time in minutes, after which your status changes to not available.') ],
- 'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxatime.') ],
- 'ask_online_status': [ opt_bool, False ],
- 'ask_offline_status': [ opt_bool, False ],
- 'trayicon': [opt_str, 'always', _("When to show notification area icon. Can be 'never', 'on_event', 'always'."), False],
- 'allow_hide_roster': [opt_bool, False, _("Allow to hide the roster window even if the tray icon is not shown."), False],
- 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ],
- 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ],
- 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ],
- 'use_transports_iconsets': [ opt_bool, True, '', True ],
- 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ],
- 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ],
- 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ],
- 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ],
- 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ],
- 'markedmsgcolor': [ opt_color, '#ff8080', '', True ],
- 'urlmsgcolor': [ opt_color, '#204a87', '', True ],
- 'notif_signin_color': [ opt_color, '#32CD32', _('Contact signed in notification color.') ], # limegreen
- 'notif_signout_color': [ opt_color, '#FF0000', _('Contact signout notification color') ], # red
- 'notif_message_color': [ opt_color, '#1E90FF', _('New message/email notification color.') ], # dodgerblue
- 'notif_ftrequest_color': [ opt_color, '#F0E68C', _('File transfer request notification color.') ], # khaki
- 'notif_fterror_color': [ opt_color, '#B22222', _('File transfer error notification color.') ], # firebrick
- 'notif_ftcomplete_color': [ opt_color, '#9ACD32', _('File transfer complete or stopped notification color.') ], # yellowgreen
- 'notif_invite_color': [ opt_color, '#D2B48C', _('Groupchat invitation notification color') ], # tan1
- 'notif_status_color': [ opt_color, '#D8BFD8', _('Background color of status changed notification') ], # thistle2
- 'notif_other_color': [ opt_color, '#FFFFFF', _('Other dialogs color.') ], # white
- 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ],
- 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ],
- 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ],
- 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ],
- 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ],
- 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ],
- 'roster_theme': [ opt_str, _('default'), '', True ],
- 'mergeaccounts': [ opt_bool, False, '', True ],
- 'sort_by_show_in_roster': [ opt_bool, True, '', True ],
- 'sort_by_show_in_muc': [ opt_bool, False, '', True ],
- 'use_speller': [ opt_bool, False, ],
- 'ignore_incoming_xhtml': [ opt_bool, False, ],
- 'speller_language': [ opt_str, '', _('Language used by speller')],
- 'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
- 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ],
- 'emoticons_theme': [opt_str, 'static', '', True ],
- 'ascii_formatting': [ opt_bool, True,
- _('Treat * / _ pairs as possible formatting characters.'), True],
- 'show_ascii_formatting_chars': [ opt_bool, True, _('If True, do not '
- 'remove */_ . So *abc* will be bold but with * * not removed.')],
- 'rst_formatting_outgoing_messages': [ opt_bool, False,
- _('Uses ReStructured text markup to send HTML, plus ascii formatting if selected. For syntax, see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html (If you want to use this, install docutils)')],
- 'sounds_on': [ opt_bool, True ],
- # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only
- 'soundplayer': [ opt_str, '' ],
- 'openwith': [ opt_str, DEFAULT_OPENWITH ],
- 'custombrowser': [ opt_str, DEFAULT_BROWSER ],
- 'custommailapp': [ opt_str, DEFAULT_MAILAPP ],
- 'custom_file_manager': [ opt_str, DEFAULT_FILE_MANAGER ],
- 'gc-hpaned-position': [opt_int, 430],
- 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')],
- 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')],
- 'msgwin-max-state': [opt_bool, False],
- 'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'msgwin-width': [opt_int, 500],
- 'msgwin-height': [opt_int, 440],
- 'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'chat-msgwin-width': [opt_int, 480],
- 'chat-msgwin-height': [opt_int, 440],
- 'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'gc-msgwin-width': [opt_int, 600],
- 'gc-msgwin-height': [opt_int, 440],
- 'single-msg-x-position': [opt_int, 0],
- 'single-msg-y-position': [opt_int, 0],
- 'single-msg-width': [opt_int, 400],
- 'single-msg-height': [opt_int, 280],
- 'save-roster-position': [opt_bool, True, _('If True, Gajim will save roster position when hiding roster, and restore it when showing roster.')],
- 'roster_x-position': [ opt_int, 0 ],
- 'roster_y-position': [ opt_int, 0 ],
- 'roster_width': [ opt_int, 200 ],
- 'roster_height': [ opt_int, 400 ],
- 'roster_hpaned_position': [opt_int, 200],
- 'roster_on_the_right': [opt_bool, False, _('Place the roster on the right in single window mode'), True],
- 'history_window_width': [ opt_int, 650 ],
- 'history_window_height': [ opt_int, 450 ],
- 'history_window_x-position': [ opt_int, 0 ],
- 'history_window_y-position': [ opt_int, 0 ],
- 'latest_disco_addresses': [ opt_str, '' ],
- 'recently_groupchat': [ opt_str, '' ],
- 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ],
- 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ],
- 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ],
- 'notify_on_new_gmail_email': [ opt_bool, True ],
- 'notify_on_new_gmail_email_extra': [ opt_bool, False ],
- 'notify_on_new_gmail_email_command': [ opt_str, '', _('Specify the command to run when new mail arrives, e.g.: /usr/bin/getmail -q') ],
- 'use_gpg_agent': [ opt_bool, False ],
- 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')],
- 'restore_lines': [opt_int, 4, _('Amount of previous messages to include when reopening a chat')],
- 'restore_timeout': [opt_int, 60, _('How many minutes should last lines from previous conversation last.')],
- 'muc_restore_lines': [opt_int, 20, _('How many lines to request from server when entering a groupchat. -1 means no limit')],
- 'muc_restore_timeout': [opt_int, 60, _('Minutes of backlog to request when entering a groupchat. -1 means no limit')],
- 'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to autorejoin to a conference you are being disconnected from. Set to 0 to disable autorejoining.')],
- 'muc_autorejoin_on_kick': [opt_bool, False, _('Should autorejoin be activated when we are being kicked from a conference?')],
- 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')],
- 'last_roster_visible': [opt_bool, True],
- 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')],
- 'version': [ opt_str, defs.version ], # which version created the config
- 'search_engine': [opt_str, 'https://www.google.com/search?&q=%s&sourceid=gajim'],
- 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")],
- 'always_english_wikipedia': [opt_bool, False],
- 'always_english_wiktionary': [opt_bool, True],
- 'remote_control': [opt_bool, False, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
- 'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')],
- 'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')],
- 'autodetect_browser_mailer': [opt_bool, True, '', True],
- 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')],
- 'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
- 'confirm_close_muc_rooms': [opt_str, '', _('Always ask for confirmation before closing groupchats with any of the JIDs on this space separated list.')],
- 'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask for confirmation before closing groupchats with any of the JIDs on this space separated list.')],
- 'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are controls that can lose data (chat, private chat, groupchat that will not be minimized)')],
- 'notify_on_file_complete': [opt_bool, True],
- 'file_transfers_port': [opt_int, 28011],
- 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of hosts that we send, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')],
- 'conversation_font': [opt_str, ''],
- 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')],
- 'notify_on_all_muc_messages': [opt_bool, False],
- 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the notification area.')],
- 'trayicon_blink': [opt_bool, True, _('If False, Gajim will display a static event icon instead of the blinking status icon in the notification area when notifying on event.')],
- 'last_save_dir': [opt_str, ''],
- 'last_send_dir': [opt_str, ''],
- 'last_emoticons_dir': [opt_str, ''],
- 'last_sounds_dir': [opt_str, ''],
- 'tabs_position': [opt_str, 'top'],
- 'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')],
- 'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')],
- 'tabs_close_button': [opt_bool, True, _('Show close button in tab?')],
- 'esession_modp': [opt_str, '15,16,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')],
- 'chat_avatar_width': [opt_int, 52],
- 'chat_avatar_height': [opt_int, 52],
- 'roster_avatar_width': [opt_int, 32],
- 'roster_avatar_height': [opt_int, 32],
- 'tooltip_avatar_width': [opt_int, 125],
- 'tooltip_avatar_height': [opt_int, 125],
- 'tooltip_status_online_color': [opt_color, '#73D216'],
- 'tooltip_status_free_for_chat_color': [opt_color, '#3465A4'],
- 'tooltip_status_away_color': [opt_color, '#EDD400'],
- 'tooltip_status_busy_color': [opt_color, '#F57900'],
- 'tooltip_status_na_color': [opt_color, '#CC0000'],
- 'tooltip_status_offline_color': [opt_color, '#555753'],
- 'tooltip_affiliation_none_color': [opt_color, '#555753'],
- 'tooltip_affiliation_member_color': [opt_color, '#73D216'],
- 'tooltip_affiliation_administrator_color': [opt_color, '#F57900'],
- 'tooltip_affiliation_owner_color': [opt_color, '#CC0000'],
- 'tooltip_account_name_color': [opt_color, '#888A85'],
- 'tooltip_idle_color': [opt_color, '#888A85'],
- 'vcard_avatar_width': [opt_int, 200],
- 'vcard_avatar_height': [opt_int, 200],
- 'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')],
- 'notification_position_x': [opt_int, -1],
- 'notification_position_y': [opt_int, -1],
- 'notification_avatar_width': [opt_int, 48],
- 'notification_avatar_height': [opt_int, 48],
- 'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')],
- 'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if notification icon is used.')],
- 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')],
- 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True],
- 'show_avatars_in_roster': [opt_bool, True, '', True],
- 'show_mood_in_roster': [opt_bool, True, '', True],
- 'show_activity_in_roster': [opt_bool, True, '', True],
- 'show_tunes_in_roster': [opt_bool, True, '', True],
- 'show_location_in_roster': [opt_bool, True, '', True],
- 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True],
- 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')],
- 'print_status_in_chats': [opt_bool, False, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
- 'print_status_in_muc': [opt_str, 'none', _('Can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')],
- 'log_contact_status_changes': [opt_bool, False],
- 'log_xhtml_messages': [opt_bool, False, _('Log XHTML messages instead of plain text messages.')],
- 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')],
- 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')],
- 'restored_messages_color': [opt_color, '#555753'],
- 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')],
- 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')],
- 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')],
- 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')],
- 'notification_timeout': [opt_int, 5],
- 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')],
- 'one_message_window': [opt_str, 'always',
-#always, never, peracct, pertype should not be translated
- _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g. chats vs. groupchats) is sent to a specific window.')],
- 'show_roster_on_startup':[opt_str, 'always', _('Show roster on startup.\n\'always\' - Always show roster.\n\'never\' - Never show roster.\n\'last_state\' - Restore the last state roster.')],
- 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')],
- 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')],
- 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')],
- 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')],
- 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')],
- 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
- 'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')],
- 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')],
- 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ],
- 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
- 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
- 'confirm_block': [ opt_str, '', _('Should we show the confirm block contact dialog or not? Empty string means we never show the dialog.')],
- 'confirm_custom_status': [ opt_str, '', _('Should we show the confirm custom status dialog or not? Empty string means we never show the dialog.')],
- 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')],
- 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')],
- 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')],
- 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')],
- 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')],
- 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')],
- 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to notification icon.')],
- 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')],
- 'uri_schemes': [opt_str, 'aaa:// aaas:// acap:// cap:// cid: crid:// data: dav: dict:// dns: fax: file:/ ftp:// geo: go: gopher:// h323: http:// https:// iax: icap:// im: imap:// info: ipp:// iris: iris.beep: iris.xpc: iris.xpcs: iris.lwz: ldap:// mid: modem: msrp:// msrps:// mtqp:// mupdate:// news: nfs:// nntp:// opaquelocktoken: pop:// pres: prospero:// rtsp:// service: shttp:// sip: sips: sms: snmp:// soap.beep:// soap.beeps:// tag: tel: telnet:// tftp:// thismessage:/ tip:// tv: urn:// vemmi:// xmlrpc.beep:// xmlrpc.beeps:// z39.50r:// z39.50s:// about: apt: cvs:// daap:// ed2k:// feed: fish:// git:// iax2: irc:// ircs:// ldaps:// magnet: mms:// rsync:// ssh:// svn:// sftp:// smb:// webcal:// aesgcm://', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True],
- 'ask_offline_status_on_connection': [ opt_bool, False, _('Request offline status messages from all contacts upon connecting. WARNING: This causes a lot of requests to be sent!') ],
- 'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')],
- 'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True],
- 'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
- 'audio_output_device': [opt_str, 'autoaudiosink'],
- 'video_input_device': [opt_str, 'autovideosrc'],
- 'video_output_device': [opt_str, 'autovideosink'],
- 'video_framerate': [opt_str, '', _('Optionally fix jingle output video framerate. Example: 10/1 or 25/2')],
- 'video_size': [opt_str, '', _('Optionally resize jingle output video. Example: 320x240')],
- 'video_see_self': [opt_bool, True, _('If True, You will also see your webcam')],
- 'audio_input_volume': [opt_int, 50],
- 'audio_output_volume': [opt_int, 50],
- 'use_stun_server': [opt_bool, False, _('If True, Gajim will try to use a STUN server when using Jingle. The one in "stun_server" option, or the one given by the XMPP server.')],
- 'stun_server': [opt_str, '', _('STUN server to use when using Jingle')],
- 'show_affiliation_in_groupchat': [opt_bool, True, _('If True, Gajim will show affiliation of groupchat occupants by adding a colored square to the status icon')],
- 'global_proxy': [opt_str, '', _('Proxy used for all outgoing connections if the account does not have a specific proxy configured')],
- 'ignore_incoming_attention': [opt_bool, False, _('If True, Gajim will ignore incoming attention requestd ("wizz").')],
- 'remember_opened_chat_controls': [ opt_bool, True, _('If enabled, Gajim will reopen chat windows that were opened last time Gajim was closed.')],
- 'positive_184_ack': [ opt_bool, False, _('If enabled, Gajim will show an icon to show that sent message has been received by your contact')],
- 'show_avatar_in_tabs': [ opt_bool, False, _('Show a mini avatar in chat window tabs and in window icon')],
- 'use_keyring': [opt_bool, True, _('If True, Gajim will use the Systems Keyring to store account passwords.')],
- 'pgp_encoding': [ opt_str, '', _('Sets the encoding used by python-gnupg'), True],
- 'remote_commands': [opt_bool, False, _('If True, Gajim will execute XEP-0146 Commands.')],
- }, {})
-
- __options_per_key = {
- 'accounts': ({
- 'name': [ opt_str, '', '', True ],
- 'hostname': [ opt_str, '', '', True ],
- 'anonymous_auth': [ opt_bool, False ],
- 'client_cert': [ opt_str, '', '', True ],
- 'client_cert_encrypted': [ opt_bool, False, '', False ],
- 'savepass': [ opt_bool, False ],
- 'password': [ opt_str, '' ],
- 'resource': [ opt_str, 'gajim.$rand', '', True ],
- 'priority': [ opt_int, 5, '', True ],
- 'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ],
- 'autopriority_online': [ opt_int, 50],
- 'autopriority_chat': [ opt_int, 50],
- 'autopriority_away': [ opt_int, 40],
- 'autopriority_xa': [ opt_int, 30],
- 'autopriority_dnd': [ opt_int, 20],
- 'autopriority_invisible': [ opt_int, 10],
- 'autoconnect': [ opt_bool, False, '', True ],
- 'autoconnect_as': [ opt_str, 'online', _('Status used to autoconnect as. Can be online, chat, away, xa, dnd, invisible. NOTE: this option is used only if restore_last_status is disabled'), True ],
- 'restore_last_status': [ opt_bool, False, _('If enabled, restore the last status that was used.') ],
- 'autoreconnect': [ opt_bool, True ],
- 'autoauth': [ opt_bool, False, _('If True, Contacts requesting authorization will be automatically accepted.')],
- 'active': [ opt_bool, True, _('If False, this account will be disabled and will not appear in roster window.'), True],
- 'proxy': [ opt_str, '', '', True ],
- 'keyid': [ opt_str, '', '', True ],
- 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
- 'keyname': [ opt_str, '', '', True ],
- 'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.'), True],
- 'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session when possible?')],
- #keep tls, ssl and plain lowercase
- 'connection_types': [ opt_str, 'tls', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')],
- 'tls_version': [ opt_str, '1.0', '' ],
- 'cipher_list': [ opt_str, 'HIGH:!aNULL:RC4-SHA', '' ],
- 'authentication_mechanisms': [ opt_str, '', _('List (space separated) of authentication mechanisms to try. Can contain ANONYMOUS, EXTERNAL, GSSAPI, SCRAM-SHA-1-PLUS, SCRAM-SHA-1, DIGEST-MD5, PLAIN, X-MESSENGER-OAUTH2 or XEP-0078') ],
- 'action_when_plaintext_connection': [ opt_str, 'warn', _('Show a warning dialog before sending password on an plaintext connection. Can be \'warn\', \'connect\', \'disconnect\'') ],
- 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ],
- 'warn_when_insecure_password': [ opt_bool, True, _('Show a warning dialog before sending PLAIN password over a plain connection.') ],
- 'ssl_fingerprint_sha1': [ opt_str, '', '', True ],
- 'ssl_fingerprint_sha256': [ opt_str, '', '', True ],
- 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ],
- 'use_srv': [ opt_bool, True, '', True ],
- 'use_custom_host': [ opt_bool, False, '', True ],
- 'custom_port': [ opt_int, 5222, '', True ],
- 'custom_host': [ opt_str, '', '', True ],
- 'sync_with_global_status': [ opt_bool, False, ],
- 'no_log_for': [ opt_str, '', _('Space separated list of JIDs for which you do not want to store logs. You can also add account name to log nothing for this account.')],
- 'sync_logs_with_server': [ opt_bool, True, _('On startup, Gajim will download logs stored on server, provided it supports XEP-0136 or XEP-0313')],
- 'allow_no_log_for': [ opt_str, '', _('Space separated list of JIDs for which you accept to not log conversations if he does not want to.')],
- 'non_minimized_gc': [ opt_str, '' ],
- 'attached_gpg_keys': [ opt_str, '' ],
- 'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')],
- 'ping_alives_enabled': [ opt_bool, True, _('XMPP ping sent after inactivity')],
- # send keepalive every N seconds of inactivity
- 'keep_alive_every_foo_secs': [ opt_int, 55 ],
- 'ping_alive_every_foo_secs': [ opt_int, 120 ],
- 'time_for_ping_alive_answer': [ opt_int, 60, _('How many seconds to wait for the answer of ping alive packet before we try to reconnect?') ],
- # try for 1 minutes before giving up (aka. timeout after those seconds)
- 'try_connecting_for_foo_secs': [ opt_int, 60 ],
- 'http_auth': [opt_str, 'ask'], # yes, no, ask
- 'dont_ack_subscription': [opt_bool, False, _('Jabberd2 workaround')],
- # proxy65 for FT
- 'file_transfer_proxies': [opt_str, ''],
- 'use_ft_proxies': [opt_bool, False, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True],
- 'test_ft_proxies_on_startup': [opt_bool, False, _('If True, Gajim will test file transfer proxies on startup to be sure it works. Openfire\'s proxies are known to fail this test even if they work.')],
- 'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide
- 'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
- 'msgwin-width': [opt_int, 480],
- 'msgwin-height': [opt_int, 440],
- 'is_zeroconf': [opt_bool, False],
- 'last_status': [opt_str, 'online'],
- 'last_status_msg': [opt_str, ''],
- 'zeroconf_first_name': [ opt_str, '', '', True ],
- 'zeroconf_last_name': [ opt_str, '', '', True ],
- 'zeroconf_jabber_id': [ opt_str, '', '', True ],
- 'zeroconf_email': [ opt_str, '', '', True ],
- 'use_env_http_proxy': [opt_bool, False],
- 'answer_receipts': [opt_bool, True, _('Answer to receipt requests')],
- 'request_receipt': [opt_bool, True, _('Sent receipt requests')],
- 'publish_tune': [opt_bool, False],
- 'publish_location': [opt_bool, False],
- 'subscribe_mood': [opt_bool, True],
- 'subscribe_activity': [opt_bool, True],
- 'subscribe_tune': [opt_bool, True],
- 'subscribe_nick': [opt_bool, True],
- 'subscribe_location': [opt_bool, True],
- 'ignore_unknown_contacts': [ opt_bool, False ],
- 'send_os_info': [ opt_bool, True, _("Allow Gajim to send information about the operating system you are running.") ],
- 'send_time_info': [ opt_bool, True, _("Allow Gajim to send your local time.") ],
- 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')],
- 'send_idle_time': [ opt_bool, True ],
- 'roster_version': [opt_str, ''],
- 'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')],
- 'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')],
- 'enable_message_carbons': [ opt_bool, True, _('If enabled and if server supports this feature, Gajim will receive messages sent and received by other resources.')],
- 'ft_send_local_ips': [ opt_bool, True, _('If enabled, Gajim will send your local IPs so your contact can connect to your machine to transfer files.')],
- 'oauth2_refresh_token': [ opt_str, '', _('Latest token for OAuth 2.0 authentication.')],
- 'oauth2_client_id': [ opt_str, '0000000044077801', _('client_id for OAuth 2.0 authentication.')],
- 'oauth2_redirect_url': [ opt_str, 'https%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for OAuth 2.0 authentication.')],
- 'opened_chat_controls': [opt_str, '', _('Space separated list of JIDs for which we want to re-open a chat window on next startup.')],
- 'last_mam_id': [opt_str, '', _('Last MAM id we are syncronized with')],
- }, {}),
- 'statusmsg': ({
- 'message': [ opt_str, '' ],
- 'activity': [ opt_str, '' ],
- 'subactivity': [ opt_str, '' ],
- 'activity_text': [ opt_str, '' ],
- 'mood': [ opt_str, '' ],
- 'mood_text': [ opt_str, '' ],
- }, {}),
- 'defaultstatusmsg': ({
- 'enabled': [ opt_bool, False ],
- 'message': [ opt_str, '' ],
- }, {}),
- 'soundevents': ({
- 'enabled': [ opt_bool, True ],
- 'path': [ opt_str, '' ],
- }, {}),
- 'proxies': ({
- 'type': [ opt_str, 'http' ],
- 'host': [ opt_str, '' ],
- 'port': [ opt_int, 3128 ],
- 'useauth': [ opt_bool, False ],
- 'user': [ opt_str, '' ],
- 'pass': [ opt_str, '' ],
- 'bosh_uri': [ opt_str, '' ],
- 'bosh_useproxy': [ opt_bool, False ],
- 'bosh_wait': [ opt_int, 30 ],
- 'bosh_hold': [ opt_int, 2 ],
- 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ],
- 'bosh_http_pipelining': [ opt_bool, False ],
- 'bosh_wait_for_restart_response': [ opt_bool, False ],
- }, {}),
- 'themes': ({
- 'accounttextcolor': [ opt_color, 'black', '', True ],
- 'accountbgcolor': [ opt_color, 'white', '', True ],
- 'accountfont': [ opt_str, '', '', True ],
- 'accountfontattrs': [ opt_str, 'B', '', True ],
- 'grouptextcolor': [ opt_color, 'black', '', True ],
- 'groupbgcolor': [ opt_color, 'white', '', True ],
- 'groupfont': [ opt_str, '', '', True ],
- 'groupfontattrs': [ opt_str, 'I', '', True ],
- 'contacttextcolor': [ opt_color, 'black', '', True ],
- 'contactbgcolor': [ opt_color, 'white', '', True ],
- 'contactfont': [ opt_str, '', '', True ],
- 'contactfontattrs': [ opt_str, '', '', True ],
- 'bannertextcolor': [ opt_color, 'black', '', True ],
- 'bannerbgcolor': [ opt_color, '', '', True ],
- 'bannerfont': [ opt_str, '', '', True ],
- 'bannerfontattrs': [ opt_str, 'B', '', True ],
- 'msgcorrectingcolor': [opt_color, '#eee8aa'],
-
- # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html
- 'state_inactive_color': [ opt_color, 'grey62' ],
- 'state_composing_color': [ opt_color, 'green4' ],
- 'state_paused_color': [ opt_color, 'mediumblue' ],
- 'state_gone_color': [ opt_color, 'grey' ],
-
- # MUC chat states
- 'state_muc_msg_color': [ opt_color, 'mediumblue' ],
- 'state_muc_directed_msg_color': [ opt_color, 'red2' ],
- }, {}),
- 'contacts': ({
- 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
- }, {}),
- 'encryption': ({'encryption': [ opt_str, '', _('The currently active encryption for that contact')],
- },{}),
- 'rooms': ({
- 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
- 'muc_restore_lines': [opt_int, -2, _('How many lines to request from server when entering a groupchat. -1 means no limit, -2 means global value')],
- 'muc_restore_timeout': [opt_int, -2, _('Minutes of backlog to request when entering a groupchat. -1 means no limit, -2 means global value')],
- }, {}),
- 'plugins': ({
- 'active': [opt_bool, False, _('State whether plugins should be activated on startup (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')],
- },{}),
- }
-
- statusmsg_default = {
- _('Sleeping'): [ 'ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', '' ],
- _('Back soon'): [ _('Back in some minutes.'), '', '', '', '', '' ],
- _('Eating'): [ _("I'm eating, so leave me a message."), 'eating', 'other', '', '', '' ],
- _('Movie'): [ _("I'm watching a movie."), 'relaxing', 'watching_a_movie', '', '', '' ],
- _('Working'): [ _("I'm working."), 'working', 'other', '', '', '' ],
- _('Phone'): [ _("I'm on the phone."), 'talking', 'on_the_phone', '', '', '' ],
- _('Out'): [ _("I'm out enjoying life."), 'relaxing', 'going_out', '', '', '' ],
- '_last_online': ['', '', '', '', '', ''],
- '_last_chat': ['', '', '', '', '', ''],
- '_last_away': ['', '', '', '', '', ''],
- '_last_xa': ['', '', '', '', '', ''],
- '_last_dnd': ['', '', '', '', '', ''],
- '_last_invisible': ['', '', '', '', '', ''],
- '_last_offline': ['', '', '', '', '', ''],
- }
-
- defaultstatusmsg_default = {
- 'online': [ False, _("I'm available.") ],
- 'chat': [ False, _("I'm free for chat.") ],
- 'away': [ False, _('Be right back.') ],
- 'xa': [ False, _("I'm not available.") ],
- 'dnd': [ False, _('Do not disturb.') ],
- 'invisible': [ False, _('Bye!') ],
- 'offline': [ False, _('Bye!') ],
- }
-
- soundevents_default = {
- 'attention_received': [True, 'attention.wav'],
- 'first_message_received': [ True, 'message1.wav' ],
- 'next_message_received_focused': [ True, 'message2.wav' ],
- 'next_message_received_unfocused': [ True, 'message2.wav' ],
- 'contact_connected': [ False, 'connected.wav' ],
- 'contact_disconnected': [ False, 'disconnected.wav' ],
- 'message_sent': [ False, 'sent.wav' ],
- 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')],
- 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ],
- 'gmail_received': [ False, 'message1.wav' ],
- }
-
- themes_default = {
- # sorted alphanum
- _('default'): [ '', '', '', 'B', '', '', '', 'I', '', '', '', '', '', '',
- '', 'B' ],
-
- _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7',
- '', 'I', '#000000', '', '', '', '',
- '#94aa8c', '', 'B' ],
-
- _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad',
- '', 'I', '#000000', '#efb26b', '', '', '',
- '#108abd', '', 'B' ],
-
- _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94',
- '', 'I', '#000000', '', '', '', '',
- '#996442', '', 'B' ],
-
- _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3',
- '', 'I', '#000000', '', '', '', '',
- '#918caa', '', 'B' ],
-
- }
-
- proxies_default = {
- _('Tor'): ['socks5', 'localhost', 9050],
- }
-
- def foreach(self, cb, data=None):
- for opt in self.__options[1]:
- cb(data, opt, None, self.__options[1][opt])
- for opt in self.__options_per_key:
- cb(data, opt, None, None)
- dict_ = self.__options_per_key[opt][1]
- for opt2 in dict_.keys():
- cb(data, opt2, [opt], None)
- for opt3 in dict_[opt2]:
- cb(data, opt3, [opt, opt2], dict_[opt2][opt3])
-
- def get_children(self, node=None):
- """
- Tree-like interface
- """
- if node is None:
- for child, option in self.__options[1].items():
- yield (child, ), option
- for grandparent in self.__options_per_key:
- yield (grandparent, ), None
- elif len(node) == 1:
- grandparent, = node
- for parent in self.__options_per_key[grandparent][1]:
- yield (grandparent, parent), None
- elif len(node) == 2:
- grandparent, parent = node
- children = self.__options_per_key[grandparent][1][parent]
- for child, option in children.items():
- yield (grandparent, parent, child), option
- else:
- raise ValueError('Invalid node')
-
- def is_valid_int(self, val):
- try:
- ival = int(val)
- except Exception:
- return None
- return ival
-
- def is_valid_bool(self, val):
- if val == 'True':
- return True
- if val == 'False':
- return False
- ival = self.is_valid_int(val)
- if ival:
- return True
- if ival is None:
- return None
- return False
-
- def is_valid_string(self, val):
- return val
-
- def is_valid(self, type_, val):
- if not type_:
- return None
- if type_[0] == 'boolean':
- return self.is_valid_bool(val)
- elif type_[0] == 'integer':
- return self.is_valid_int(val)
- elif type_[0] == 'string':
- return self.is_valid_string(val)
- else:
- if re.match(type_[1], val):
- return val
- else:
- return None
-
- def set(self, optname, value):
- if optname not in self.__options[1]:
- return
- value = self.is_valid(self.__options[0][optname][Option.TYPE], value)
- if value is None:
- return
-
- self.__options[1][optname] = value
- self._timeout_save()
-
- def get(self, optname=None):
- if not optname:
- return list(self.__options[1].keys())
- if optname not in self.__options[1]:
- return None
- return self.__options[1][optname]
-
- def get_default(self, optname):
- if optname not in self.__options[0]:
- return None
- return self.__options[0][optname][Option.VAL]
-
- def get_type(self, optname):
- if optname not in self.__options[0]:
- return None
- return self.__options[0][optname][Option.TYPE][0]
-
- def get_desc(self, optname):
- if optname not in self.__options[0]:
- return None
- if len(self.__options[0][optname]) > Option.DESC:
- return self.__options[0][optname][Option.DESC]
-
- def get_restart(self, optname):
- if optname not in self.__options[0]:
- return None
- if len(self.__options[0][optname]) > Option.RESTART:
- return self.__options[0][optname][Option.RESTART]
-
- def add_per(self, typename, name): # per_group_of_option
- if typename not in self.__options_per_key:
- return
-
- opt = self.__options_per_key[typename]
- if name in opt[1]:
- # we already have added group name before
- return 'you already have added %s before' % name
- opt[1][name] = {}
- for o in opt[0]:
- opt[1][name][o] = opt[0][o][Option.VAL]
- self._timeout_save()
-
- def del_per(self, typename, name, subname = None): # per_group_of_option
- if typename not in self.__options_per_key:
- return
-
- opt = self.__options_per_key[typename]
- if subname is None:
- del opt[1][name]
- # if subname is specified, delete the item in the group.
- elif subname in opt[1][name]:
- del opt[1][name][subname]
- self._timeout_save()
-
- def set_per(self, optname, key, subname, value): # per_group_of_option
- if optname not in self.__options_per_key:
- return
- if not key:
- return
- dict_ = self.__options_per_key[optname][1]
- if key not in dict_:
- self.add_per(optname, key)
- obj = dict_[key]
- if subname not in obj:
- return
- typ = self.__options_per_key[optname][0][subname][Option.TYPE]
- value = self.is_valid(typ, value)
- if value is None:
- return
- obj[subname] = value
- self._timeout_save()
-
- def get_per(self, optname, key=None, subname=None): # per_group_of_option
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][1]
- if not key:
- return list(dict_.keys())
- if key not in dict_:
- if subname in self.__options_per_key[optname][0]:
- return self.__options_per_key[optname][0][subname][1]
- return None
- obj = dict_[key]
- if not subname:
- return obj
- if subname not in obj:
- return None
- return obj[subname]
-
- def get_default_per(self, optname, subname):
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][0]
- if subname not in dict_:
- return None
- return dict_[subname][Option.VAL]
-
- def get_type_per(self, optname, subname):
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][0]
- if subname not in dict_:
- return None
- return dict_[subname][Option.TYPE][0]
-
- def get_desc_per(self, optname, key=None, subname=None):
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][0]
- if not key:
- return None
- if key not in dict_:
- return None
- obj = dict_[key]
- if not subname:
- return None
- if subname not in obj:
- return None
- if len(obj[subname]) > Option.DESC:
- return obj[subname][Option.DESC]
- return None
-
- def get_restart_per(self, optname, key=None, subname=None):
- if optname not in self.__options_per_key:
- return False
- dict_ = self.__options_per_key[optname][0]
- if not key:
- return False
- if key not in dict_:
- return False
- obj = dict_[key]
- if not subname:
- return False
- if subname not in obj:
- return False
- if len(obj[subname]) > Option.RESTART:
- return obj[subname][Option.RESTART]
- return False
-
- def should_log(self, account, jid):
- """
- Should conversations between a local account and a remote jid be logged?
- """
- no_log_for = self.get_per('accounts', account, 'no_log_for')
-
- if not no_log_for:
- no_log_for = ''
-
- no_log_for = no_log_for.split()
-
- return (account not in no_log_for) and (jid not in no_log_for)
-
- def _init_options(self):
- for opt in self.__options[0]:
- self.__options[1][opt] = self.__options[0][opt][Option.VAL]
-
- def _really_save(self):
- from common import gajim
- if gajim.interface:
- gajim.interface.save_config()
- self.save_timeout_id = None
- return False
-
- def _timeout_save(self):
- if self.save_timeout_id:
- return
- self.save_timeout_id = GLib.timeout_add(1000, self._really_save)
-
- def __init__(self):
- #init default values
- self._init_options()
- self.save_timeout_id = None
- for event in self.soundevents_default:
- default = self.soundevents_default[event]
- self.add_per('soundevents', event)
- self.set_per('soundevents', event, 'enabled', default[0])
- self.set_per('soundevents', event, 'path', default[1])
-
- for status in self.defaultstatusmsg_default:
- default = self.defaultstatusmsg_default[status]
- self.add_per('defaultstatusmsg', status)
- self.set_per('defaultstatusmsg', status, 'enabled', default[0])
- self.set_per('defaultstatusmsg', status, 'message', default[1])
diff --git a/src/common/configpaths.py b/src/common/configpaths.py
deleted file mode 100644
index 1edf3bbbf..000000000
--- a/src/common/configpaths.py
+++ /dev/null
@@ -1,197 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/configpaths.py
-##
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Junglecow J <junglecow AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import os
-import sys
-import tempfile
-from common import defs
-from enum import Enum, unique
-
-@unique
-class Type(Enum):
- CONFIG = 0
- CACHE = 1
- DATA = 2
-
-# Note on path and filename encodings:
-#
-# In general it is very difficult to do this correctly.
-# We may pull information from environment variables, and what encoding that is
-# in is anyone's guess. Any information we request directly from the file
-# system will be in filesystemencoding, and (parts of) paths that we write in
-# this source code will be in whatever encoding the source is in. (I hereby
-# declare this file to be UTF-8 encoded.)
-#
-# To make things more complicated, modern Windows filesystems use UTF-16, but
-# the API tends to hide this from us.
-#
-# I tried to minimize problems by passing Unicode strings to OS functions as
-# much as possible. Hopefully this makes the function return an Unicode string
-# as well. If not, we get an 8-bit string in filesystemencoding, which we can
-# happily pass to functions that operate on files and directories, so we can
-# just leave it as is. Since these paths are meant to be internal to Gajim and
-# not displayed to the user, Unicode is not really necessary here.
-
-
-def windowsify(s):
- if os.name == 'nt':
- return s.capitalize()
- return s
-
-
-def get(key):
- return gajimpaths[key]
-
-
-class ConfigPaths:
- def __init__(self):
- # {'name': (type, path), } type can be Type.CONFIG, Type.CACHE, Type.DATA
- # or None
- self.paths = {}
-
- if os.name == 'nt':
- try:
- # Documents and Settings\[User Name]\Application Data\Gajim
-
- # How are we supposed to know what encoding the environment
- # variable 'appdata' is in? Assuming it to be in filesystem
- # encoding.
- self.config_root = self.cache_root = self.data_root = \
- os.path.join(os.environ['appdata'], 'Gajim')
- except KeyError:
- # win9x, in cwd
- self.config_root = self.cache_root = self.data_root = '.'
- else: # Unices
- # Pass in an Unicode string, and hopefully get one back.
- expand = os.path.expanduser
- base = os.getenv('XDG_CONFIG_HOME')
- if base is None or base[0] != '/':
- base = expand('~/.config')
- self.config_root = os.path.join(base, 'gajim')
- base = os.getenv('XDG_CACHE_HOME')
- if base is None or base[0] != '/':
- base = expand('~/.cache')
- self.cache_root = os.path.join(base, 'gajim')
- base = os.getenv('XDG_DATA_HOME')
- if base is None or base[0] != '/':
- base = expand('~/.local/share')
- self.data_root = os.path.join(base, 'gajim')
-
- basedir = os.environ.get('GAJIM_BASEDIR', defs.basedir)
- self.add('DATA', None, os.path.join(basedir, 'data'))
- self.add('GUI', None, os.path.join(basedir, 'data', 'gui'))
- self.add('ICONS', None, os.path.join(basedir, 'icons'))
- self.add('HOME', None, os.path.expanduser('~'))
- self.add('PLUGINS_BASE', None, os.path.join(basedir, 'plugins'))
-
- def add(self, name, type_, path):
- self.paths[name] = (type_, path)
-
- def __getitem__(self, key):
- type_, path = self.paths[key]
- if type_ == Type.CONFIG:
- return os.path.join(self.config_root, path)
- elif type_ == Type.CACHE:
- return os.path.join(self.cache_root, path)
- elif type_ == Type.DATA:
- return os.path.join(self.data_root, path)
- return path
-
- def get(self, key, default=None):
- try:
- return self[key]
- except KeyError:
- return default
-
- def items(self):
- for key in self.paths.keys():
- yield (key, self[key])
-
- def init(self, root=None, profile='', profile_separation=False):
- if root is not None:
- self.config_root = self.cache_root = self.data_root = root
-
- self.init_profile(profile)
-
- if len(profile) > 0 and profile_separation:
- profile = u'.' + profile
- else:
- profile = ''
-
- d = {'LOG_DB': 'logs.db', 'MY_CACERTS': 'cacerts.pem',
- 'MY_EMOTS': 'emoticons', 'MY_ICONSETS': 'iconsets',
- 'MY_MOOD_ICONSETS': 'moods', 'MY_ACTIVITY_ICONSETS': 'activities',
- 'PLUGINS_USER': 'plugins',
- 'RNG_SEED': 'rng_seed'}
- for name in d:
- d[name] += profile
- self.add(name, Type.DATA, windowsify(d[name]))
- if len(profile):
- self.add('MY_DATA', Type.DATA, 'data.dir')
- else:
- self.add('MY_DATA', Type.DATA, '')
-
- d = {'CACHE_DB': 'cache.db', 'VCARD': 'vcards',
- 'AVATAR': 'avatars'}
- for name in d:
- d[name] += profile
- self.add(name, Type.CACHE, windowsify(d[name]))
- if len(profile):
- self.add('MY_CACHE', Type.CACHE, 'cache.dir')
- else:
- self.add('MY_CACHE', Type.CACHE, '')
-
- if len(profile):
- self.add('MY_CONFIG', Type.CONFIG, 'config.dir')
- else:
- self.add('MY_CONFIG', Type.CONFIG, '')
-
- try:
- self.add('TMP', None, tempfile.gettempdir())
- except IOError as e:
- print('Error opening tmp folder: %s\nUsing %s' % (str(e),
- os.path.expanduser('~')), file=sys.stderr)
- self.add('TMP', None, os.path.expanduser('~'))
-
- def init_profile(self, profile):
- conffile = windowsify('config')
- secretsfile = windowsify('secrets')
- pluginsconfdir = windowsify('pluginsconfig')
- certsdir = windowsify(u'certs')
- localcertsdir = windowsify(u'localcerts')
-
- if len(profile) > 0:
- conffile += '.' + profile
- secretsfile += '.' + profile
- pluginsconfdir += '.' + profile
- certsdir += u'.' + profile
- localcertsdir += u'.' + profile
-
- self.add('SECRETS_FILE', Type.DATA, secretsfile)
- self.add('MY_PEER_CERTS', Type.DATA, certsdir)
- self.add('CONFIG_FILE', Type.CONFIG, conffile)
- self.add('PLUGINS_CONFIG_DIR', Type.CONFIG, pluginsconfdir)
- self.add('MY_CERT', Type.CONFIG, localcertsdir)
-
-gajimpaths = ConfigPaths()
diff --git a/src/common/connection.py b/src/common/connection.py
deleted file mode 100644
index d77cfa7cf..000000000
--- a/src/common/connection.py
+++ /dev/null
@@ -1,2982 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/connection.py
-##
-## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
-## Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import os
-import random
-import socket
-import operator
-import string
-import time
-import locale
-import hmac
-import hashlib
-import json
-
-try:
- randomsource = random.SystemRandom()
-except Exception:
- randomsource = random.Random()
- randomsource.seed()
-
-import signal
-if os.name != 'nt':
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-
-import nbxmpp
-from common import helpers
-from common import gajim
-from common import gpg
-from common import passwords
-from common import exceptions
-from common import check_X509
-from common.connection_handlers import *
-
-from gtkgui_helpers import get_action
-
-if gajim.HAVE_PYOPENSSL:
- import OpenSSL.crypto
-
-from nbxmpp import Smacks
-from string import Template
-import logging
-log = logging.getLogger('gajim.c.connection')
-
-ssl_error = {
- 2: _("Unable to get issuer certificate"),
- 3: _("Unable to get certificate CRL"),
- 4: _("Unable to decrypt certificate's signature"),
- 5: _("Unable to decrypt CRL's signature"),
- 6: _("Unable to decode issuer public key"),
- 7: _("Certificate signature failure"),
- 8: _("CRL signature failure"),
- 9: _("Certificate is not yet valid"),
- 10: _("Certificate has expired"),
- 11: _("CRL is not yet valid"),
- 12: _("CRL has expired"),
- 13: _("Format error in certificate's notBefore field"),
- 14: _("Format error in certificate's notAfter field"),
- 15: _("Format error in CRL's lastUpdate field"),
- 16: _("Format error in CRL's nextUpdate field"),
- 17: _("Out of memory"),
- 18: _("Self signed certificate"),
- 19: _("Self signed certificate in certificate chain"),
- 20: _("Unable to get local issuer certificate"),
- 21: _("Unable to verify the first certificate"),
- 22: _("Certificate chain too long"),
- 23: _("Certificate revoked"),
- 24: _("Invalid CA certificate"),
- 25: _("Path length constraint exceeded"),
- 26: _("Unsupported certificate purpose"),
- 27: _("Certificate not trusted"),
- 28: _("Certificate rejected"),
- 29: _("Subject issuer mismatch"),
- 30: _("Authority and subject key identifier mismatch"),
- 31: _("Authority and issuer serial number mismatch"),
- 32: _("Key usage does not include certificate signing"),
- 50: _("Application verification failure")
- #100 is for internal usage: host not correct
-}
-
-class CommonConnection:
- """
- Common connection class, can be derivated for normal connection or zeroconf
- connection
- """
-
- def __init__(self, name):
- self.name = name
- # self.connected:
- # 0=>offline,
- # 1=>connection in progress,
- # 2=>online
- # 3=>free for chat
- # ...
- self.connected = 0
- self.connection = None # xmpppy ClientCommon instance
- self.on_purpose = False
- self.is_zeroconf = False
- self.password = ''
- self.server_resource = self._compute_resource()
- self.gpg = None
- self.USE_GPG = False
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = gpg.GnuPG()
- self.status = ''
- self.old_show = ''
- self.priority = gajim.get_priority(name, 'offline')
- self.time_to_reconnect = None
- self.bookmarks = []
-
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.blocked_all = False
-
- self.seclabel_supported = False
- self.seclabel_catalogues = {}
-
- self.pep_supported = False
- self.pep = {}
- # Do we continue connection when we get roster (send presence,get vcard..)
- self.continue_connect_info = None
-
- # Remember where we are in the register agent process
- self.agent_registrations = {}
- # To know the groupchat jid associated with a sranza ID. Useful to
- # request vcard or os info... to a real JID but act as if it comes from
- # the fake jid
- self.groupchat_jids = {} # {ID : groupchat_jid}
-
- self.privacy_rules_supported = False
- self.vcard_supported = False
- self.private_storage_supported = False
- self.archiving_namespace = None
- self.archiving_supported = False
- self.archiving_313_supported = False
- self.archiving_136_supported = False
- self.archive_pref_supported = False
- self.roster_supported = True
- self.blocking_supported = False
- self.addressing_supported = False
- self.carbons_enabled = False
-
- self.muc_jid = {} # jid of muc server for each transport type
- self._stun_servers = [] # STUN servers of our jabber server
-
- self.awaiting_cids = {} # Used for XEP-0231
-
- self.nested_group_delimiter = '::'
-
- self.get_config_values_or_default()
-
- def _compute_resource(self):
- resource = gajim.config.get_per('accounts', self.name, 'resource')
- # All valid resource substitution strings should be added to this hash.
- if resource:
- rand = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8))
- resource = Template(resource).safe_substitute({
- 'hostname': socket.gethostname(),
- 'rand': rand
- })
- return resource
-
- def dispatch(self, event, data):
- """
- Always passes account name as first param
- """
- gajim.ged.raise_event(event, self.name, data)
-
- def reconnect(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def quit(self, kill_core):
- if kill_core and gajim.account_is_connected(self.name):
- self.disconnect(on_purpose=True)
-
- def test_gpg_passphrase(self, password):
- """
- Returns 'ok', 'bad_pass' or 'expired'
- """
- if not self.gpg:
- return False
- self.gpg.passphrase = password
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- signed = self.gpg.sign('test', keyID)
- self.gpg.password = None
- if signed == 'KEYEXPIRED':
- return 'expired'
- elif signed == 'BAD_PASSPHRASE':
- return 'bad_pass'
- return 'ok'
-
- def get_signed_msg(self, msg, callback = None):
- """
- Returns the signed message if possible or an empty string if gpg is not
- used or None if waiting for passphrase
-
- callback is the function to call when user give the passphrase
- """
- signed = ''
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- if keyID and self.USE_GPG:
- if self.gpg.passphrase is None and not self.gpg.use_agent:
- # We didn't set a passphrase
- return None
- signed = self.gpg.sign(msg, keyID)
- if signed == 'BAD_PASSPHRASE':
- self.USE_GPG = False
- signed = ''
- gajim.nec.push_incoming_event(BadGPGPassphraseEvent(None,
- conn=self))
- return signed
-
- def _on_disconnected(self):
- """
- Called when a disconnect request has completed successfully
- """
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
-
- def get_status(self):
- return gajim.SHOW_LIST[self.connected]
-
- def check_jid(self, jid):
- """
- This function must be implemented by derivated classes. It has to return
- the valid jid, or raise a helpers.InvalidFormat exception
- """
- raise NotImplementedError
-
- def _prepare_message(self, obj):
-
- if not self.connection or self.connected < 2:
- return 1
-
- if isinstance(obj.jid, list):
- for jid in obj.jid:
- try:
- self.check_jid(jid)
- except helpers.InvalidFormat:
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error', pri_txt=_('Invalid JID'),
- sec_txt=_('It is not possible to send a message '
- 'to %s, this JID is not valid.') % jid))
- return
- else:
- try:
- self.check_jid(obj.jid)
- except helpers.InvalidFormat:
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Invalid JID'), sec_txt=_(
- 'It is not possible to send a message to %s, this JID is not '
- 'valid.') % obj.jid))
- return
-
- if obj.message and not obj.xhtml and gajim.config.get(
- 'rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- obj.xhtml = create_xhtml(obj.message)
- if not obj.message and obj.chatstate is None and obj.form_node is None:
- return
-
- self._build_message_stanza(obj)
-
- def _build_message_stanza(self, obj):
- if obj.jid == gajim.get_jid_from_account(self.name):
- fjid = obj.jid
- else:
- fjid = obj.get_full_jid()
-
- if obj.type_ == 'chat':
- msg_iq = nbxmpp.Message(body=obj.message, typ=obj.type_,
- xhtml=obj.xhtml)
- else:
- if obj.subject:
- msg_iq = nbxmpp.Message(body=obj.message, typ='normal',
- subject=obj.subject, xhtml=obj.xhtml)
- else:
- msg_iq = nbxmpp.Message(body=obj.message, typ='normal',
- xhtml=obj.xhtml)
-
- if obj.correct_id:
- msg_iq.setTag('replace', attrs={'id': obj.correct_id},
- namespace=nbxmpp.NS_CORRECT)
-
- if obj.msg_id:
- msg_iq.setID(obj.msg_id)
-
- if obj.form_node:
- msg_iq.addChild(node=obj.form_node)
- if obj.label:
- msg_iq.addChild(node=obj.label)
-
- # XEP-0172: user_nickname
- if obj.user_nick:
- msg_iq.setTag('nick', namespace=nbxmpp.NS_NICK).setData(
- obj.user_nick)
-
- # XEP-0203
- if obj.delayed:
- our_jid = gajim.get_jid_from_account(self.name) + '/' + \
- self.server_resource
- timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(obj.delayed))
- msg_iq.addChild('delay', namespace=nbxmpp.NS_DELAY2,
- attrs={'from': our_jid, 'stamp': timestamp})
-
- # XEP-0224
- if obj.attention:
- msg_iq.setTag('attention', namespace=nbxmpp.NS_ATTENTION)
-
- if isinstance(obj.jid, list):
- if self.addressing_supported:
- msg_iq.setTo(gajim.config.get_per('accounts', self.name, 'hostname'))
- addresses = msg_iq.addChild('addresses',
- namespace=nbxmpp.NS_ADDRESS)
- for j in obj.jid:
- addresses.addChild('address', attrs = {'type': 'to',
- 'jid': j})
- else:
- iqs = []
- for j in obj.jid:
- iq = nbxmpp.Message(node=msg_iq)
- iq.setTo(j)
- iqs.append(iq)
- msg_iq = iqs
- else:
- msg_iq.setTo(fjid)
- r_ = obj.resource
- if not r_ and obj.jid != fjid: # Only if we're not in a pm
- r_ = gajim.get_resource_from_jid(fjid)
- if r_:
- contact = gajim.contacts.get_contact(self.name, obj.jid, r_)
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.name, obj.jid)
-
- # chatstates - if peer supports xep85, send chatstates
- # please note that the only valid tag inside a message containing a
- # <body> tag is the active event
- if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
- msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
- only_chatste = False
- if not obj.message:
- only_chatste = True
- if only_chatste and not obj.session.enable_encryption:
- msg_iq.setTag('no-store',
- namespace=nbxmpp.NS_MSG_HINTS)
-
- # XEP-0184
- if obj.jid != gajim.get_jid_from_account(self.name):
- request = gajim.config.get_per('accounts', self.name,
- 'request_receipt')
- if obj.message and request:
- msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS)
-
- if obj.forward_from:
- addresses = msg_iq.addChild('addresses',
- namespace=nbxmpp.NS_ADDRESS)
- addresses.addChild('address', attrs = {'type': 'ofrom',
- 'jid': obj.forward_from})
-
- if obj.session:
- # XEP-0201
- obj.session.last_send = time.time()
- msg_iq.setThread(obj.session.thread_id)
-
- self._push_stanza_message_outgoing(obj, msg_iq)
-
- def _push_stanza_message_outgoing(self, obj, msg_iq):
- obj.conn = self
- if isinstance(msg_iq, list):
- for iq in msg_iq:
- obj.msg_iq = iq
- gajim.nec.push_incoming_event(
- StanzaMessageOutgoingEvent(None, **vars(obj)))
- else:
- obj.msg_iq = msg_iq
- gajim.nec.push_incoming_event(
- StanzaMessageOutgoingEvent(None, **vars(obj)))
-
- def log_message(self, obj, jid):
- if not obj.is_loggable:
- return
-
- if obj.forward_from or not obj.session or not obj.session.is_loggable():
- return
-
- if not gajim.config.should_log(self.name, jid):
- return
-
- if obj.xhtml and gajim.config.get('log_xhtml_messages'):
- message = '<body xmlns="%s">%s</body>' % (nbxmpp.NS_XHTML,
- obj.xhtml)
- else:
- message = obj.original_message or obj.message
- if not message:
- return
-
- if obj.type_ == 'chat':
- kind = 'chat_msg_sent'
- else:
- kind = 'single_msg_sent'
-
- gajim.logger.write(
- kind, jid, message, subject=obj.subject,
- additional_data=obj.additional_data)
-
- def ack_subscribed(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def ack_unsubscribed(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def request_subscription(self, jid, msg='', name='', groups=None,
- auto_auth=False):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def send_authorization(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def refuse_authorization(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def unsubscribe(self, jid, remove_auth = True):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def unsubscribe_agent(self, agent):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def update_contact(self, jid, name, groups):
- if self.connection and self.roster_supported:
- self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
-
- def update_contacts(self, contacts):
- """
- Update multiple roster items
- """
- if self.connection and self.roster_supported:
- self.connection.getRoster().setItemMulti(contacts)
-
- def new_account(self, name, config, sync=False):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def _on_new_account(self, con=None, con_type=None):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def account_changed(self, new_name):
- self.name = new_name
-
- def request_last_status_time(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not gajim.account_is_connected(self.name):
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = nbxmpp.Iq(to=to_whom_jid, typ='get', queryNS=nbxmpp.NS_LAST)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.last_ids.append(id_)
- self.connection.send(iq)
-
- def request_os_info(self, jid, resource):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_settings(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_bookmarks(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def store_bookmarks(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_metacontacts(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def send_agent_status(self, agent, ptype):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def gpg_passphrase(self, passphrase):
- if self.gpg:
- if self.gpg.use_agent:
- self.gpg.passphrase = None
- else:
- self.gpg.passphrase = passphrase
-
- def ask_gpg_keys(self, keyID=None):
- if self.gpg:
- if keyID:
- return self.gpg.get_key(keyID)
- return self.gpg.get_keys()
- return None
-
- def ask_gpg_secrete_keys(self):
- if self.gpg:
- return self.gpg.get_secret_keys()
- return None
-
- def load_roster_from_db(self):
- # Do nothing by default
- return
-
- def _event_dispatcher(self, realm, event, data):
- if realm == '':
- if event == nbxmpp.transports_nb.DATA_RECEIVED:
- gajim.nec.push_incoming_event(StanzaReceivedEvent(None,
- conn=self, stanza_str=data))
- elif event == nbxmpp.transports_nb.DATA_SENT:
- gajim.nec.push_incoming_event(StanzaSentEvent(None, conn=self,
- stanza_str=data))
-
- def change_status(self, show, msg, auto=False):
- if not msg:
- msg = ''
- sign_msg = False
- if not auto and not show == 'offline':
- sign_msg = True
- if show != 'invisible':
- # We save it only when privacy list is accepted
- self.status = msg
- if show != 'offline' and self.connected < 1:
- # set old_show to requested 'show' in case we need to
- # recconect before we auth to server
- self.old_show = show
- self.on_purpose = False
- self.server_resource = self._compute_resource()
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = gpg.GnuPG()
- gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
- conn=self, show=show, message=msg))
- self.connect_and_init(show, msg, sign_msg)
- return
-
- if show == 'offline':
- self.connected = 0
- if self.connection:
- gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
- conn=self, show=show, message=msg))
- p = nbxmpp.Presence(typ = 'unavailable')
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
-
- self.connection.RegisterDisconnectHandler(self._on_disconnected)
- self.connection.send(p, now=True)
- self.connection.start_disconnect()
- else:
- self._on_disconnected()
- return
-
- if show != 'offline' and self.connected > 0:
- # dont'try to connect, when we are in state 'connecting'
- if self.connected == 1:
- return
- if show == 'invisible':
- gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
- conn=self, show=show, message=msg))
- self._change_to_invisible(msg)
- return
- if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
- return -1
- was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
- self.connected = gajim.SHOW_LIST.index(show)
- gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
- conn=self, show=show, message=msg))
- if was_invisible:
- self._change_from_invisible()
- self._update_status(show, msg)
-
-class Connection(CommonConnection, ConnectionHandlers):
- def __init__(self, name):
- CommonConnection.__init__(self, name)
- ConnectionHandlers.__init__(self)
- # this property is used to prevent double connections
- self.last_connection = None # last ClientCommon instance
- # If we succeed to connect, remember it so next time we try (after a
- # disconnection) we try only this type.
- self.last_connection_type = None
- self.lang = None
- if locale.getdefaultlocale()[0]:
- self.lang = locale.getdefaultlocale()[0].split('_')[0]
- # increase/decrease default timeout for server responses
- self.try_connecting_for_foo_secs = 45
- # holds the actual hostname to which we are connected
- self.connected_hostname = None
- self.redirected = None
- self.last_time_to_reconnect = None
- self.new_account_info = None
- self.new_account_form = None
- self.annotations = {}
- self.last_io = gajim.idlequeue.current_time()
- self.last_sent = []
- self.last_history_time = {}
- self.password = passwords.get_password(name)
-
- self.music_track_info = 0
- self.location_info = {}
- self.pubsub_supported = False
- self.register_supported = False
- self.pubsub_publish_options_supported = False
- # Do we auto accept insecure connection
- self.connection_auto_accepted = False
- self.pasword_callback = None
-
- self.on_connect_success = None
- self.on_connect_failure = None
- self.retrycount = 0
- self.jids_for_auto_auth = [] # list of jid to auto-authorize
- self.available_transports = {} # list of available transports on this
- # server {'icq': ['icq.server.com', 'icq2.server.com'], }
- self.private_storage_supported = True
- self.privacy_rules_requested = False
- self.streamError = ''
- self.secret_hmac = str(random.random())[2:].encode('utf-8')
-
- self.sm = Smacks(self) # Stream Management
-
- gajim.ged.register_event_handler('privacy-list-received', ged.CORE,
- self._nec_privacy_list_received)
- gajim.ged.register_event_handler('agent-info-error-received', ged.CORE,
- self._nec_agent_info_error_received)
- gajim.ged.register_event_handler('agent-info-received', ged.CORE,
- self._nec_agent_info_received)
- gajim.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
- self._nec_message_outgoing)
- gajim.ged.register_event_handler('gc-message-outgoing', ged.OUT_CORE,
- self._nec_gc_message_outgoing)
- gajim.ged.register_event_handler('gc-stanza-message-outgoing', ged.OUT_CORE,
- self._nec_gc_stanza_message_outgoing)
- gajim.ged.register_event_handler('stanza-message-outgoing',
- ged.OUT_CORE, self._nec_stanza_message_outgoing)
- # END __init__
-
- def cleanup(self):
- ConnectionHandlers.cleanup(self)
- gajim.ged.remove_event_handler('privacy-list-received', ged.CORE,
- self._nec_privacy_list_received)
- gajim.ged.remove_event_handler('agent-info-error-received', ged.CORE,
- self._nec_agent_info_error_received)
- gajim.ged.remove_event_handler('agent-info-received', ged.CORE,
- self._nec_agent_info_received)
- gajim.ged.remove_event_handler('message-outgoing', ged.OUT_CORE,
- self._nec_message_outgoing)
- gajim.ged.remove_event_handler('gc-message-outgoing', ged.OUT_CORE,
- self._nec_gc_message_outgoing)
- gajim.ged.remove_event_handler('gc-stanza-message-outgoing', ged.OUT_CORE,
- self._nec_gc_stanza_message_outgoing)
- gajim.ged.remove_event_handler('stanza-message-outgoing', ged.OUT_CORE,
- self._nec_stanza_message_outgoing)
-
- def get_config_values_or_default(self):
- if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
- self.keepalives = gajim.config.get_per('accounts', self.name,
- 'keep_alive_every_foo_secs')
- else:
- self.keepalives = 0
- if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'):
- self.pingalives = gajim.config.get_per('accounts', self.name,
- 'ping_alive_every_foo_secs')
- else:
- self.pingalives = 0
- self.client_cert = gajim.config.get_per('accounts', self.name,
- 'client_cert')
- self.client_cert_passphrase = ''
-
- def check_jid(self, jid):
- return helpers.parse_jid(jid)
-
- def reconnect(self):
- # Do not try to reco while we are already trying
- self.time_to_reconnect = None
- if self.connected < 2: # connection failed
- log.debug('reconnect')
- self.connected = 1
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='connecting'))
- self.retrycount += 1
- self.on_connect_auth = self._discover_server_at_connection
- self.connect_and_init(self.old_show, self.status, self.USE_GPG)
- else:
- # reconnect succeeded
- self.time_to_reconnect = None
- self.retrycount = 0
-
- # We are doing disconnect at so many places, better use one function in all
- def disconnect(self, on_purpose=False):
- gajim.interface.music_track_changed(None, None, self.name)
- self.reset_awaiting_pep()
- self.on_purpose = on_purpose
- self.connected = 0
- self.time_to_reconnect = None
- self.privacy_rules_supported = False
- if on_purpose:
- self.sm = Smacks(self)
- if self.connection:
- # make sure previous connection is completely closed
- gajim.proxy65_manager.disconnect(self.connection)
- self.terminate_sessions()
- self.remove_all_transfers()
- self.connection.disconnect()
- self.last_connection = None
- self.connection = None
-
- def set_oldst(self): # Set old state
- if self.old_show:
- self.connected = gajim.SHOW_LIST.index(self.old_show)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=self.connected))
- else: # we default to online
- self.connected = 2
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=gajim.SHOW_LIST[self.connected]))
-
- def disconnectedReconnCB(self):
- """
- Called when we are disconnected
- """
- log.info('disconnectedReconnCB called')
- if gajim.account_is_connected(self.name):
- # we cannot change our status to offline or connecting
- # after we auth to server
- self.old_show = gajim.SHOW_LIST[self.connected]
- self.connected = 0
- if not self.on_purpose:
- if not (self.sm and self.sm.resumption):
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- else:
- self.sm.enabled = False
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='error'))
- if self.connection:
- self.connection.UnregisterDisconnectHandler(
- self.disconnectedReconnCB)
- self.disconnect()
- if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
- self.connected = -1
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='error'))
- if gajim.status_before_autoaway[self.name]:
- # We were auto away. So go back online
- self.status = gajim.status_before_autoaway[self.name]
- gajim.status_before_autoaway[self.name] = ''
- self.old_show = 'online'
- # this check has moved from reconnect method
- # do exponential backoff until less than 5 minutes
- if self.retrycount < 2 or self.last_time_to_reconnect is None:
- self.last_time_to_reconnect = 5
- self.last_time_to_reconnect += randomsource.randint(0, 5)
- if self.last_time_to_reconnect < 200:
- self.last_time_to_reconnect *= 1.5
- self.time_to_reconnect = int(self.last_time_to_reconnect)
- log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect)
- gajim.idlequeue.set_alarm(self._reconnect_alarm,
- self.time_to_reconnect)
- elif self.on_connect_failure:
- self.on_connect_failure()
- self.on_connect_failure = None
- else:
- # show error dialog
- self._connection_lost()
- else:
- if self.redirected:
- self.disconnect(on_purpose=True)
- self.connect()
- return
- else:
- self.disconnect()
- self.on_purpose = False
- # END disconnectedReconnCB
-
- def _connection_lost(self):
- log.debug('_connection_lost')
- self.disconnect(on_purpose = False)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Connection with account "%s" has been lost') % self.name,
- msg=_('Reconnect manually.')))
-
- def _event_dispatcher(self, realm, event, data):
- CommonConnection._event_dispatcher(self, realm, event, data)
- if realm == nbxmpp.NS_REGISTER:
- if event == nbxmpp.features_nb.REGISTER_DATA_RECEIVED:
- # data is (agent, DataFrom, is_form, error_msg)
- if self.new_account_info and \
- self.new_account_info['hostname'] == data[0]:
- # it's a new account
- if not data[1]: # wrong answer
- reason = _('Server %(name)s answered wrongly to '
- 'register request: %(error)s') % {'name': data[0],
- 'error': data[3]}
- gajim.nec.push_incoming_event(AccountNotCreatedEvent(
- None, conn=self, reason=reason))
- return
- is_form = data[2]
- conf = data[1]
- if data[4] is not '':
- helpers.replace_dataform_media(conf, data[4])
- if self.new_account_form:
- def _on_register_result(result):
- if not nbxmpp.isResultNode(result):
- gajim.nec.push_incoming_event(AccountNotCreatedEvent(
- None, conn=self, reason=result.getError()))
- return
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = gpg.GnuPG()
- gajim.nec.push_incoming_event(
- AccountCreatedEvent(None, conn=self,
- account_info = self.new_account_info))
- self.new_account_info = None
- self.new_account_form = None
- if self.connection:
- self.connection.UnregisterDisconnectHandler(
- self._on_new_account)
- self.disconnect(on_purpose=True)
- # it's the second time we get the form, we have info user
- # typed, so send them
- if is_form:
- #TODO: Check if form has changed
- iq = nbxmpp.Iq('set', nbxmpp.NS_REGISTER,
- to=self._hostname)
- iq.setTag('query').addChild(node=self.new_account_form)
- self.connection.SendAndCallForResponse(iq,
- _on_register_result)
- else:
- if list(self.new_account_form.keys()).sort() != \
- list(conf.keys()).sort():
- # requested config has changed since first connection
- reason = _('Server %s provided a different '
- 'registration form') % data[0]
- gajim.nec.push_incoming_event(AccountNotCreatedEvent(
- None, conn=self, reason=reason))
- return
- nbxmpp.features_nb.register(self.connection,
- self._hostname, self.new_account_form,
- _on_register_result)
- return
- gajim.nec.push_incoming_event(NewAccountConnectedEvent(None,
- conn=self, config=conf, is_form=is_form))
- self.connection.UnregisterDisconnectHandler(
- self._on_new_account)
- self.disconnect(on_purpose=True)
- return
- if not data[1]: # wrong answer
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error', pri_txt=_('Invalid answer'),
- sec_txt=_('Transport %(name)s answered wrongly to '
- 'register request: %(error)s') % {'name': data[0],
- 'error': data[3]}))
- return
- is_form = data[2]
- conf = data[1]
- gajim.nec.push_incoming_event(RegisterAgentInfoReceivedEvent(
- None, conn=self, agent=data[0], config=conf,
- is_form=is_form))
- elif realm == nbxmpp.NS_PRIVACY:
- if event == nbxmpp.features_nb.PRIVACY_LISTS_RECEIVED:
- # data is (list)
- gajim.nec.push_incoming_event(PrivacyListsReceivedEvent(None,
- conn=self, lists_list=data))
- elif event == nbxmpp.features_nb.PRIVACY_LIST_RECEIVED:
- # data is (resp)
- if not data:
- return
- rules = []
- name = data.getTag('query').getTag('list').getAttr('name')
- for child in data.getTag('query').getTag('list').getChildren():
- dict_item = child.getAttrs()
- childs = []
- if 'type' in dict_item:
- for scnd_child in child.getChildren():
- childs += [scnd_child.getName()]
- rules.append({'action':dict_item['action'],
- 'type':dict_item['type'], 'order':dict_item['order'],
- 'value':dict_item['value'], 'child':childs})
- else:
- for scnd_child in child.getChildren():
- childs.append(scnd_child.getName())
- rules.append({'action':dict_item['action'],
- 'order':dict_item['order'], 'child':childs})
- gajim.nec.push_incoming_event(PrivacyListReceivedEvent(None,
- conn=self, list_name=name, rules=rules))
- elif event == nbxmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
- # data is (dict)
- gajim.nec.push_incoming_event(PrivacyListActiveDefaultEvent(
- None, conn=self, active_list=data['active'],
- default_list=data['default']))
-
- def _select_next_host(self, hosts):
- """
- Selects the next host according to RFC2782 p.3 based on it's priority.
- Chooses between hosts with the same priority randomly, where the
- probability of being selected is proportional to the weight of the host
- """
- hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio'))
-
- try:
- lowest_prio = hosts_by_prio[0]['prio']
- except IndexError:
- raise ValueError("No hosts to choose from!")
-
- hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio]
-
- if len(hosts_lowest_prio) == 1:
- return hosts_lowest_prio[0]
- else:
- rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio))
- weightsum = 0
- for host in sorted(hosts_lowest_prio, key=operator.itemgetter(
- 'weight')):
- weightsum += host['weight']
- if weightsum >= rndint:
- return host
-
- def connect(self, data=None):
- """
- Start a connection to the XMPP server
-
- Returns connection, and connection type ('tls', 'ssl', 'plain', '') data
- MUST contain hostname, proxy, use_custom_host, custom_host (if
- use_custom_host), custom_port (if use_custom_host)
- """
- if self.connection:
- return self.connection, ''
-
- if self.sm.resuming and self.sm.location:
- # If resuming and server gave a location, connect from there
- hostname = self.sm.location
- self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
- self.name, 'try_connecting_for_foo_secs')
- use_custom = False
- proxy = helpers.get_proxy_info(self.name)
-
- elif data:
- hostname = data['hostname']
- self.try_connecting_for_foo_secs = 45
- p = data['proxy']
- if p and p in gajim.config.get_per('proxies'):
- proxy = {}
- proxyptr = gajim.config.get_per('proxies', p)
- for key in proxyptr.keys():
- proxy[key] = proxyptr[key]
- else:
- proxy = None
- use_srv = True
- use_custom = data['use_custom_host']
- if use_custom:
- custom_h = data['custom_host']
- custom_p = data['custom_port']
- else:
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
- self.name, 'try_connecting_for_foo_secs')
- proxy = helpers.get_proxy_info(self.name)
- use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
- if self.redirected:
- use_custom = True
- custom_h = self.redirected['host']
- custom_p = self.redirected['port']
- else:
- use_custom = gajim.config.get_per('accounts', self.name,
- 'use_custom_host')
- if use_custom:
- custom_h = gajim.config.get_per('accounts', self.name,
- 'custom_host')
- custom_p = gajim.config.get_per('accounts', self.name,
- 'custom_port')
- try:
- helpers.idn_to_ascii(custom_h)
- except Exception:
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error',
- pri_txt=_('Wrong Custom Hostname'),
- sec_txt='Wrong custom hostname "%s". Ignoring it.' \
- % custom_h))
- use_custom = False
-
- # create connection if it doesn't already exist
- self.connected = 1
-
- h = hostname
- p = 5222
- ssl_p = 5223
- if use_custom:
- h = custom_h
- p = custom_p
- ssl_p = custom_p
- if not self.redirected:
- use_srv = False
-
- self.redirected = None
- # SRV resolver
- self._proxy = proxy
- self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
- 'weight': 10} ]
- self._hostname = hostname
- if use_srv:
- # add request for srv query to the resolve, on result '_on_resolve'
- # will be called
- gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(
- h), self._on_resolve)
- else:
- self._on_resolve('', [])
-
- def _on_resolve(self, host, result_array):
- # SRV query returned at least one valid result, we put it in hosts dict
- if len(result_array) != 0:
- self._hosts = [i for i in result_array]
- # Add ssl port
- ssl_p = 5223
- if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
- ssl_p = gajim.config.get_per('accounts', self.name,
- 'custom_port')
- for i in self._hosts:
- i['ssl_port'] = ssl_p
- self._connect_to_next_host()
-
-
- def _connect_to_next_host(self, retry=False):
- log.debug('Connection to next host')
- if len(self._hosts):
- # No config option exist when creating a new account
- if self.name in gajim.config.get_per('accounts'):
- self._connection_types = gajim.config.get_per('accounts', self.name,
- 'connection_types').split()
- else:
- self._connection_types = ['tls', 'ssl']
- if self.last_connection_type:
- if self.last_connection_type in self._connection_types:
- self._connection_types.remove(self.last_connection_type)
- self._connection_types.insert(0, self.last_connection_type)
-
-
- if self._proxy and self._proxy['type']=='bosh':
- # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain"
- # connection and TLS with handshake right after TCP connecting ("ssl")
- scheme = nbxmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
- if scheme=='https':
- self._connection_types = ['ssl']
- else:
- self._connection_types = ['plain']
-
- host = self._select_next_host(self._hosts)
- self._current_host = host
- self._hosts.remove(host)
- self.connect_to_next_type()
-
- else:
- if not retry and self.retrycount == 0:
- log.debug("Out of hosts, giving up connecting to %s", self.name)
- self.time_to_reconnect = None
- if self.on_connect_failure:
- self.on_connect_failure()
- self.on_connect_failure = None
- else:
- # shown error dialog
- self._connection_lost()
- else:
- # try reconnect if connection has failed before auth to server
- self.disconnectedReconnCB()
-
- def connect_to_next_type(self, retry=False):
- if self.redirected:
- self.disconnect(on_purpose=True)
- self.connect()
- return
- if len(self._connection_types):
- self._current_type = self._connection_types.pop(0)
- if self.last_connection:
- self.last_connection.socket.disconnect()
- self.last_connection = None
- self.connection = None
-
- if self._current_type == 'ssl':
- # SSL (force TLS on different port than plain)
- # If we do TLS over BOSH, port of XMPP server should be the standard one
- # and TLS should be negotiated because TLS on 5223 is deprecated
- if self._proxy and self._proxy['type']=='bosh':
- port = self._current_host['port']
- else:
- port = self._current_host['ssl_port']
- elif self._current_type == 'tls':
- # TLS - negotiate tls after XMPP stream is estabilished
- port = self._current_host['port']
- elif self._current_type == 'plain':
- # plain connection on defined port
- port = self._current_host['port']
-
- cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
- if not os.path.exists(cacerts):
- cacerts = ''
- mycerts = common.gajim.MY_CACERTS
- tls_version = gajim.config.get_per('accounts', self.name,
- 'tls_version')
- cipher_list = gajim.config.get_per('accounts', self.name,
- 'cipher_list')
- secure_tuple = (self._current_type, cacerts, mycerts, tls_version, cipher_list)
-
- con = nbxmpp.NonBlockingClient(
- domain=self._hostname,
- caller=self,
- idlequeue=gajim.idlequeue)
-
- self.last_connection = con
- # increase default timeout for server responses
- nbxmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = \
- self.try_connecting_for_foo_secs
- # FIXME: this is a hack; need a better way
- if self.on_connect_success == self._on_new_account:
- con.RegisterDisconnectHandler(self._on_new_account)
-
- if self.client_cert and gajim.config.get_per('accounts', self.name,
- 'client_cert_encrypted'):
- gajim.nec.push_incoming_event(ClientCertPassphraseEvent(
- None, conn=self, con=con, port=port,
- secure_tuple=secure_tuple))
- return
- self.on_client_cert_passphrase('', con, port, secure_tuple)
-
- else:
- self._connect_to_next_host(retry)
-
- def on_client_cert_passphrase(self, passphrase, con, port, secure_tuple):
- self.client_cert_passphrase = passphrase
-
- self.log_hosttype_info(port)
- con.connect(
- hostname=self._current_host['host'],
- port=port,
- on_connect=self.on_connect_success,
- on_proxy_failure=self.on_proxy_failure,
- on_connect_failure=self.connect_to_next_type,
- on_stream_error_cb=self._StreamCB,
- proxy=self._proxy,
- secure_tuple = secure_tuple)
-
- def log_hosttype_info(self, port):
- msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
- self._current_host['host'], port, self._current_type)
- log.info(msg)
- if self._proxy:
- msg = '>>>>>> '
- if self._proxy['type']=='bosh':
- msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri'])
- if self._proxy['type'] in ['http', 'socks5'] or self._proxy['bosh_useproxy']:
- msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port'])
- log.info(msg)
-
- def _connect_failure(self, con_type=None):
- if not con_type:
- # we are not retrying, and not conecting
- if not self.retrycount and self.connected != 0:
- self.disconnect(on_purpose = True)
- if self._proxy:
- pritxt = _('Could not connect to "%(host)s" via proxy "%(proxy)s"') %\
- {'host': self._hostname, 'proxy': self._proxy['host']}
- else:
- pritxt = _('Could not connect to "%(host)s"') % {'host': \
- self._hostname}
- sectxt = _('Check your connection or try again later.')
- if self.streamError:
- # show error dialog
- key = nbxmpp.NS_XMPP_STREAMS + ' ' + self.streamError
- if key in nbxmpp.ERRORS:
- sectxt2 = _('Server replied: %s') % nbxmpp.ERRORS[key][2]
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error', pri_txt=pritxt,
- sec_txt='%s\n%s' % (sectxt2, sectxt)))
- return
- # show popup
- gajim.nec.push_incoming_event(ConnectionLostEvent(None,
- conn=self, title=pritxt, msg=sectxt))
-
- def on_proxy_failure(self, reason):
- log.error('Connection to proxy failed: %s' % reason)
- self.time_to_reconnect = None
- self.on_connect_failure = None
- self.disconnect(on_purpose = True)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Connection to proxy failed'), msg=reason))
-
- def _connect_success(self, con, con_type):
- if not self.connected: # We went offline during connecting process
- # FIXME - not possible, maybe it was when we used threads
- return
- _con_type = con_type
- if _con_type != self._current_type:
- log.info('Connecting to next type beacuse desired is %s and returned is %s'
- % (self._current_type, _con_type))
- self.connect_to_next_type()
- return
- con.RegisterDisconnectHandler(self._on_disconnected)
- if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
- 'action_when_plaintext_connection') == 'warn':
- gajim.nec.push_incoming_event(PlainConnectionEvent(None, conn=self,
- xmpp_client=con))
- return True
- if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
- 'action_when_plaintext_connection') == 'disconnect':
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- return False
- if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \
- and gajim.config.get_per('accounts', self.name,
- 'warn_when_insecure_ssl_connection') and \
- not self.connection_auto_accepted:
- # Pyopenssl is not used
- gajim.nec.push_incoming_event(InsecureSSLConnectionEvent(None,
- conn=self, xmpp_client=con, conn_type=_con_type))
- return True
- return self.connection_accepted(con, con_type)
-
- def connection_accepted(self, con, con_type):
- if not con or not con.Connection:
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not connect to account %s') % self.name,
- msg=_('Connection with account %s has been lost. Retry '
- 'connecting.') % self.name))
- return
- self.hosts = []
- self.connection_auto_accepted = False
- self.connected_hostname = self._current_host['host']
- self.on_connect_failure = None
- con.UnregisterDisconnectHandler(self._on_disconnected)
- con.RegisterDisconnectHandler(self.disconnectedReconnCB)
- log.debug('Connected to server %s:%s with %s' % (
- self._current_host['host'], self._current_host['port'], con_type))
-
- self.last_connection_type = con_type
- if con_type == 'tls' and 'ssl' in self._connection_types:
- # we were about to try ssl after tls, but tls succeed, so
- # definitively stop trying ssl
- con_types = gajim.config.get_per('accounts', self.name,
- 'connection_types').split()
- con_types.remove('ssl')
- gajim.config.set_per('accounts', self.name, 'connection_types',
- ' '.join(con_types))
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
- name = None
- else:
- name = gajim.config.get_per('accounts', self.name, 'name')
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- self.connection = con
- try:
- errnum = con.Connection.ssl_errnum
- except AttributeError:
- errnum = 0
- cert = con.Connection.ssl_certificate
- if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts',
- self.name, 'ignore_ssl_errors').split():
- text = _('The authenticity of the %s certificate could be invalid'
- ) % hostname
- if errnum in ssl_error:
- text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
- else:
- text += _('\nUnknown SSL error: %d') % errnum
- fingerprint_sha1 = cert.digest('sha1').decode('utf-8')
- fingerprint_sha256 = cert.digest('sha256').decode('utf-8')
- pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
- cert).decode('utf-8')
- gajim.nec.push_incoming_event(SSLErrorEvent(None, conn=self,
- error_text=text, error_num=errnum, cert=pem,
- fingerprint_sha1=fingerprint_sha1,
- fingerprint_sha256=fingerprint_sha256, certificate=cert))
- return True
- if cert:
- fingerprint_sha1 = cert.digest('sha1').decode('utf-8')
- fingerprint_sha256 = cert.digest('sha256').decode('utf-8')
- saved_fingerprint_sha1 = gajim.config.get_per('accounts', self.name,
- 'ssl_fingerprint_sha1')
- if saved_fingerprint_sha1:
- # Check sha1 fingerprint
- if fingerprint_sha1 != saved_fingerprint_sha1:
- if not check_X509.check_certificate(cert, hostname):
- gajim.nec.push_incoming_event(FingerprintErrorEvent(
- None, conn=self, certificate=cert,
- new_fingerprint_sha1=fingerprint_sha1,
- new_fingerprint_sha256=fingerprint_sha256))
- return True
- gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1',
- fingerprint_sha1)
-
- saved_fingerprint_sha256 = gajim.config.get_per('accounts', self.name,
- 'ssl_fingerprint_sha256')
- if saved_fingerprint_sha256:
- # Check sha256 fingerprint
- if fingerprint_sha256 != saved_fingerprint_sha256:
- if not check_X509.check_certificate(cert, hostname):
- gajim.nec.push_incoming_event(FingerprintErrorEvent(
- None, conn=self, certificate=cert,
- new_fingerprint_sha1=fingerprint_sha1,
- new_fingerprint_sha256=fingerprint_sha256))
- return True
- gajim.config.set_per('accounts', self.name,
- 'ssl_fingerprint_sha256', fingerprint_sha256)
-
- if not check_X509.check_certificate(cert, hostname) and \
- '100' not in gajim.config.get_per('accounts', self.name,
- 'ignore_ssl_errors').split():
- pem = OpenSSL.crypto.dump_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert).decode('utf-8')
- txt = _('The authenticity of the %s certificate could be '
- 'invalid.\nThe certificate does not cover this domain.') %\
- hostname
- gajim.nec.push_incoming_event(SSLErrorEvent(None, conn=self,
- error_text=txt, error_num=100, cert=pem,
- fingerprint_sha1=fingerprint_sha1,
- fingerprint_sha256=fingerprint_sha256, certificate=cert))
- return True
-
- self._register_handlers(con, con_type)
- auth_mechs = gajim.config.get_per('accounts', self.name, 'authentication_mechanisms')
- auth_mechs = auth_mechs.split()
- for mech in auth_mechs:
- if mech not in nbxmpp.auth_nb.SASL_AUTHENTICATION_MECHANISMS | set(['XEP-0078']):
- log.warning("Unknown authentication mechanisms %s" % mech)
- if len(auth_mechs) == 0:
- auth_mechs = None
- con.auth(user=name, password=self.password,
- resource=self.server_resource, sasl=True, on_auth=self.__on_auth, auth_mechs=auth_mechs)
-
- def ssl_certificate_accepted(self):
- if not self.connection:
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not connect to account %s') % self.name,
- msg=_('Connection with account %s has been lost. Retry '
- 'connecting.') % self.name))
- return
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
- name = None
- else:
- name = gajim.config.get_per('accounts', self.name, 'name')
- self._register_handlers(self.connection, 'ssl')
- self.connection.auth(name, self.password, self.server_resource, 1,
- self.__on_auth)
-
- def _register_handlers(self, con, con_type):
- self.peerhost = con.get_peerhost()
- gajim.con_types[self.name] = con_type
- # notify the gui about con_type
- gajim.nec.push_incoming_event(ConnectionTypeEvent(None,
- conn=self, connection_type=con_type))
- ConnectionHandlers._register_handlers(self, con, con_type)
-
- def __on_auth(self, con, auth):
- if not con:
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not connect to "%s"') % self._hostname,
- msg=_('Check your connection or try again later.')))
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- return
- if not self.connected: # We went offline during connecting process
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- return
- if hasattr(con, 'Resource'):
- self.server_resource = con.Resource
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
- # Get jid given by server
- old_jid = gajim.get_jid_from_account(self.name)
- gajim.config.set_per('accounts', self.name, 'name', con.User)
- new_jid = gajim.get_jid_from_account(self.name)
- gajim.nec.push_incoming_event(AnonymousAuthEvent(None,
- conn=self, old_jid=old_jid, new_jid=new_jid))
- if auth:
- self.last_io = gajim.idlequeue.current_time()
- self.connected = 2
- self.retrycount = 0
- if self.on_connect_auth:
- self.on_connect_auth(con)
- self.on_connect_auth = None
- else:
- if not gajim.config.get_per('accounts', self.name, 'savepass'):
- # Forget password, it's wrong
- self.password = None
- gajim.log.debug("Couldn't authenticate to %s" % self._hostname)
- self.disconnect(on_purpose = True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Authentication failed with "%s"') % \
- self._hostname, sec_txt=_('Please check your login and password'
- ' for correctness.')))
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- # END connect
-
- def add_lang(self, stanza):
- if self.lang:
- stanza.setAttr('xml:lang', self.lang)
-
- def get_privacy_lists(self):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.getPrivacyLists(self.connection)
-
- def send_keepalive(self):
- # nothing received for the last foo seconds
- if self.connection:
- self.connection.send(' ')
-
- def _on_xmpp_ping_answer(self, iq_obj):
- id_ = iq_obj.getAttr('id')
- assert id_ == self.awaiting_xmpp_ping_id
- self.awaiting_xmpp_ping_id = None
-
- def sendPing(self, pingTo=None, control=None):
- """
- Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to
- server to detect connection failure at application level
- If control is set, display result there
- """
- if not gajim.account_is_connected(self.name):
- return
- id_ = self.connection.getAnID()
- if pingTo:
- to = pingTo.get_full_jid()
- gajim.nec.push_incoming_event(PingSentEvent(None, conn=self,
- contact=pingTo))
- else:
- to = gajim.config.get_per('accounts', self.name, 'hostname')
- self.awaiting_xmpp_ping_id = id_
- iq = nbxmpp.Iq('get', to=to)
- iq.addChild(name='ping', namespace=nbxmpp.NS_PING)
- iq.setID(id_)
- def _on_response(resp):
- timePong = time.time()
- if not nbxmpp.isResultNode(resp):
- gajim.nec.push_incoming_event(PingErrorEvent(None, conn=self,
- contact=pingTo))
- return
- timeDiff = round(timePong - timePing, 2)
- gajim.nec.push_incoming_event(PingReplyEvent(None, conn=self,
- contact=pingTo, seconds=timeDiff, control=control))
- if pingTo:
- timePing = time.time()
- self.connection.SendAndCallForResponse(iq, _on_response)
- else:
- self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer)
- gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per(
- 'accounts', self.name, 'time_for_ping_alive_answer'))
-
- def get_active_default_lists(self):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
-
- def del_privacy_list(self, privacy_list):
- if not gajim.account_is_connected(self.name):
- return
- def _on_del_privacy_list_result(result):
- if result:
- gajim.nec.push_incoming_event(PrivacyListRemovedEvent(None,
- conn=self, list_name=privacy_list))
- else:
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Error while removing privacy '
- 'list'), sec_txt=_('Privacy list %s has not been removed. '
- 'It is maybe active in one of your connected resources. '
- 'Deactivate it and try again.') % privacy_list))
- nbxmpp.features_nb.delPrivacyList(self.connection, privacy_list,
- _on_del_privacy_list_result)
-
- def get_privacy_list(self, title):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.getPrivacyList(self.connection, title)
-
- def set_privacy_list(self, listname, tags):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.setPrivacyList(self.connection, listname, tags)
-
- def set_active_list(self, listname):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.setActivePrivacyList(self.connection, listname,
- 'active')
-
- def set_default_list(self, listname):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
-
- def build_privacy_rule(self, name, action, order=1):
- """
- Build a Privacy rule stanza for invisibility
- """
- iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVACY, xmlns='')
- l = iq.setQuery().setTag('list', {'name': name})
- i = l.setTag('item', {'action': action, 'order': str(order)})
- i.setTag('presence-out')
- return iq
-
- def build_invisible_rule(self):
- iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVACY, xmlns='')
- l = iq.setQuery().setTag('list', {'name': 'invisible'})
- if self.name in gajim.interface.status_sent_to_groups and \
- len(gajim.interface.status_sent_to_groups[self.name]) > 0:
- for group in gajim.interface.status_sent_to_groups[self.name]:
- i = l.setTag('item', {'type': 'group', 'value': group,
- 'action': 'allow', 'order': '1'})
- i.setTag('presence-out')
- if self.name in gajim.interface.status_sent_to_users and \
- len(gajim.interface.status_sent_to_users[self.name]) > 0:
- for jid in gajim.interface.status_sent_to_users[self.name]:
- i = l.setTag('item', {'type': 'jid', 'value': jid,
- 'action': 'allow', 'order': '2'})
- i.setTag('presence-out')
- i = l.setTag('item', {'action': 'deny', 'order': '3'})
- i.setTag('presence-out')
- return iq
-
- def set_invisible_rule(self):
- if not gajim.account_is_connected(self.name):
- return
- iq = self.build_invisible_rule()
- self.connection.send(iq)
-
- def get_max_blocked_list_order(self):
- max_order = 0
- for rule in self.blocked_list:
- order = int(rule['order'])
- if order > max_order:
- max_order = order
- return max_order
-
- def block_contacts(self, contact_list, message):
- if self.privacy_default_list is None:
- self.privacy_default_list = 'block'
- if not self.privacy_rules_supported:
- if self.blocking_supported: #XEP-0191
- iq = nbxmpp.Iq('set', xmlns='')
- query = iq.setQuery(name='block')
- query.setNamespace(nbxmpp.NS_BLOCKING)
- for contact in contact_list:
- query.addChild(name='item', attrs={'jid': contact.jid})
- self.connection.send(iq)
- return
- for contact in contact_list:
- self.send_custom_status('offline', message, contact.jid)
- max_order = self.get_max_blocked_list_order()
- new_rule = {'order': str(max_order + 1),
- 'type': 'jid',
- 'action': 'deny',
- 'value': contact.jid}
- self.blocked_list.append(new_rule)
- self.blocked_contacts.append(contact.jid)
- self.set_privacy_list(self.privacy_default_list, self.blocked_list)
- if len(self.blocked_list) == 1:
- self.set_default_list(self.privacy_default_list)
-
- def unblock_contacts(self, contact_list):
- if not self.privacy_rules_supported:
- if self.blocking_supported: #XEP-0191
- iq = nbxmpp.Iq('set', xmlns='')
- query = iq.setQuery(name='unblock')
- query.setNamespace(nbxmpp.NS_BLOCKING)
- for contact in contact_list:
- query.addChild(name='item', attrs={'jid': contact.jid})
- self.connection.send(iq)
- return
- self.new_blocked_list = []
- self.to_unblock = []
- for contact in contact_list:
- self.to_unblock.append(contact.jid)
- if contact.jid in self.blocked_contacts:
- self.blocked_contacts.remove(contact.jid)
- for rule in self.blocked_list:
- if rule['action'] != 'deny' or rule['type'] != 'jid' \
- or rule['value'] not in self.to_unblock:
- self.new_blocked_list.append(rule)
- if len(self.new_blocked_list) == 0:
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.set_default_list('')
- self.del_privacy_list(self.privacy_default_list)
- else:
- self.set_privacy_list(self.privacy_default_list, self.new_blocked_list)
- if not gajim.interface.roster.regroup:
- show = gajim.SHOW_LIST[self.connected]
- else: # accounts merged
- show = helpers.get_global_show()
- if show == 'invisible':
- return
- for contact in contact_list:
- self.send_custom_status(show, self.status, contact.jid)
-
- def block_group(self, group, contact_list, message):
- if not self.privacy_rules_supported:
- return
- self.blocked_groups.append(group)
- for contact in contact_list:
- self.send_custom_status('offline', message, contact.jid)
- max_order = self.get_max_blocked_list_order()
- new_rule = {'order': str(max_order + 1),
- 'type': 'group',
- 'action': 'deny',
- 'value': group}
- self.blocked_list.append(new_rule)
- self.set_privacy_list(self.privacy_default_list, self.blocked_list)
- if len(self.blocked_list) == 1:
- self.set_default_list(self.privacy_default_list)
-
- def unblock_group(self, group, contact_list):
- if not self.privacy_rules_supported:
- return
- if group in self.blocked_groups:
- self.blocked_groups.remove(group)
- self.new_blocked_list = []
- for rule in self.blocked_list:
- if rule['action'] != 'deny' or rule['type'] != 'group' or \
- rule['value'] != group:
- self.new_blocked_list.append(rule)
- if len(self.new_blocked_list) == 0:
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.set_default_list('')
- self.del_privacy_list(self.privacy_default_list)
- else:
- self.set_privacy_list(self.privacy_default_list, self.new_blocked_list)
- if not gajim.interface.roster.regroup:
- show = gajim.SHOW_LIST[self.connected]
- else: # accounts merged
- show = helpers.get_global_show()
- if show == 'invisible':
- return
- for contact in contact_list:
- self.send_custom_status(show, self.status, contact.jid)
-
- def send_invisible_presence(self, msg, signed, initial = False):
- if not gajim.account_is_connected(self.name):
- return
- if not self.privacy_rules_supported:
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=gajim.SHOW_LIST[self.connected]))
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Invisibility not supported'),
- sec_txt=_('Account %s doesn\'t support invisibility.') % \
- self.name))
- return
- # If we are already connected, and privacy rules are supported, send
- # offline presence first as it's required by XEP-0126
- if self.connected > 1 and self.privacy_rules_supported:
- self.on_purpose = True
- p = nbxmpp.Presence(typ='unavailable')
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
- self.remove_all_transfers()
- self.connection.send(p)
-
- # try to set the privacy rule
- iq = self.build_invisible_rule()
- self.connection.SendAndCallForResponse(iq, self._continue_invisible,
- {'msg': msg, 'signed': signed, 'initial': initial})
-
- def _continue_invisible(self, con, iq_obj, msg, signed, initial):
- if iq_obj.getType() == 'error': # server doesn't support privacy lists
- return
- # active the privacy rule
- self.set_active_list('invisible')
- self.connected = gajim.SHOW_LIST.index('invisible')
- self.status = msg
- priority = gajim.get_priority(self.name, 'invisible')
- p = nbxmpp.Presence(priority=priority)
- p = self.add_sha(p, True)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
- self.connection.send(p)
- self.priority = priority
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='invisible'))
- if initial:
- # ask our VCard
- self.request_vcard(None)
-
- # Get bookmarks from private namespace
- self.get_bookmarks()
-
- # Get annotations
- self.get_annotations()
-
- # Inform GUI we just signed in
- gajim.nec.push_incoming_event(SignedInEvent(None, conn=self))
-
- def get_signed_presence(self, msg, callback = None):
- if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'):
- return self.get_signed_msg(msg, callback)
- return ''
-
- def connect_and_auth(self):
- self.on_connect_success = self._connect_success
- self.on_connect_failure = self._connect_failure
- self.connect()
-
- def connect_and_init(self, show, msg, sign_msg):
- self.continue_connect_info = [show, msg, sign_msg]
- self.on_connect_auth = self._discover_server_at_connection
- self.connect_and_auth()
-
- def _discover_server_at_connection(self, con):
- self.connection = con
- if not gajim.account_is_connected(self.name):
- return
- self.connection.set_send_timeout(self.keepalives, self.send_keepalive)
- self.connection.set_send_timeout2(self.pingalives, self.sendPing)
- self.connection.onreceive(None)
-
- self.privacy_rules_requested = False
-
- # If we are not resuming, we ask for discovery info
- # and archiving preferences
- if not self.sm.supports_sm or (not self.sm.resuming and self.sm.enabled):
- self.discoverInfo(gajim.config.get_per('accounts', self.name,
- 'hostname'), id_prefix='Gajim_')
-
- self.sm.resuming = False # back to previous state
- # Discover Stun server(s)
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(hostname),
- self._on_stun_resolved)
-
- def _on_stun_resolved(self, host, result_array):
- if len(result_array) != 0:
- self._stun_servers = self._hosts = [i for i in result_array]
-
- def _request_privacy(self):
- if not gajim.account_is_connected(self.name) or not self.connection:
- return
- iq = nbxmpp.Iq('get', nbxmpp.NS_PRIVACY, xmlns='')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (PRIVACY_ARRIVED, )
- self.connection.send(iq)
-
- def _continue_connection_request_privacy(self):
- if self.privacy_rules_supported:
- if not self.privacy_rules_requested:
- self.privacy_rules_requested = True
- self._request_privacy()
- else:
- if self.continue_connect_info and self.continue_connect_info[0]\
- == 'invisible':
- # Trying to login as invisible but privacy list not
- # supported
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error', pri_txt=_('Invisibility not '
- 'supported'), sec_txt=_('Account %s doesn\'t support '
- 'invisibility.') % self.name))
- return
- if self.blocking_supported:
- iq = nbxmpp.Iq('get', xmlns='')
- query = iq.setQuery(name='blocklist')
- query.setNamespace(nbxmpp.NS_BLOCKING)
- id2_ = self.connection.getAnID()
- iq.setID(id2_)
- self.awaiting_answers[id2_] = (BLOCKING_ARRIVED, )
- self.connection.send(iq)
- # Ask metacontacts before roster
- self.get_metacontacts()
-
- def _nec_agent_info_error_received(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.id_[:6] == 'Gajim_':
- self._continue_connection_request_privacy()
-
- def _nec_agent_info_received(self, obj):
- if obj.conn.name != self.name:
- return
- is_muc = False
- transport_type = ''
- for identity in obj.identities:
- if 'category' in identity and identity['category'] in ('gateway',
- 'headline') and 'type' in identity:
- transport_type = identity['type']
- if 'category' in identity and identity['category'] == 'server' and \
- 'type' in identity and identity['type'] == 'im':
- transport_type = 'jabber' # it's a jabber server
- if 'category' in identity and identity['category'] == 'conference' \
- and 'type' in identity and identity['type'] == 'text':
- is_muc = True
-
- if transport_type != '' and obj.fjid not in gajim.transport_type:
- gajim.transport_type[obj.fjid] = transport_type
- gajim.logger.save_transport_type(obj.fjid, transport_type)
-
- if obj.id_[:6] == 'Gajim_':
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- our_jid = gajim.get_jid_from_account(self.name)
- if obj.fjid == hostname:
- if nbxmpp.NS_GMAILNOTIFY in obj.features:
- gajim.gmail_domains.append(obj.fjid)
- self.request_gmail_notifications()
- if nbxmpp.NS_SECLABEL in obj.features:
- self.seclabel_supported = True
- for identity in obj.identities:
- if identity['category'] == 'pubsub' and identity.get(
- 'type') == 'pep':
- self.pep_supported = True
- break
- if nbxmpp.NS_VCARD in obj.features:
- self.vcard_supported = True
- get_action(self.name + '-profile').set_enabled(True)
- if nbxmpp.NS_REGISTER in obj.features:
- self.register_supported = True
- if nbxmpp.NS_PUBSUB in obj.features:
- self.pubsub_supported = True
- if nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS in obj.features:
- self.pubsub_publish_options_supported = True
- else:
- # Remove stored bookmarks accessible to everyone.
- self.send_pb_purge(our_jid, 'storage:bookmarks')
- self.send_pb_delete(our_jid, 'storage:bookmarks')
- if nbxmpp.NS_MAM_2 in obj.features:
- self.archiving_namespace = nbxmpp.NS_MAM_2
- elif nbxmpp.NS_MAM_1 in obj.features:
- self.archiving_namespace = nbxmpp.NS_MAM_1
- elif nbxmpp.NS_MAM in obj.features:
- self.archiving_namespace = nbxmpp.NS_MAM
- if self.archiving_namespace:
- self.archiving_supported = True
- self.archiving_313_supported = True
- get_action(self.name + '-archive').set_enabled(True)
- if nbxmpp.NS_ARCHIVE in obj.features:
- self.archiving_supported = True
- self.archiving_136_supported = True
- self.request_message_archiving_preferences()
- if nbxmpp.NS_ARCHIVE_AUTO in obj.features:
- self.archive_auto_supported = True
- if nbxmpp.NS_ARCHIVE_MANAGE in obj.features:
- self.archive_manage_supported = True
- if nbxmpp.NS_ARCHIVE_MANUAL in obj.features:
- self.archive_manual_supported = True
- if nbxmpp.NS_ARCHIVE_PREF in obj.features:
- self.archive_pref_supported = True
- if nbxmpp.NS_BLOCKING in obj.features:
- self.blocking_supported = True
- if nbxmpp.NS_ADDRESS in obj.features:
- self.addressing_supported = True
- if nbxmpp.NS_CARBONS in obj.features and gajim.config.get_per(
- 'accounts', self.name, 'enable_message_carbons'):
- self.carbons_enabled = True
- # Server supports carbons, activate it
- iq = nbxmpp.Iq('set')
- iq.setTag('enable', namespace=nbxmpp.NS_CARBONS)
- self.connection.send(iq)
- if nbxmpp.NS_PRIVACY in obj.features:
- self.privacy_rules_supported = True
- get_action(self.name + '-privacylists').set_enabled(True)
-
- if nbxmpp.NS_BYTESTREAM in obj.features and \
- gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
- our_fjid = helpers.parse_jid(our_jid + '/' + \
- self.server_resource)
- testit = gajim.config.get_per('accounts', self.name,
- 'test_ft_proxies_on_startup')
- gajim.proxy65_manager.resolve(obj.fjid, self.connection,
- our_fjid, default=self.name, testit=testit)
- if nbxmpp.NS_MUC in obj.features and is_muc:
- type_ = transport_type or 'jabber'
- self.muc_jid[type_] = obj.fjid
- if transport_type:
- if transport_type in self.available_transports:
- self.available_transports[transport_type].append(obj.fjid)
- else:
- self.available_transports[transport_type] = [obj.fjid]
- self._continue_connection_request_privacy()
-
- def send_custom_status(self, show, msg, jid):
- if not show in gajim.SHOW_LIST:
- return -1
- if not gajim.account_is_connected(self.name):
- return
- sshow = helpers.get_xmpp_show(show)
- if not msg:
- msg = ''
- if show == 'offline':
- p = nbxmpp.Presence(typ='unavailable', to=jid)
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
- else:
- signed = self.get_signed_presence(msg)
- priority = gajim.get_priority(self.name, sshow)
- p = nbxmpp.Presence(typ=None, priority=priority, show=sshow, to=jid)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
- self.connection.send(p)
-
- def _change_to_invisible(self, msg):
- signed = self.get_signed_presence(msg)
- self.send_invisible_presence(msg, signed)
-
- def _change_from_invisible(self):
- if self.privacy_rules_supported:
- self.set_active_list('')
-
- def _update_status(self, show, msg):
- xmpp_show = helpers.get_xmpp_show(show)
- priority = gajim.get_priority(self.name, xmpp_show)
- p = nbxmpp.Presence(typ=None, priority=priority, show=xmpp_show)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- signed = self.get_signed_presence(msg)
- if signed:
- p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
- if self.connection:
- self.connection.send(p)
- self.priority = priority
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=show))
-
- def send_motd(self, jid, subject='', msg='', xhtml=None):
- if not gajim.account_is_connected(self.name):
- return
- msg_iq = nbxmpp.Message(to=jid, body=msg, subject=subject,
- xhtml=xhtml)
-
- self.connection.send(msg_iq)
-
- def _nec_message_outgoing(self, obj):
- if obj.account != self.name:
- return
-
- self._prepare_message(obj)
-
- def _nec_stanza_message_outgoing(self, obj):
- if obj.conn.name != self.name:
- return
-
- config_key = '%s-%s' % (self.name, obj.jid)
- encryption = gajim.config.get_per('encryption', config_key, 'encryption')
- if encryption:
- gajim.plugin_manager.extension_point(
- 'encrypt' + encryption, self, obj, self.send_message)
- else:
- self.send_message(obj)
-
- def send_message(self, obj):
- obj.msg_id = self.connection.send(obj.msg_iq, now=obj.now)
-
- gajim.nec.push_incoming_event(MessageSentEvent(
- None, conn=self, jid=obj.jid, message=obj.message, keyID=obj.keyID,
- chatstate=obj.chatstate, automatic_message=obj.automatic_message,
- msg_id=obj.msg_id, additional_data=obj.additional_data))
- if obj.callback:
- obj.callback(obj, obj.msg_iq, *obj.callback_args)
-
- if isinstance(obj.jid, list):
- for j in obj.jid:
- if obj.session is None:
- obj.session = self.get_or_create_session(j, '')
- self.log_message(obj, j)
- else:
- self.log_message(obj, obj.jid)
-
- def send_contacts(self, contacts, fjid, type_='message'):
- """
- Send contacts with RosterX (Xep-0144)
- """
- if not gajim.account_is_connected(self.name):
- return
- if type_ == 'message':
- if len(contacts) == 1:
- msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(),
- contacts[0].get_shown_name())
- else:
- msg = _('Sent contacts:')
- for contact in contacts:
- msg += '\n "%s" (%s)' % (contact.get_full_jid(),
- contact.get_shown_name())
- stanza = nbxmpp.Message(to=gajim.get_jid_without_resource(fjid),
- body=msg)
- elif type_ == 'iq':
- stanza = nbxmpp.Iq(to=fjid, typ='set')
- x = stanza.addChild(name='x', namespace=nbxmpp.NS_ROSTERX)
- for contact in contacts:
- x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid,
- 'name': contact.get_shown_name()})
- self.connection.send(stanza)
-
- def send_stanza(self, stanza):
- """
- Send a stanza untouched
- """
- if not self.connection:
- return
- self.connection.send(stanza)
-
- def ack_subscribed(self, jid):
- if not gajim.account_is_connected(self.name):
- return
- log.debug('ack\'ing subscription complete for %s' % jid)
- p = nbxmpp.Presence(jid, 'subscribe')
- self.connection.send(p)
-
- def ack_unsubscribed(self, jid):
- if not gajim.account_is_connected(self.name):
- return
- log.debug('ack\'ing unsubscription complete for %s' % jid)
- p = nbxmpp.Presence(jid, 'unsubscribe')
- self.connection.send(p)
-
- def request_subscription(self, jid, msg='', name='', groups=None,
- auto_auth=False, user_nick=''):
- if not gajim.account_is_connected(self.name):
- return
- if groups is None:
- groups = []
- log.debug('subscription request for %s' % jid)
- if auto_auth:
- self.jids_for_auto_auth.append(jid)
- # RFC 3921 section 8.2
- infos = {'jid': jid}
- if name:
- infos['name'] = name
- iq = nbxmpp.Iq('set', nbxmpp.NS_ROSTER)
- q = iq.setQuery()
- item = q.addChild('item', attrs=infos)
- for g in groups:
- item.addChild('group').setData(g)
- self.connection.send(iq)
-
- p = nbxmpp.Presence(jid, 'subscribe')
- if user_nick:
- p.setTag('nick', namespace = nbxmpp.NS_NICK).setData(user_nick)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- self.connection.send(p)
-
- def send_authorization(self, jid):
- if not gajim.account_is_connected(self.name):
- return
- p = nbxmpp.Presence(jid, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
-
- def refuse_authorization(self, jid):
- if not gajim.account_is_connected(self.name):
- return
- p = nbxmpp.Presence(jid, 'unsubscribed')
- p = self.add_sha(p)
- self.connection.send(p)
-
- def unsubscribe(self, jid, remove_auth = True):
- if not gajim.account_is_connected(self.name):
- return
- if remove_auth:
- self.connection.getRoster().delItem(jid)
- jid_list = gajim.config.get_per('contacts')
- for j in jid_list:
- if j.startswith(jid):
- gajim.config.del_per('contacts', j)
- else:
- self.connection.getRoster().Unsubscribe(jid)
- self.update_contact(jid, '', [])
-
- def unsubscribe_agent(self, agent):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq('set', nbxmpp.NS_REGISTER, to=agent)
- iq.setQuery().setTag('remove')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
- self.connection.send(iq)
- self.connection.getRoster().delItem(agent)
-
- def send_new_account_infos(self, form, is_form):
- if is_form:
- # Get username and password and put them in new_account_info
- for field in form.iter_fields():
- if field.var == 'username':
- self.new_account_info['name'] = field.value
- if field.var == 'password':
- self.new_account_info['password'] = field.value
- else:
- # Get username and password and put them in new_account_info
- if 'username' in form:
- self.new_account_info['name'] = form['username']
- if 'password' in form:
- self.new_account_info['password'] = form['password']
- self.new_account_form = form
- self.new_account(self.name, self.new_account_info)
-
- def new_account(self, name, config, sync=False):
- # If a connection already exist we cannot create a new account
- if self.connection:
- return
- self._hostname = config['hostname']
- self.new_account_info = config
- self.name = name
- self.on_connect_success = self._on_new_account
- self.on_connect_failure = self._on_new_account
- self.connect(config)
-
- def _on_new_account(self, con=None, con_type=None):
- if not con_type:
- if len(self._connection_types) or len(self._hosts):
- # There are still other way to try to connect
- return
- reason = _('Could not connect to "%s"') % self._hostname
- gajim.nec.push_incoming_event(NewAccountNotConnectedEvent(None,
- conn=self, reason=reason))
- return
- self.on_connect_failure = None
- self.connection = con
- nbxmpp.features_nb.getRegInfo(con, self._hostname)
-
- def request_os_info(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not gajim.account_is_connected(self.name):
- return
- # If we are invisible, do not request
- if self.connected == gajim.SHOW_LIST.index('invisible'):
- self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status')))
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = nbxmpp.Iq(to=to_whom_jid, typ='get', queryNS=nbxmpp.NS_VERSION)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.version_ids.append(id_)
- self.connection.send(iq)
-
- def request_entity_time(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not gajim.account_is_connected(self.name):
- return
- # If we are invisible, do not request
- if self.connected == gajim.SHOW_LIST.index('invisible'):
- self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status')))
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = nbxmpp.Iq(to=to_whom_jid, typ='get')
- iq.addChild('time', namespace=nbxmpp.NS_TIME_REVISED)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.entity_time_ids.append(id_)
- self.connection.send(iq)
-
- def request_gateway_prompt(self, jid, prompt=None):
- def _on_prompt_result(resp):
- gajim.nec.push_incoming_event(GatewayPromptReceivedEvent(None,
- conn=self, stanza=resp))
- if prompt:
- typ_ = 'set'
- else:
- typ_ = 'get'
- iq = nbxmpp.Iq(typ=typ_, to=jid)
- query = iq.addChild(name='query', namespace=nbxmpp.NS_GATEWAY)
- if prompt:
- query.setTagData('prompt', prompt)
- self.connection.SendAndCallForResponse(iq, _on_prompt_result)
-
- def get_settings(self):
- """
- Get Gajim settings as described in XEP 0049
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='gajim', namespace='gajim:prefs')
- self.connection.send(iq)
-
- def seclabel_catalogue(self, to, callback):
- if not gajim.account_is_connected(self.name):
- return
- self.seclabel_catalogue_request(to, callback)
- server = gajim.get_jid_from_account(self.name).split("@")[1] # Really, no better way?
- iq = nbxmpp.Iq(typ='get', to=server)
- iq2 = iq.addChild(name='catalog', namespace=nbxmpp.NS_SECLABEL_CATALOG)
- iq2.setAttr('to', to)
- self.connection.send(iq)
-
- def _nec_privacy_list_received(self, obj):
- roster = gajim.interface.roster
- if obj.conn.name != self.name:
- return
- if obj.list_name != self.privacy_default_list:
- return
- self.blocked_contacts = []
- self.blocked_groups = []
- self.blocked_list = []
- self.blocked_all = False
- for rule in obj.rules:
- if rule['action'] == 'allow':
- if not 'type' in rule:
- self.blocked_all = False
- elif rule['type'] == 'jid' and rule['value'] in \
- self.blocked_contacts:
- self.blocked_contacts.remove(rule['value'])
- elif rule['type'] == 'group' and rule['value'] in \
- self.blocked_groups:
- self.blocked_groups.remove(rule['value'])
- elif rule['action'] == 'deny':
- if not 'type' in rule:
- self.blocked_all = True
- elif rule['type'] == 'jid' and rule['value'] not in \
- self.blocked_contacts:
- self.blocked_contacts.append(rule['value'])
- elif rule['type'] == 'group' and rule['value'] not in \
- self.blocked_groups:
- self.blocked_groups.append(rule['value'])
- self.blocked_list.append(rule)
-
- if 'type' in rule:
- if rule['type'] == 'jid':
- roster.draw_contact(rule['value'], self.name)
- if rule['type'] == 'group':
- roster.draw_group(rule['value'], self.name)
-
- def _request_bookmarks_xml(self):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:bookmarks')
- self.connection.send(iq)
-
- def _check_bookmarks_received(self):
- if not self.bookmarks:
- self._request_bookmarks_xml()
-
- def get_bookmarks(self, storage_type=None):
- """
- Get Bookmarks from storage or PubSub if supported as described in XEP
- 0048
-
- storage_type can be set to xml to force request to xml storage
- """
- if not gajim.account_is_connected(self.name):
- return
- if self.pubsub_supported and self.pubsub_publish_options_supported \
- and storage_type != 'xml':
- self.send_pb_retrieve('', 'storage:bookmarks')
- # some server (ejabberd) are so slow to answer that we request via XML
- # if we don't get answer in the next 30 seconds
- gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30)
- else:
- self._request_bookmarks_xml()
-
- def store_bookmarks(self, storage_type=None):
- """
- Send bookmarks to the storage namespace or PubSub if supported
-
- storage_type can be set to 'pubsub' or 'xml' so store in only one method
- else it will be stored on both
- """
- NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks'
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'})
- for bm in self.bookmarks:
- iq2 = iq.addChild(name="conference")
- iq2.setAttr('jid', bm['jid'])
- iq2.setAttr('autojoin', bm['autojoin'])
- iq2.setAttr('name', bm['name'])
- iq2.setTag('minimize', namespace=NS_GAJIM_BM). \
- setData(bm['minimize'])
- # Only add optional elements if not empty
- # Note: need to handle both None and '' as empty
- # thus shouldn't use "is not None"
- if bm.get('nick', None):
- iq2.setTagData('nick', bm['nick'])
- if bm.get('password', None):
- iq2.setTagData('password', bm['password'])
- if bm.get('print_status', None):
- iq2.setTag('print_status', namespace=NS_GAJIM_BM). \
- setData(bm['print_status'])
-
- if self.pubsub_supported and self.pubsub_publish_options_supported and\
- storage_type != 'xml':
- options = nbxmpp.Node(nbxmpp.NS_DATA + ' x',
- attrs={'type': 'submit'})
- f = options.addChild('field',
- attrs={'var': 'FORM_TYPE', 'type': 'hidden'})
- f.setTagData('value', nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS)
- f = options.addChild('field',
- attrs={'var': 'pubsub#persist_items'})
- f.setTagData('value', 'true')
- f = options.addChild('field', attrs={'var': 'pubsub#access_model'})
- f.setTagData('value', 'whitelist')
- self.send_pb_publish('', 'storage:bookmarks', iq, 'current',
- options=options)
- if storage_type != 'pubsub':
- iqA = nbxmpp.Iq(typ='set')
- iqB = iqA.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iqB.addChild(node=iq)
- self.connection.send(iqA)
-
- def get_annotations(self):
- """
- Get Annonations from storage as described in XEP 0048, and XEP 0145
- """
- self.annotations = {}
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:rosternotes')
- self.connection.send(iq)
-
- def store_annotations(self):
- """
- Set Annonations in private storage as described in XEP 0048, and XEP 0145
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes')
- for jid in self.annotations.keys():
- if self.annotations[jid]:
- iq4 = iq3.addChild(name = "note")
- iq4.setAttr('jid', jid)
- iq4.setData(self.annotations[jid])
- self.connection.send(iq)
-
- def get_roster_delimiter(self):
- """
- Get roster group delimiter from storage as described in XEP 0083
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='roster', namespace='roster:delimiter')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (DELIMITER_ARRIVED, )
- self.connection.send(iq)
-
- def set_roster_delimiter(self, delimiter='::'):
- """
- Set roster group delimiter to the storage namespace
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq3 = iq2.addChild(name='roster', namespace='roster:delimiter')
- iq3.setData(delimiter)
-
- self.connection.send(iq)
-
- def get_metacontacts(self):
- """
- Get metacontacts list from storage as described in XEP 0049
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:metacontacts')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, )
- self.connection.send(iq)
-
- def store_metacontacts(self, tags_list):
- """
- Send meta contacts to the storage namespace
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts')
- for tag in tags_list:
- for data in tags_list[tag]:
- jid = data['jid']
- dict_ = {'jid': jid, 'tag': tag}
- if 'order' in data:
- dict_['order'] = data['order']
- iq3.addChild(name='meta', attrs=dict_)
- self.connection.send(iq)
-
- def request_roster(self):
- version = None
- features = self.connection.Dispatcher.Stream.features
- if features and features.getTag('ver',
- namespace=nbxmpp.NS_ROSTER_VER):
- version = gajim.config.get_per('accounts', self.name,
- 'roster_version')
- if version and not gajim.contacts.get_contacts_jid_list(
- self.name):
- gajim.config.set_per('accounts', self.name, 'roster_version',
- '')
- version = None
-
- iq_id = self.connection.initRoster(version=version)
- self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
-
- def send_agent_status(self, agent, ptype):
- if not gajim.account_is_connected(self.name):
- return
- show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- p = nbxmpp.Presence(to=agent, typ=ptype, show=show)
- p = self.add_sha(p, ptype != 'unavailable')
- self.connection.send(p)
-
- def send_captcha(self, jid, form_node):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=jid)
- captcha = iq.addChild(name='captcha', namespace=nbxmpp.NS_CAPTCHA)
- captcha.addChild(node=form_node)
- self.connection.send(iq)
-
- def check_unique_room_id_support(self, server, instance):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get', to=server)
- iq.setAttr('id', 'unique1')
- iq.addChild('unique', namespace=nbxmpp.NS_MUC_UNIQUE)
- def _on_response(resp):
- if not nbxmpp.isResultNode(resp):
- gajim.nec.push_incoming_event(UniqueRoomIdNotSupportedEvent(
- None, conn=self, instance=instance, server=server))
- return
- gajim.nec.push_incoming_event(UniqueRoomIdSupportedEvent(None,
- conn=self, instance=instance, server=server,
- room_id=resp.getTag('unique').getData()))
- self.connection.SendAndCallForResponse(iq, _on_response)
-
- def join_gc(self, nick, room_jid, password, change_nick=False,
- rejoin=False):
- # FIXME: This room JID needs to be normalized; see #1364
- if not gajim.account_is_connected(self.name):
- return
- show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- if show == 'invisible':
- # Never join a room when invisible
- return
-
- # last date/time in history to avoid duplicate
- if room_jid not in self.last_history_time:
- # Not in memory, get it from DB
- last_log = None
- # Do not check if we are not logging for this room
- if gajim.config.should_log(self.name, room_jid):
- # Check time first in the FAST table
- last_log = gajim.logger.get_room_last_message_time(room_jid)
- if last_log is None:
- # Not in special table, get it from messages DB
- last_log = gajim.logger.get_last_date_that_has_logs(room_jid,
- is_room=True)
- # Create self.last_history_time[room_jid] even if not logging,
- # could be used in connection_handlers
- if last_log is None:
- last_log = 0
- self.last_history_time[room_jid] = last_log
-
- p = nbxmpp.Presence(to='%s/%s' % (room_jid, nick),
- show=show, status=self.status)
- h = hmac.new(self.secret_hmac, room_jid.encode('utf-8'), hashlib.md5).\
- hexdigest()[:6]
- id_ = self.connection.getAnID()
- id_ = 'gajim_muc_' + id_ + '_' + h
- p.setID(id_)
- if gajim.config.get('send_sha_in_gc_presence'):
- p = self.add_sha(p)
- self.add_lang(p)
- if not change_nick:
- t = p.setTag(nbxmpp.NS_MUC + ' x')
- tags = {}
- timeout = gajim.config.get_per('rooms', room_jid,
- 'muc_restore_timeout')
- if timeout is None or timeout == -2:
- timeout = gajim.config.get('muc_restore_timeout')
- timeout *= 60
- if timeout >= 0:
- last_date = self.last_history_time[room_jid]
- if last_date == 0:
- last_date = time.time() - timeout
- elif not rejoin:
- last_date = min(last_date, time.time() - timeout)
- last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(
- last_date))
- tags['since'] = last_date
- nb = gajim.config.get_per('rooms', room_jid, 'muc_restore_lines')
- if nb is None or nb == -2:
- nb = gajim.config.get('muc_restore_lines')
- if nb >= 0:
- tags['maxstanzas'] = nb
- if tags:
- t.setTag('history', tags)
- if password:
- t.setTagData('password', password)
- self.connection.send(p)
-
- def _nec_gc_message_outgoing(self, obj):
- if obj.account != self.name:
- return
- if not gajim.account_is_connected(self.name):
- return
-
- if not obj.xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- obj.xhtml = create_xhtml(obj.message)
-
- msg_iq = nbxmpp.Message(obj.jid, obj.message, typ='groupchat',
- xhtml=obj.xhtml)
-
- if obj.correct_id:
- msg_iq.setTag('replace', attrs={'id': obj.correct_id},
- namespace=nbxmpp.NS_CORRECT)
-
- if obj.chatstate:
- msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
- if obj.label is not None:
- msg_iq.addChild(node=obj.label)
-
- obj.msg_iq = msg_iq
- obj.conn = self
- gajim.nec.push_incoming_event(GcStanzaMessageOutgoingEvent(None, **vars(obj)))
-
- def _nec_gc_stanza_message_outgoing(self, obj):
- if obj.conn.name != self.name:
- return
-
- config_key = '%s-%s' % (self.name, obj.jid)
- encryption = gajim.config.get_per('encryption', config_key, 'encryption')
- if encryption:
- gajim.plugin_manager.extension_point(
- 'gc_encrypt' + encryption, self, obj, self.send_gc_message)
- else:
- self.send_gc_message(obj)
-
- def send_gc_message(self, obj):
- obj.msg_id = self.connection.send(obj.msg_iq)
- gajim.nec.push_incoming_event(MessageSentEvent(
- None, conn=self, jid=obj.jid, message=obj.message, keyID=None,
- chatstate=None, automatic_message=obj.automatic_message,
- msg_id=obj.msg_id, additional_data=obj.additional_data))
- if obj.callback:
- obj.callback(obj)
-
- def send_gc_subject(self, jid, subject):
- if not gajim.account_is_connected(self.name):
- return
- msg_iq = nbxmpp.Message(jid, typ='groupchat', subject=subject)
- self.connection.send(msg_iq)
-
- def request_gc_config(self, room_jid):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get', queryNS=nbxmpp.NS_MUC_OWNER,
- to=room_jid)
- self.add_lang(iq)
- self.connection.send(iq)
-
- def destroy_gc_room(self, room_jid, reason = '', jid = ''):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', queryNS=nbxmpp.NS_MUC_OWNER,
- to=room_jid)
- destroy = iq.setQuery().setTag('destroy')
- if reason:
- destroy.setTagData('reason', reason)
- if jid:
- destroy.setAttr('jid', jid)
- self.connection.send(iq)
-
- def send_gc_status(self, nick, jid, show, status):
- if not gajim.account_is_connected(self.name):
- return
- if show == 'invisible':
- show = 'offline'
- ptype = None
- if show == 'offline':
- ptype = 'unavailable'
- xmpp_show = helpers.get_xmpp_show(show)
- p = nbxmpp.Presence(to='%s/%s' % (jid, nick), typ=ptype,
- show=xmpp_show, status=status)
- h = hmac.new(self.secret_hmac, jid.encode('utf-8'), hashlib.md5).\
- hexdigest()[:6]
- id_ = self.connection.getAnID()
- id_ = 'gajim_muc_' + id_ + '_' + h
- p.setID(id_)
- if gajim.config.get('send_sha_in_gc_presence') and show != 'offline':
- p = self.add_sha(p, ptype != 'unavailable')
- self.add_lang(p)
- # send instantly so when we go offline, status is sent to gc before we
- # disconnect from jabber server
- self.connection.send(p)
-
- def gc_got_disconnected(self, room_jid):
- """
- A groupchat got disconnected. This can be or purpose or not
-
- Save the time we had last message to avoid duplicate logs AND be faster
- than get that date from DB. Save time that we have in mem in a small
- table (with fast access)
- """
- gajim.logger.set_room_last_message_time(room_jid, self.last_history_time[room_jid])
-
- def gc_set_role(self, room_jid, nick, role, reason=''):
- """
- Role is for all the life of the room so it's based on nick
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
- item = iq.setQuery().setTag('item')
- item.setAttr('nick', nick)
- item.setAttr('role', role)
- if reason:
- item.addChild(name='reason', payload=reason)
- self.connection.send(iq)
-
- def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
- """
- Affiliation is for all the life of the room so it's based on jid
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
- item = iq.setQuery().setTag('item')
- item.setAttr('jid', jid)
- item.setAttr('affiliation', affiliation)
- if reason:
- item.addChild(name = 'reason', payload = reason)
- self.connection.send(iq)
-
- def send_gc_affiliation_list(self, room_jid, users_dict):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
- item = iq.setQuery()
- for jid in users_dict:
- item_tag = item.addChild('item', {'jid': jid,
- 'affiliation': users_dict[jid]['affiliation']})
- if 'reason' in users_dict[jid] and users_dict[jid]['reason']:
- item_tag.setTagData('reason', users_dict[jid]['reason'])
- self.connection.send(iq)
-
- def get_affiliation_list(self, room_jid, affiliation):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
- item = iq.setQuery().setTag('item')
- item.setAttr('affiliation', affiliation)
- self.connection.send(iq)
-
- def send_gc_config(self, room_jid, form):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_OWNER)
- query = iq.setQuery()
- form.setAttr('type', 'submit')
- query.addChild(node = form)
- self.connection.send(iq)
-
- def change_password(self, password):
- if not gajim.account_is_connected(self.name):
- return
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- username = gajim.config.get_per('accounts', self.name, 'name')
- iq = nbxmpp.Iq(typ='set', to=hostname)
- q = iq.setTag(nbxmpp.NS_REGISTER + ' query')
- q.setTagData('username', username)
- q.setTagData('password', password)
- self.connection.send(iq)
-
- def get_password(self, callback, type_):
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth') and \
- type_ != 'ANONYMOUS':
- gajim.nec.push_incoming_event(NonAnonymousServerErrorEvent(None,
- conn=self))
- self._on_disconnected()
- return
- self.pasword_callback = (callback, type_)
- if type_ == 'X-MESSENGER-OAUTH2':
- client_id = gajim.config.get_per('accounts', self.name,
- 'oauth2_client_id')
- refresh_token = gajim.config.get_per('accounts', self.name,
- 'oauth2_refresh_token')
- if refresh_token:
- renew_URL = 'https://oauth.live.com/token?client_id=' \
- '%(client_id)s&redirect_uri=https%%3A%%2F%%2Foauth.live.' \
- 'com%%2Fdesktop&grant_type=refresh_token&refresh_token=' \
- '%(refresh_token)s' % locals()
- result = helpers.download_image(self.name, {'src': renew_URL})[0]
- if result:
- dict_ = json.loads(result)
- if 'access_token' in dict_:
- self.set_password(dict_['access_token'])
- return
- script_url = gajim.config.get_per('accounts', self.name,
- 'oauth2_redirect_url')
- token_URL = 'https://oauth.live.com/authorize?client_id=' \
- '%(client_id)s&scope=wl.messenger%%20wl.offline_access&' \
- 'response_type=code&redirect_uri=%(script_url)s' % locals()
- helpers.launch_browser_mailer('url', token_URL)
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(Oauth2CredentialsRequiredEvent(None,
- conn=self))
- return
- if self.password:
- self.set_password(self.password)
- return
- gajim.nec.push_incoming_event(PasswordRequiredEvent(None, conn=self))
-
- def set_password(self, password):
- self.password = password
- if self.pasword_callback:
- callback, type_ = self.pasword_callback
- if self._current_type == 'plain' and type_ == 'PLAIN' and \
- gajim.config.get_per('accounts', self.name,
- 'warn_when_insecure_password'):
- gajim.nec.push_incoming_event(InsecurePasswordEvent(None,
- conn=self))
- return
- callback(password)
- self.pasword_callback = None
-
- def accept_insecure_password(self):
- if self.pasword_callback:
- callback, type_ = self.pasword_callback
- callback(self.password)
- self.pasword_callback = None
-
- def unregister_account(self, on_remove_success):
- # no need to write this as a class method and keep the value of
- # on_remove_success as a class property as pass it as an argument
- def _on_unregister_account_connect(con):
- self.on_connect_auth = None
- if gajim.account_is_connected(self.name):
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- iq = nbxmpp.Iq(typ='set', to=hostname)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- iq.setTag(nbxmpp.NS_REGISTER + ' query').setTag('remove')
- def _on_answer(con, result):
- if result.getID() == id_:
- on_remove_success(True)
- return
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error',
- pri_txt=_('Unregister failed'),
- sec_txt=_('Unregistration with server %(server)s '
- 'failed: %(error)s') % {'server': hostname,
- 'error': result.getErrorMsg()}))
- on_remove_success(False)
- con.RegisterHandler('iq', _on_answer, 'result', system=True)
- con.SendAndWaitForResponse(iq)
- return
- on_remove_success(False)
- if self.connected == 0:
- self.on_connect_auth = _on_unregister_account_connect
- self.connect_and_auth()
- else:
- _on_unregister_account_connect(self.connection)
-
- def send_invite(self, room, to, reason='', continue_tag=False):
- """
- Send invitation
- """
- if not gajim.account_is_connected(self.name):
- return
- contact = gajim.contacts.get_contact_from_full_jid(self.name, to)
- if contact and contact.supports(nbxmpp.NS_CONFERENCE):
- # send direct invite
- message=nbxmpp.Message(to=to)
- attrs = {'jid': room}
- if reason:
- attrs['reason'] = reason
- if continue_tag:
- attrs['continue'] = 'true'
- password = gajim.gc_passwords.get(room, '')
- if password:
- attrs['password'] = password
- c = message.addChild(name='x', attrs=attrs,
- namespace=nbxmpp.NS_CONFERENCE)
- self.connection.send(message)
- return
- message=nbxmpp.Message(to=room)
- c = message.addChild(name='x', namespace=nbxmpp.NS_MUC_USER)
- c = c.addChild(name='invite', attrs={'to': to})
- if continue_tag:
- c.addChild(name='continue')
- if reason != '':
- c.setTagData('reason', reason)
- self.connection.send(message)
-
- def decline_invitation(self, room, to, reason=''):
- """
- decline a groupchat invitation
- """
- if not gajim.account_is_connected(self.name):
- return
- message=nbxmpp.Message(to=room)
- c = message.addChild(name='x', namespace=nbxmpp.NS_MUC_USER)
- c = c.addChild(name='decline', attrs={'to': to})
- if reason != '':
- c.setTagData('reason', reason)
- self.connection.send(message)
-
- def request_voice(self, room):
- """
- Request voice in a moderated room
- """
- if not gajim.account_is_connected(self.name):
- return
- message = nbxmpp.Message(to=room)
-
- x = nbxmpp.DataForm(typ='submit')
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value=nbxmpp.NS_MUC + '#request'))
- x.addChild(node=nbxmpp.DataField(name='muc#role', value='participant',
- typ='text-single'))
-
- message.addChild(node=x)
-
- self.connection.send(message)
-
- def check_pingalive(self):
- if not gajim.config.get_per('accounts', self.name, 'active'):
- # Account may have been disabled
- return
- if self.awaiting_xmpp_ping_id:
- # We haven't got the pong in time, disco and reconnect
- log.warning("No reply received for keepalive ping. Reconnecting.")
- self.disconnectedReconnCB()
-
- def _reconnect_alarm(self):
- if not gajim.config.get_per('accounts', self.name, 'active'):
- # Account may have been disabled
- return
- if self.time_to_reconnect:
- if self.connected < 2:
- self.reconnect()
- else:
- self.time_to_reconnect = None
-
- def request_search_fields(self, jid):
- iq = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_SEARCH)
- self.connection.send(iq)
-
- def send_search_form(self, jid, form, is_form):
- iq = nbxmpp.Iq(typ='set', to=jid, queryNS=nbxmpp.NS_SEARCH)
- item = iq.setQuery()
- if is_form:
- item.addChild(node=form)
- else:
- for i in form.keys():
- item.setTagData(i, form[i])
- def _on_response(resp):
- gajim.nec.push_incoming_event(SearchResultReceivedEvent(None,
- conn=self, stanza=resp))
-
- self.connection.SendAndCallForResponse(iq, _on_response)
-
- def load_roster_from_db(self):
- gajim.nec.push_incoming_event(RosterReceivedEvent(None, conn=self))
-
-# END Connection
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
deleted file mode 100644
index fe024b54b..000000000
--- a/src/common/connection_handlers.py
+++ /dev/null
@@ -1,2297 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/connection_handlers.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Junglecow J <junglecow AT gmail.com>
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import os
-import base64
-import operator
-import hashlib
-
-from time import (altzone, daylight, gmtime, localtime, strftime,
- time as time_time, timezone, tzname)
-
-from gi.repository import GLib
-
-import nbxmpp
-from common import caps_cache as capscache
-
-from common.pep import LOCATION_DATA
-from common import helpers
-from common import gajim
-from common import exceptions
-from common import dataforms
-from common import jingle_xtls
-from common.commands import ConnectionCommands
-from common.pubsub import ConnectionPubSub
-from common.protocol.caps import ConnectionCaps
-from common.protocol.bytestream import ConnectionSocks5Bytestream
-from common.protocol.bytestream import ConnectionIBBytestream
-from common.message_archiving import ConnectionArchive136
-from common.message_archiving import ConnectionArchive313
-from common.connection_handlers_events import *
-
-from common import ged
-from common import nec
-from common.nec import NetworkEvent
-
-from common.jingle import ConnectionJingle
-
-import logging
-log = logging.getLogger('gajim.c.connection_handlers')
-
-# kind of events we can wait for an answer
-VCARD_PUBLISHED = 'vcard_published'
-VCARD_ARRIVED = 'vcard_arrived'
-AGENT_REMOVED = 'agent_removed'
-METACONTACTS_ARRIVED = 'metacontacts_arrived'
-ROSTER_ARRIVED = 'roster_arrived'
-DELIMITER_ARRIVED = 'delimiter_arrived'
-PRIVACY_ARRIVED = 'privacy_arrived'
-BLOCKING_ARRIVED = 'blocking_arrived'
-PEP_CONFIG = 'pep_config'
-HAS_IDLE = True
-try:
-# import idle
- import common.sleepy
-except Exception:
- log.debug(_('Unable to load idle module'))
- HAS_IDLE = False
-
-
-class ConnectionDisco:
- """
- Holds xmpppy handlers and public methods for discover services
- """
-
- def discoverItems(self, jid, node=None, id_prefix=None):
- """
- According to XEP-0030:
- jid is mandatory;
- name, node, action is optional.
- """
- id_ = self._discover(nbxmpp.NS_DISCO_ITEMS, jid, node, id_prefix)
- self.disco_items_ids.append(id_)
-
- def discoverInfo(self, jid, node=None, id_prefix=None):
- """
- According to XEP-0030:
- For identity: category, type is mandatory, name is optional.
- For feature: var is mandatory.
- """
- id_ = self._discover(nbxmpp.NS_DISCO_INFO, jid, node, id_prefix)
- self.disco_info_ids.append(id_)
-
- def request_register_agent_info(self, agent):
- if not self.connection or self.connected < 2:
- return None
- iq = nbxmpp.Iq('get', nbxmpp.NS_REGISTER, to=agent)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- # Wait the answer during 30 secondes
- self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id_,
- _('Registration information for transport %s has not arrived in '
- 'time') % agent)
- self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo,
- {'agent': agent})
-
- def _agent_registered_cb(self, con, resp, agent):
- if resp.getType() == 'result':
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='info', pri_txt=_('Registration succeeded'), sec_txt=_(
- 'Registration with agent %s succeeded') % agent))
- self.request_subscription(agent, auto_auth=True)
- self.agent_registrations[agent]['roster_push'] = True
- if self.agent_registrations[agent]['sub_received']:
- p = nbxmpp.Presence(agent, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
- if resp.getType() == 'error':
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Registration failed'), sec_txt=_(
- 'Registration with agent %(agent)s failed with error %(error)s:'
- ' %(error_msg)s') % {'agent': agent, 'error': resp.getError(),
- 'error_msg': resp.getErrorMsg()}))
-
- def register_agent(self, agent, info, is_form=False):
- if not self.connection or self.connected < 2:
- return
- if is_form:
- iq = nbxmpp.Iq('set', nbxmpp.NS_REGISTER, to=agent)
- query = iq.setQuery()
- info.setAttr('type', 'submit')
- query.addChild(node=info)
- self.connection.SendAndCallForResponse(iq,
- self._agent_registered_cb, {'agent': agent})
- else:
- # fixed: blocking
- nbxmpp.features_nb.register(self.connection, agent, info,
- self._agent_registered_cb, {'agent': agent})
- self.agent_registrations[agent] = {'roster_push': False,
- 'sub_received': False}
-
- def _discover(self, ns, jid, node=None, id_prefix=None):
- if not self.connection or self.connected < 2:
- return
- iq = nbxmpp.Iq(typ='get', to=jid, queryNS=ns)
- id_ = self.connection.getAnID()
- if id_prefix:
- id_ = id_prefix + id_
- iq.setID(id_)
- if node:
- iq.setQuerynode(node)
- self.connection.send(iq)
- return id_
-
- def _ReceivedRegInfo(self, con, resp, agent):
- nbxmpp.features_nb._ReceivedRegInfo(con, resp, agent)
- self._IqCB(con, resp)
-
- def _discoGetCB(self, con, iq_obj):
- """
- Get disco info
- """
- if not self.connection or self.connected < 2:
- return
- frm = helpers.get_full_jid_from_iq(iq_obj)
- to = iq_obj.getAttr('to')
- id_ = iq_obj.getAttr('id')
- iq = nbxmpp.Iq(to=frm, typ='result', queryNS=nbxmpp.NS_DISCO, frm=to)
- iq.setAttr('id', id_)
- query = iq.setTag('query')
- query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[
- 0])
- for f in (nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
- nbxmpp.NS_COMMANDS, nbxmpp.NS_JINGLE_FILE_TRANSFER_5,
- nbxmpp.NS_JINGLE_XTLS, nbxmpp.NS_PUBKEY_PUBKEY, nbxmpp.NS_PUBKEY_REVOKE,
- nbxmpp.NS_PUBKEY_ATTEST):
- feature = nbxmpp.Node('feature')
- feature.setAttr('var', f)
- query.addChild(node=feature)
-
- self.connection.send(iq)
- raise nbxmpp.NodeProcessed
-
- def _DiscoverItemsErrorCB(self, con, iq_obj):
- log.debug('DiscoverItemsErrorCB')
- gajim.nec.push_incoming_event(AgentItemsErrorReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _DiscoverItemsCB(self, con, iq_obj):
- log.debug('DiscoverItemsCB')
- gajim.nec.push_incoming_event(AgentItemsReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _DiscoverItemsGetCB(self, con, iq_obj):
- log.debug('DiscoverItemsGetCB')
-
- if not self.connection or self.connected < 2:
- return
-
- if self.commandItemsQuery(con, iq_obj):
- raise nbxmpp.NodeProcessed
- node = iq_obj.getTagAttr('query', 'node')
- if node is None:
- result = iq_obj.buildReply('result')
- self.connection.send(result)
- raise nbxmpp.NodeProcessed
- if node == nbxmpp.NS_COMMANDS:
- self.commandListQuery(con, iq_obj)
- raise nbxmpp.NodeProcessed
-
- def _DiscoverInfoGetCB(self, con, iq_obj):
- log.debug('DiscoverInfoGetCB')
- if not self.connection or self.connected < 2:
- return
- node = iq_obj.getQuerynode()
-
- if self.commandInfoQuery(con, iq_obj):
- raise nbxmpp.NodeProcessed
-
- id_ = iq_obj.getAttr('id')
- if id_[:6] == 'Gajim_':
- # We get this request from echo.server
- raise nbxmpp.NodeProcessed
-
- iq = iq_obj.buildReply('result')
- q = iq.setQuery()
- if node:
- q.setAttr('node', node)
- q.addChild('identity', attrs=gajim.gajim_identity)
- client_version = 'http://gajim.org#' + gajim.caps_hash[self.name]
-
- if node in (None, client_version):
- for f in gajim.gajim_common_features:
- q.addChild('feature', attrs={'var': f})
- for f in gajim.gajim_optional_features[self.name]:
- q.addChild('feature', attrs={'var': f})
-
- if q.getChildren():
- self.connection.send(iq)
- raise nbxmpp.NodeProcessed
-
- def _DiscoverInfoErrorCB(self, con, iq_obj):
- log.debug('DiscoverInfoErrorCB')
- gajim.nec.push_incoming_event(AgentInfoErrorReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _DiscoverInfoCB(self, con, iq_obj):
- log.debug('DiscoverInfoCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(AgentInfoReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
-class ConnectionVcard:
- def __init__(self):
- self.vcard_sha = None
- self.vcard_shas = {} # sha of contacts
- # list of gc jids so that vcard are saved in a folder
- self.room_jids = []
-
- def add_sha(self, p, send_caps=True):
- c = p.setTag('x', namespace=nbxmpp.NS_VCARD_UPDATE)
- if self.vcard_sha is not None:
- c.setTagData('photo', self.vcard_sha)
- if send_caps:
- return self._add_caps(p)
- return p
-
- def _add_caps(self, p):
- ''' advertise our capabilities in presence stanza (xep-0115)'''
- c = p.setTag('c', namespace=nbxmpp.NS_CAPS)
- c.setAttr('hash', 'sha-1')
- c.setAttr('node', 'http://gajim.org')
- c.setAttr('ver', gajim.caps_hash[self.name])
- return p
-
- def _node_to_dict(self, node):
- dict_ = {}
- for info in node.getChildren():
- name = info.getName()
- if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
- dict_.setdefault(name, [])
- entry = {}
- for c in info.getChildren():
- entry[c.getName()] = c.getData()
- dict_[name].append(entry)
- elif info.getChildren() == []:
- dict_[name] = info.getData()
- else:
- dict_[name] = {}
- for c in info.getChildren():
- dict_[name][c.getName()] = c.getData()
- return dict_
-
- def _save_vcard_to_hd(self, full_jid, card):
- jid, nick = gajim.get_room_and_nick_from_fjid(full_jid)
- puny_jid = helpers.sanitize_filename(jid)
- path = os.path.join(gajim.VCARD_PATH, puny_jid)
- if jid in self.room_jids or os.path.isdir(path):
- if not nick:
- return
- # remove room_jid file if needed
- if os.path.isfile(path):
- os.remove(path)
- # create folder if needed
- if not os.path.isdir(path):
- os.mkdir(path, 0o700)
- puny_nick = helpers.sanitize_filename(nick)
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- else:
- path_to_file = path
- try:
- fil = open(path_to_file, 'w', encoding='utf-8')
- fil.write(str(card))
- fil.close()
- except IOError as e:
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Disk Write Error'), sec_txt=str(e)))
-
- def get_cached_vcard(self, fjid, is_fake_jid=False):
- """
- Return the vcard as a dict.
- Return {} if vcard was too old.
- Return None if we don't have cached vcard.
- """
- jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
- puny_jid = helpers.sanitize_filename(jid)
- if is_fake_jid:
- puny_nick = helpers.sanitize_filename(nick)
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- else:
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid)
- if not os.path.isfile(path_to_file):
- return None
- # We have the vcard cached
- f = open(path_to_file, encoding='utf-8')
- c = f.read()
- f.close()
- try:
- card = nbxmpp.Node(node=c)
- except Exception:
- # We are unable to parse it. Remove it
- os.remove(path_to_file)
- return None
- vcard = self._node_to_dict(card)
- if 'PHOTO' in vcard:
- if not isinstance(vcard['PHOTO'], dict):
- del vcard['PHOTO']
- elif 'SHA' in vcard['PHOTO']:
- cached_sha = vcard['PHOTO']['SHA']
- if jid in self.vcard_shas and self.vcard_shas[jid] != \
- cached_sha:
- # user change his vcard so don't use the cached one
- return {}
- vcard['jid'] = jid
- vcard['resource'] = gajim.get_resource_from_jid(fjid)
- return vcard
-
- def request_vcard(self, jid=None, groupchat_jid=None):
- """
- Request the VCARD
-
- If groupchat_jid is not null, it means we request a vcard to a fake jid,
- like in private messages in groupchat. jid can be the real jid of the
- contact, but we want to consider it comes from a fake jid
- """
- if not self.connection or self.connected < 2:
- return
- iq = nbxmpp.Iq(typ='get')
- if jid:
- iq.setTo(jid)
- iq.setQuery('vCard').setNamespace(nbxmpp.NS_VCARD)
-
- id_ = self.connection.getAnID()
- iq.setID(id_)
- j = jid
- if not j:
- j = gajim.get_jid_from_account(self.name)
- self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid)
- if groupchat_jid:
- room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0]
- if not room_jid in self.room_jids:
- self.room_jids.append(room_jid)
- self.groupchat_jids[id_] = groupchat_jid
- self.connection.send(iq)
-
- def send_vcard(self, vcard):
- if not self.connection or self.connected < 2:
- return
- iq = nbxmpp.Iq(typ='set')
- iq2 = iq.setTag(nbxmpp.NS_VCARD + ' vCard')
- for i in vcard:
- if i == 'jid':
- continue
- if isinstance(vcard[i], dict):
- iq3 = iq2.addChild(i)
- for j in vcard[i]:
- iq3.addChild(j).setData(vcard[i][j])
- elif isinstance(vcard[i], list):
- for j in vcard[i]:
- iq3 = iq2.addChild(i)
- for k in j:
- iq3.addChild(k).setData(j[k])
- else:
- iq2.addChild(i).setData(vcard[i])
-
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.connection.send(iq)
-
- our_jid = gajim.get_jid_from_account(self.name)
- # Add the sha of the avatar
- if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
- 'BINVAL' in vcard['PHOTO']:
- photo = vcard['PHOTO']['BINVAL']
- photo_decoded = base64.b64decode(photo.encode('utf-8'))
- gajim.interface.save_avatar_files(our_jid, photo_decoded)
- avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
- iq2.getTag('PHOTO').setTagData('SHA', avatar_sha)
- else:
- gajim.interface.remove_avatar_files(our_jid)
-
- self.awaiting_answers[id_] = (VCARD_PUBLISHED, iq2)
-
- def _IqCB(self, con, iq_obj):
- id_ = iq_obj.getID()
-
- gajim.nec.push_incoming_event(NetworkEvent('raw-iq-received',
- conn=self, stanza=iq_obj))
-
- # Check if we were waiting a timeout for this id
- found_tim = None
- for tim in self.awaiting_timeouts:
- if id_ == self.awaiting_timeouts[tim][0]:
- found_tim = tim
- break
- if found_tim:
- del self.awaiting_timeouts[found_tim]
-
- if id_ not in self.awaiting_answers:
- return
-
- if self.awaiting_answers[id_][0] == VCARD_PUBLISHED:
- if iq_obj.getType() == 'result':
- vcard_iq = self.awaiting_answers[id_][1]
- # Save vcard to HD
- if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag(
- 'SHA'):
- new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA')
- else:
- new_sha = ''
-
- # Save it to file
- our_jid = gajim.get_jid_from_account(self.name)
- self._save_vcard_to_hd(our_jid, vcard_iq)
-
- # Send new presence if sha changed and we are not invisible
- if self.vcard_sha != new_sha and gajim.SHOW_LIST[
- self.connected] != 'invisible':
- if not self.connection or self.connected < 2:
- del self.awaiting_answers[id_]
- return
- self.vcard_sha = new_sha
- sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[
- self.connected])
- p = nbxmpp.Presence(typ=None, priority=self.priority,
- show=sshow, status=self.status)
- p = self.add_sha(p)
- self.connection.send(p)
- gajim.nec.push_incoming_event(VcardPublishedEvent(None,
- conn=self))
- elif iq_obj.getType() == 'error':
- gajim.nec.push_incoming_event(VcardNotPublishedEvent(None,
- conn=self))
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == VCARD_ARRIVED:
- # If vcard is empty, we send to the interface an empty vcard so that
- # it knows it arrived
- jid = self.awaiting_answers[id_][1]
- groupchat_jid = self.awaiting_answers[id_][2]
- frm = jid
- if groupchat_jid:
- # We do as if it comes from the fake_jid
- frm = groupchat_jid
- our_jid = gajim.get_jid_from_account(self.name)
- if (not iq_obj.getTag('vCard') and iq_obj.getType() == 'result') or\
- iq_obj.getType() == 'error':
- if id_ in self.groupchat_jids:
- frm = self.groupchat_jids[id_]
- del self.groupchat_jids[id_]
- if frm:
- # Write an empty file
- self._save_vcard_to_hd(frm, '')
- jid, resource = gajim.get_room_and_nick_from_fjid(frm)
- vcard = {'jid': jid, 'resource': resource}
- gajim.nec.push_incoming_event(VcardReceivedEvent(None,
- conn=self, vcard_dict=vcard))
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == AGENT_REMOVED:
- jid = self.awaiting_answers[id_][1]
- gajim.nec.push_incoming_event(AgentRemovedEvent(None, conn=self,
- agent=jid))
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED:
- if not self.connection:
- return
- if iq_obj.getType() == 'result':
- gajim.nec.push_incoming_event(MetacontactsReceivedEvent(None,
- conn=self, stanza=iq_obj))
- else:
- if iq_obj.getErrorCode() not in ('403', '406', '404'):
- self.private_storage_supported = False
- self.get_roster_delimiter()
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == DELIMITER_ARRIVED:
- del self.awaiting_answers[id_]
- if not self.connection:
- return
- if iq_obj.getType() == 'result':
- query = iq_obj.getTag('query')
- if not query:
- return
- delimiter = query.getTagData('roster')
- if delimiter:
- self.nested_group_delimiter = delimiter
- else:
- self.set_roster_delimiter('::')
- else:
- self.private_storage_supported = False
-
- # We can now continue connection by requesting the roster
- self.request_roster()
- elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED:
- if iq_obj.getType() == 'result':
- if not iq_obj.getTag('query'):
- account_jid = gajim.get_jid_from_account(self.name)
- roster_data = gajim.logger.get_roster(account_jid)
- roster = self.connection.getRoster(force=True)
- roster.setRaw(roster_data)
- self._getRoster()
- elif iq_obj.getType() == 'error':
- self.roster_supported = False
- self.discoverItems(gajim.config.get_per('accounts', self.name,
- 'hostname'), id_prefix='Gajim_')
- if gajim.config.get_per('accounts', self.name,
- 'use_ft_proxies'):
- self.discover_ft_proxies()
- gajim.nec.push_incoming_event(RosterReceivedEvent(None,
- conn=self))
- GLib.timeout_add_seconds(10, self.discover_servers)
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED:
- del self.awaiting_answers[id_]
- if iq_obj.getType() != 'error':
- for list_ in iq_obj.getQueryPayload():
- if list_.getName() == 'default':
- self.privacy_default_list = list_.getAttr('name')
- self.get_privacy_list(self.privacy_default_list)
- break
- # Ask metacontacts before roster
- self.get_metacontacts()
- else:
- # That should never happen, but as it's blocking in the
- # connection process, we don't take the risk
- self.privacy_rules_supported = False
- self._continue_connection_request_privacy()
- elif self.awaiting_answers[id_][0] == BLOCKING_ARRIVED:
- del self.awaiting_answers[id_]
- if iq_obj.getType() == 'result':
- list_node = iq_obj.getTag('blocklist')
- if not list_node:
- return
- self.blocked_contacts = []
- for i in list_node.iterTags('item'):
- self.blocked_contacts.append(i.getAttr('jid'))
- elif self.awaiting_answers[id_][0] == PEP_CONFIG:
- del self.awaiting_answers[id_]
- if iq_obj.getType() == 'error':
- return
- if not iq_obj.getTag('pubsub'):
- return
- conf = iq_obj.getTag('pubsub').getTag('configure')
- if not conf:
- return
- node = conf.getAttr('node')
- form_tag = conf.getTag('x', namespace=nbxmpp.NS_DATA)
- if form_tag:
- form = dataforms.ExtendForm(node=form_tag)
- gajim.nec.push_incoming_event(PEPConfigReceivedEvent(None,
- conn=self, node=node, form=form))
-
- def _vCardCB(self, con, vc):
- """
- Called when we receive a vCard Parse the vCard and send it to plugins
- """
- if not vc.getTag('vCard'):
- return
- if not vc.getTag('vCard').getNamespace() == nbxmpp.NS_VCARD:
- return
- id_ = vc.getID()
- frm_iq = vc.getFrom()
- our_jid = gajim.get_jid_from_account(self.name)
- resource = ''
- if id_ in self.groupchat_jids:
- who = self.groupchat_jids[id_]
- frm, resource = gajim.get_room_and_nick_from_fjid(who)
- del self.groupchat_jids[id_]
- elif frm_iq:
- who = helpers.get_full_jid_from_iq(vc)
- frm, resource = gajim.get_room_and_nick_from_fjid(who)
- else:
- who = frm = our_jid
- card = vc.getChildren()[0]
- vcard = self._node_to_dict(card)
- photo_decoded = None
- if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
- 'BINVAL' in vcard['PHOTO']:
- photo = vcard['PHOTO']['BINVAL']
- try:
- photo_decoded = base64.b64decode(photo.encode('utf-8'))
- avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
- except Exception:
- avatar_sha = ''
- else:
- avatar_sha = ''
-
- if avatar_sha:
- card.getTag('PHOTO').setTagData('SHA', avatar_sha)
-
- # Save it to file
- self._save_vcard_to_hd(who, card)
- # Save the decoded avatar to a separate file too, and generate files
- # for dbus notifications
- puny_jid = helpers.sanitize_filename(frm)
- puny_nick = None
- begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid)
- frm_jid = frm
- if frm in self.room_jids:
- puny_nick = helpers.sanitize_filename(resource)
- # create folder if needed
- if not os.path.isdir(begin_path):
- os.mkdir(begin_path, 0o700)
- begin_path = os.path.join(begin_path, puny_nick)
- frm_jid += '/' + resource
- if photo_decoded:
- avatar_file = begin_path + '_notif_size_colored.png'
- if frm_jid == our_jid and avatar_sha != self.vcard_sha:
- gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
- elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \
- frm_jid not in self.vcard_shas or \
- avatar_sha != self.vcard_shas[frm_jid]):
- gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
- if avatar_sha:
- self.vcard_shas[frm_jid] = avatar_sha
- elif frm in self.vcard_shas:
- del self.vcard_shas[frm]
- else:
- for ext in ('.jpeg', '.png', '_notif_size_bw.png',
- '_notif_size_colored.png'):
- path = begin_path + ext
- if os.path.isfile(path):
- os.remove(path)
-
- vcard['jid'] = frm
- vcard['resource'] = resource
- gajim.nec.push_incoming_event(VcardReceivedEvent(None, conn=self,
- vcard_dict=vcard))
- if frm_jid == our_jid:
- # we re-send our presence with sha if has changed and if we are
- # not invisible
- if self.vcard_sha == avatar_sha:
- return
- self.vcard_sha = avatar_sha
- if gajim.SHOW_LIST[self.connected] == 'invisible':
- return
- if not self.connection:
- return
- sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- p = nbxmpp.Presence(typ=None, priority=self.priority,
- show=sshow, status=self.status)
- p = self.add_sha(p)
- self.connection.send(p)
-
-
-class ConnectionPEP(object):
-
- def __init__(self, account, dispatcher, pubsub_connection):
- self._account = account
- self._dispatcher = dispatcher
- self._pubsub_connection = pubsub_connection
- self.reset_awaiting_pep()
-
- def pep_change_account_name(self, new_name):
- self._account = new_name
-
- def reset_awaiting_pep(self):
- self.to_be_sent_activity = None
- self.to_be_sent_mood = None
- self.to_be_sent_tune = None
- self.to_be_sent_nick = None
- self.to_be_sent_location = None
-
- def send_awaiting_pep(self):
- """
- Send pep info that were waiting for connection
- """
- if self.to_be_sent_activity:
- self.send_activity(*self.to_be_sent_activity)
- if self.to_be_sent_mood:
- self.send_mood(*self.to_be_sent_mood)
- if self.to_be_sent_tune:
- self.send_tune(*self.to_be_sent_tune)
- if self.to_be_sent_nick:
- self.send_nick(self.to_be_sent_nick)
- if self.to_be_sent_location:
- self.send_location(self.to_be_sent_location)
- self.reset_awaiting_pep()
-
- def _pubsubEventCB(self, xmpp_dispatcher, msg):
- ''' Called when we receive <message /> with pubsub event. '''
- gajim.nec.push_incoming_event(PEPReceivedEvent(None, conn=self,
- stanza=msg))
-
- def send_activity(self, activity, subactivity=None, message=None):
- if self.connected == 1:
- # We are connecting, keep activity in mem and send it when we'll be
- # connected
- self.to_be_sent_activity = (activity, subactivity, message)
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('activity', {'xmlns': nbxmpp.NS_ACTIVITY})
- if activity:
- i = item.addChild(activity)
- if subactivity:
- i.addChild(subactivity)
- if message:
- i = item.addChild('text')
- i.addData(message)
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_ACTIVITY, item,
- '0')
-
- def retract_activity(self):
- if not self.pep_supported:
- return
- self.send_activity(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_ACTIVITY, '0')
-
- def send_mood(self, mood, message=None):
- if self.connected == 1:
- # We are connecting, keep mood in mem and send it when we'll be
- # connected
- self.to_be_sent_mood = (mood, message)
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('mood', {'xmlns': nbxmpp.NS_MOOD})
- if mood:
- item.addChild(mood)
- if message:
- i = item.addChild('text')
- i.addData(message)
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_MOOD, item, '0')
-
- def retract_mood(self):
- if not self.pep_supported:
- return
- self.send_mood(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_MOOD, '0')
-
- def send_tune(self, artist='', title='', source='', track=0, length=0,
- items=None):
- if self.connected == 1:
- # We are connecting, keep tune in mem and send it when we'll be
- # connected
- self.to_be_sent_tune = (artist, title, source, track, length, items)
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('tune', {'xmlns': nbxmpp.NS_TUNE})
- if artist:
- i = item.addChild('artist')
- i.addData(artist)
- if title:
- i = item.addChild('title')
- i.addData(title)
- if source:
- i = item.addChild('source')
- i.addData(source)
- if track:
- i = item.addChild('track')
- i.addData(track)
- if length:
- i = item.addChild('length')
- i.addData(length)
- if items:
- item.addChild(payload=items)
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_TUNE, item, '0')
-
- def retract_tune(self):
- if not self.pep_supported:
- return
- self.send_tune(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_TUNE, '0')
-
- def send_nickname(self, nick):
- if self.connected == 1:
- # We are connecting, keep nick in mem and send it when we'll be
- # connected
- self.to_be_sent_nick = nick
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('nick', {'xmlns': nbxmpp.NS_NICK})
- item.addData(nick)
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_NICK, item, '0')
-
- def retract_nickname(self):
- if not self.pep_supported:
- return
- self.send_nickname(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_NICK, '0')
-
- def send_location(self, info):
- if self.connected == 1:
- # We are connecting, keep location in mem and send it when we'll be
- # connected
- self.to_be_sent_location = info
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('geoloc', {'xmlns': nbxmpp.NS_LOCATION})
- for field in LOCATION_DATA:
- if info.get(field, None):
- i = item.addChild(field)
- i.addData(info[field])
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_LOCATION, item, '0')
-
- def retract_location(self):
- if not self.pep_supported:
- return
- self.send_location({})
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_LOCATION, '0')
-
-# basic connection handlers used here and in zeroconf
-class ConnectionHandlersBase:
- def __init__(self):
- # List of IDs we are waiting answers for {id: (type_of_request, data), }
- self.awaiting_answers = {}
- # List of IDs that will produce a timeout is answer doesn't arrive
- # {time_of_the_timeout: (id, message to send to gui), }
- self.awaiting_timeouts = {}
- # keep the jids we auto added (transports contacts) to not send the
- # SUBSCRIBED event to gui
- self.automatically_added = []
- # IDs of jabber:iq:last requests
- self.last_ids = []
-
- # keep track of sessions this connection has with other JIDs
- self.sessions = {}
-
- # IDs of sent messages (https://trac.gajim.org/ticket/8222)
- self.sent_message_ids = []
-
- self.received_message_hashes = []
-
- # We decrypt GPG messages one after the other. Keep queue in mem
- self.gpg_messages_to_decrypt = []
-
- gajim.ged.register_event_handler('iq-error-received', ged.CORE,
- self._nec_iq_error_received)
- gajim.ged.register_event_handler('presence-received', ged.CORE,
- self._nec_presence_received)
- gajim.ged.register_event_handler('gc-presence-received', ged.CORE,
- self._nec_gc_presence_received)
- gajim.ged.register_event_handler('message-received', ged.CORE,
- self._nec_message_received)
- gajim.ged.register_event_handler('mam-message-received', ged.CORE,
- self._nec_message_received)
- gajim.ged.register_event_handler('decrypted-message-received', ged.CORE,
- self._nec_decrypted_message_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('iq-error-received', ged.CORE,
- self._nec_iq_error_received)
- gajim.ged.remove_event_handler('presence-received', ged.CORE,
- self._nec_presence_received)
- gajim.ged.remove_event_handler('gc-presence-received', ged.CORE,
- self._nec_gc_presence_received)
- gajim.ged.remove_event_handler('message-received', ged.CORE,
- self._nec_message_received)
- gajim.ged.remove_event_handler('mam-message-received', ged.CORE,
- self._nec_message_received)
- gajim.ged.remove_event_handler('decrypted-message-received', ged.CORE,
- self._nec_decrypted_message_received)
-
- def _nec_iq_error_received(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.id_ in self.last_ids:
- gajim.nec.push_incoming_event(LastResultReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
-
- def _nec_presence_received(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- jid = obj.jid
- resource = obj.resource or ''
-
- statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible']
- obj.old_show = 0
- obj.new_show = statuss.index(obj.show)
-
- obj.contact_list = []
-
- highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
- obj.was_highest = (highest and highest.resource == resource)
-
- # Update contact
- obj.contact_list = gajim.contacts.get_contacts(account, jid)
- obj.contact = None
- resources = []
- for c in obj.contact_list:
- resources.append(c.resource)
- if c.resource == resource:
- obj.contact = c
- break
-
- if obj.avatar_sha is not None and obj.ptype != 'error':
- if obj.jid not in self.vcard_shas:
- cached_vcard = self.get_cached_vcard(obj.jid)
- if cached_vcard and 'PHOTO' in cached_vcard and \
- 'SHA' in cached_vcard['PHOTO']:
- self.vcard_shas[obj.jid] = cached_vcard['PHOTO']['SHA']
- else:
- self.vcard_shas[obj.jid] = ''
- if obj.avatar_sha != self.vcard_shas[obj.jid]:
- # avatar has been updated
- self.request_vcard(obj.jid)
-
- if obj.contact:
- if obj.contact.show in statuss:
- obj.old_show = statuss.index(obj.contact.show)
- # nick changed
- if obj.contact_nickname is not None and \
- obj.contact.contact_name != obj.contact_nickname:
- obj.contact.contact_name = obj.contact_nickname
- obj.need_redraw = True
-
- if obj.old_show == obj.new_show and obj.contact.status == \
- obj.status and obj.contact.priority == obj.prio: # no change
- return True
- else:
- obj.contact = gajim.contacts.get_first_contact_from_jid(account,
- jid)
- if not obj.contact:
- # Presence of another resource of our jid
- # Create self contact and add to roster
- if resource == obj.conn.server_resource:
- return
- # Ignore offline presence of unknown self resource
- if obj.new_show < 2:
- return
- obj.contact = gajim.contacts.create_self_contact(jid=jid,
- account=account, show=obj.show, status=obj.status,
- priority=obj.prio, keyID=obj.keyID,
- resource=obj.resource)
- gajim.contacts.add_contact(account, obj.contact)
- obj.contact_list.append(obj.contact)
- elif obj.contact.show in statuss:
- obj.old_show = statuss.index(obj.contact.show)
- if (resources != [''] and (len(obj.contact_list) != 1 or \
- obj.contact_list[0].show not in ('not in roster', 'offline'))) and \
- not gajim.jid_is_transport(jid):
- # Another resource of an existing contact connected
- obj.old_show = 0
- obj.contact = gajim.contacts.copy_contact(obj.contact)
- obj.contact_list.append(obj.contact)
- obj.contact.resource = resource
-
- obj.need_add_in_roster = True
-
- if not gajim.jid_is_transport(jid) and len(obj.contact_list) == 1:
- # It's not an agent
- if obj.old_show == 0 and obj.new_show > 1:
- if not jid in gajim.newly_added[account]:
- gajim.newly_added[account].append(jid)
- if jid in gajim.to_be_removed[account]:
- gajim.to_be_removed[account].remove(jid)
- elif obj.old_show > 1 and obj.new_show == 0 and \
- obj.conn.connected > 1:
- if not jid in gajim.to_be_removed[account]:
- gajim.to_be_removed[account].append(jid)
- if jid in gajim.newly_added[account]:
- gajim.newly_added[account].remove(jid)
- obj.need_redraw = True
-
- obj.contact.show = obj.show
- obj.contact.status = obj.status
- obj.contact.priority = obj.prio
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- obj.contact.keyID = attached_keys[attached_keys.index(jid) + 1]
- else:
- # Do not override assigned key
- obj.contact.keyID = obj.keyID
- if obj.timestamp:
- obj.contact.last_status_time = localtime(obj.timestamp)
- elif not gajim.block_signed_in_notifications[account]:
- # We're connected since more that 30 seconds
- obj.contact.last_status_time = localtime()
- obj.contact.contact_nickname = obj.contact_nickname
-
- if gajim.jid_is_transport(jid):
- return
-
- # It isn't an agent
- # reset chatstate if needed:
- # (when contact signs out or has errors)
- if obj.show in ('offline', 'error'):
- obj.contact.our_chatstate = obj.contact.chatstate = None
-
- # TODO: This causes problems when another
- # resource signs off!
- self.stop_all_active_file_transfers(obj.contact)
-
- # disable encryption, since if any messages are
- # lost they'll be not decryptable (note that
- # this contradicts XEP-0201 - trying to get that
- # in the XEP, though)
-
- # there won't be any sessions here if the contact terminated
- # their sessions before going offline (which we do)
- for sess in self.get_sessions(jid):
- sess_fjid = sess.jid.getStripped()
- if sess.resource:
- sess_fjid += '/' + sess.resource
- if obj.fjid != sess_fjid:
- continue
- if sess.control:
- sess.control.no_autonegotiation = False
- if sess.enable_encryption:
- sess.terminate_e2e()
-
- if gajim.config.get('log_contact_status_changes') and \
- gajim.config.should_log(self.name, obj.jid):
- gajim.logger.write('status', obj.jid, obj.status, obj.show)
-
- def _nec_gc_presence_received(self, obj):
- if obj.conn.name != self.name:
- return
- for sess in self.get_sessions(obj.fjid):
- if obj.fjid != sess.jid:
- continue
- if sess.enable_encryption:
- sess.terminate_e2e()
-
- def _nec_message_received(self, obj):
- if obj.conn.name != self.name:
- return
-
- gajim.plugin_manager.extension_point(
- 'decrypt', self, obj, self._on_message_received)
- if not obj.encrypted:
- self._on_message_received(obj)
-
- def _on_message_received(self, obj):
- if isinstance(obj, MessageReceivedEvent):
- gajim.nec.push_incoming_event(
- DecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
- else:
- gajim.nec.push_incoming_event(
- MamDecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
-
- def _nec_decrypted_message_received(self, obj):
- if obj.conn.name != self.name:
- return
-
- # Receipt requested
- # TODO: We shouldn't answer if we're invisible!
- contact = gajim.contacts.get_contact(self.name, obj.jid)
- nick = obj.resource
- gc_contact = gajim.contacts.get_gc_contact(self.name, obj.jid, nick)
- if obj.sent:
- jid_to = obj.stanza.getFrom()
- else:
- jid_to = obj.stanza.getTo()
- reply = False
- if not jid_to:
- reply = True
- else:
- fjid_to = helpers.parse_jid(str(jid_to))
- jid_to = gajim.get_jid_without_resource(fjid_to)
- if jid_to == gajim.get_jid_from_account(self.name):
- reply = True
-
- if obj.jid != gajim.get_jid_from_account(self.name):
- if obj.receipt_request_tag and gajim.config.get_per('accounts',
- self.name, 'answer_receipts') and ((contact and contact.sub \
- not in ('to', 'none')) or gc_contact) and obj.mtype != 'error' and \
- reply:
- receipt = nbxmpp.Message(to=obj.fjid, typ='chat')
- receipt.setTag('received', namespace='urn:xmpp:receipts',
- attrs={'id': obj.id_})
-
- if obj.thread_id:
- receipt.setThread(obj.thread_id)
- self.connection.send(receipt)
-
- # We got our message's receipt
- if obj.receipt_received_tag and gajim.config.get_per('accounts',
- self.name, 'request_receipt'):
- ctrl = obj.session.control
- if not ctrl:
- # Received <message> doesn't have the <thread> element
- # or control is not bound to session?
- # --> search for it
- ctrl = gajim.interface.msg_win_mgr.search_control(obj.jid,
- obj.conn.name, obj.resource)
-
- if ctrl:
- id_ = obj.receipt_received_tag.getAttr('id')
- if not id_:
- # old XEP implementation
- id_ = obj.id_
- ctrl.conv_textview.show_xep0184_ack(id_)
-
- if obj.mtype == 'error':
- if not obj.msgtxt:
- obj.msgtxt = _('message')
- self.dispatch_error_message(obj.stanza, obj.msgtxt,
- obj.session, obj.fjid, obj.timestamp)
- return True
- elif obj.mtype == 'groupchat':
- gajim.nec.push_incoming_event(GcMessageReceivedEvent(None,
- conn=self, msg_obj=obj))
- return True
-
- # process and dispatch an error message
- def dispatch_error_message(self, msg, msgtxt, session, frm, tim):
- error_msg = msg.getErrorMsg()
-
- if not error_msg:
- error_msg = msgtxt
- msgtxt = None
-
- subject = msg.getSubject()
-
- if session.is_loggable():
- gajim.logger.write('error', frm, error_msg, tim=tim,
- subject=subject)
- gajim.nec.push_incoming_event(MessageErrorEvent(None, conn=self,
- fjid=frm, error_code=msg.getErrorCode(), error_msg=error_msg,
- msg=msgtxt, time_=tim, session=session, stanza=msg))
-
- def _LastResultCB(self, con, iq_obj):
- log.debug('LastResultCB')
- gajim.nec.push_incoming_event(LastResultReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def get_sessions(self, jid):
- """
- Get all sessions for the given full jid
- """
- if not gajim.interface.is_pm_contact(jid, self.name):
- jid = gajim.get_jid_without_resource(jid)
-
- try:
- return list(self.sessions[jid].values())
- except KeyError:
- return []
-
- def get_or_create_session(self, fjid, thread_id):
- """
- Return an existing session between this connection and 'jid', returns a
- new one if none exist
- """
- pm = True
- jid = fjid
-
- if not gajim.interface.is_pm_contact(fjid, self.name):
- pm = False
- jid = gajim.get_jid_without_resource(fjid)
-
- session = self.find_session(jid, thread_id)
-
- if session:
- return session
-
- if pm:
- return self.make_new_session(fjid, thread_id, type_='pm')
- else:
- return self.make_new_session(fjid, thread_id)
-
- def find_session(self, jid, thread_id):
- try:
- if not thread_id:
- return self.find_null_session(jid)
- else:
- return self.sessions[jid][thread_id]
- except KeyError:
- return None
-
- def terminate_sessions(self, send_termination=False):
- """
- Send termination messages and delete all active sessions
- """
- for jid in self.sessions:
- for thread_id in self.sessions[jid]:
- self.sessions[jid][thread_id].terminate(send_termination)
-
- self.sessions = {}
-
- def delete_session(self, jid, thread_id):
- if not jid in self.sessions:
- jid = gajim.get_jid_without_resource(jid)
- if not jid in self.sessions:
- return
-
- del self.sessions[jid][thread_id]
-
- if not self.sessions[jid]:
- del self.sessions[jid]
-
- def find_null_session(self, jid):
- """
- Find all of the sessions between us and a remote jid in which we haven't
- received a thread_id yet and returns the session that we last sent a
- message to
- """
- sessions = list(self.sessions[jid].values())
-
- # sessions that we haven't received a thread ID in
- idless = [s for s in sessions if not s.received_thread_id]
-
- # filter out everything except the default session type
- chat_sessions = [s for s in idless if isinstance(s,
- gajim.default_session_type)]
-
- if chat_sessions:
- # return the session that we last sent a message in
- return sorted(chat_sessions, key=operator.attrgetter('last_send'))[
- -1]
- else:
- return None
-
- def get_latest_session(self, jid):
- """
- Get the session that we last sent a message to
- """
- if jid not in self.sessions:
- return None
- sessions = self.sessions[jid].values()
- if not sessions:
- return None
- return sorted(sessions, key=operator.attrgetter('last_send'))[-1]
-
- def find_controlless_session(self, jid, resource=None):
- """
- Find an active session that doesn't have a control attached
- """
- try:
- sessions = list(self.sessions[jid].values())
-
- # filter out everything except the default session type
- chat_sessions = [s for s in sessions if isinstance(s,
- gajim.default_session_type)]
-
- orphaned = [s for s in chat_sessions if not s.control]
-
- if resource:
- orphaned = [s for s in orphaned if s.resource == resource]
-
- return orphaned[0]
- except (KeyError, IndexError):
- return None
-
- def make_new_session(self, jid, thread_id=None, type_='chat', cls=None):
- """
- Create and register a new session
-
- thread_id=None to generate one.
- type_ should be 'chat' or 'pm'.
- """
- if not cls:
- cls = gajim.default_session_type
-
- sess = cls(self, nbxmpp.JID(jid), thread_id, type_)
-
- # determine if this session is a pm session
- # if not, discard the resource so that all sessions are stored bare
- if not type_ == 'pm':
- jid = gajim.get_jid_without_resource(jid)
-
- if not jid in self.sessions:
- self.sessions[jid] = {}
-
- self.sessions[jid][sess.thread_id] = sess
-
- return sess
-
-class ConnectionHandlers(ConnectionArchive136, ConnectionArchive313,
-ConnectionVcard, ConnectionSocks5Bytestream, ConnectionDisco,
-ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps,
-ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
- def __init__(self):
- global HAS_IDLE
- ConnectionArchive136.__init__(self)
- ConnectionArchive313.__init__(self)
- ConnectionVcard.__init__(self)
- ConnectionSocks5Bytestream.__init__(self)
- ConnectionIBBytestream.__init__(self)
- ConnectionCommands.__init__(self)
- ConnectionPubSub.__init__(self)
- ConnectionPEP.__init__(self, account=self.name, dispatcher=self,
- pubsub_connection=self)
-
- # Handle presences BEFORE caps
- gajim.nec.register_incoming_event(PresenceReceivedEvent)
-
- ConnectionCaps.__init__(self, account=self.name,
- capscache=capscache.capscache,
- client_caps_factory=capscache.create_suitable_client_caps)
- ConnectionJingle.__init__(self)
- ConnectionHandlersBase.__init__(self)
- self.gmail_url = None
-
- # keep the latest subscribed event for each jid to prevent loop when we
- # acknowledge presences
- self.subscribed_events = {}
- # IDs of jabber:iq:version requests
- self.version_ids = []
- # IDs of urn:xmpp:time requests
- self.entity_time_ids = []
- # IDs of disco#items requests
- self.disco_items_ids = []
- # IDs of disco#info requests
- self.disco_info_ids = []
- # ID of urn:xmpp:ping requests
- self.awaiting_xmpp_ping_id = None
- self.continue_connect_info = None
-
- self.privacy_default_list = None
-
- try:
- self.sleeper = common.sleepy.Sleepy()
- HAS_IDLE = True
- except Exception:
- HAS_IDLE = False
-
- self.gmail_last_tid = None
- self.gmail_last_time = None
-
- gajim.nec.register_incoming_event(PrivateStorageBookmarksReceivedEvent)
- gajim.nec.register_incoming_event(BookmarksReceivedEvent)
- gajim.nec.register_incoming_event(
- PrivateStorageRosternotesReceivedEvent)
- gajim.nec.register_incoming_event(RosternotesReceivedEvent)
- gajim.nec.register_incoming_event(StreamConflictReceivedEvent)
- gajim.nec.register_incoming_event(StreamOtherHostReceivedEvent)
- gajim.nec.register_incoming_event(MessageReceivedEvent)
- gajim.nec.register_incoming_event(ArchivingErrorReceivedEvent)
- gajim.nec.register_incoming_event(
- ArchivingPreferencesChangedReceivedEvent)
- gajim.nec.register_incoming_event(
- Archiving313PreferencesChangedReceivedEvent)
- gajim.nec.register_incoming_event(
- ArchivingFinishedLegacyReceivedEvent)
- gajim.nec.register_incoming_event(
- ArchivingFinishedReceivedEvent)
- gajim.nec.register_incoming_event(NotificationEvent)
-
- gajim.ged.register_event_handler('http-auth-received', ged.CORE,
- self._nec_http_auth_received)
- gajim.ged.register_event_handler('version-request-received', ged.CORE,
- self._nec_version_request_received)
- gajim.ged.register_event_handler('last-request-received', ged.CORE,
- self._nec_last_request_received)
- gajim.ged.register_event_handler('time-request-received', ged.CORE,
- self._nec_time_request_received)
- gajim.ged.register_event_handler('time-revised-request-received',
- ged.CORE, self._nec_time_revised_request_received)
- gajim.ged.register_event_handler('roster-set-received',
- ged.CORE, self._nec_roster_set_received)
- gajim.ged.register_event_handler('private-storage-bookmarks-received',
- ged.CORE, self._nec_private_storate_bookmarks_received)
- gajim.ged.register_event_handler('private-storage-rosternotes-received',
- ged.CORE, self._nec_private_storate_rosternotes_received)
- gajim.ged.register_event_handler('roster-received', ged.CORE,
- self._nec_roster_received)
- gajim.ged.register_event_handler('iq-error-received', ged.CORE,
- self._nec_iq_error_received)
- gajim.ged.register_event_handler('gmail-new-mail-received', ged.CORE,
- self._nec_gmail_new_mail_received)
- gajim.ged.register_event_handler('ping-received', ged.CORE,
- self._nec_ping_received)
- gajim.ged.register_event_handler('subscribe-presence-received',
- ged.CORE, self._nec_subscribe_presence_received)
- gajim.ged.register_event_handler('subscribed-presence-received',
- ged.CORE, self._nec_subscribed_presence_received)
- gajim.ged.register_event_handler('subscribed-presence-received',
- ged.POSTGUI, self._nec_subscribed_presence_received_end)
- gajim.ged.register_event_handler('unsubscribed-presence-received',
- ged.CORE, self._nec_unsubscribed_presence_received)
- gajim.ged.register_event_handler('unsubscribed-presence-received',
- ged.POSTGUI, self._nec_unsubscribed_presence_received_end)
- gajim.ged.register_event_handler('agent-removed', ged.CORE,
- self._nec_agent_removed)
- gajim.ged.register_event_handler('stream-other-host-received', ged.CORE,
- self._nec_stream_other_host_received)
- gajim.ged.register_event_handler('blocking', ged.CORE,
- self._nec_blocking)
-
- def cleanup(self):
- ConnectionHandlersBase.cleanup(self)
- ConnectionCaps.cleanup(self)
- ConnectionArchive136.cleanup(self)
- ConnectionArchive313.cleanup(self)
- ConnectionPubSub.cleanup(self)
- gajim.ged.remove_event_handler('http-auth-received', ged.CORE,
- self._nec_http_auth_received)
- gajim.ged.remove_event_handler('version-request-received', ged.CORE,
- self._nec_version_request_received)
- gajim.ged.remove_event_handler('last-request-received', ged.CORE,
- self._nec_last_request_received)
- gajim.ged.remove_event_handler('time-request-received', ged.CORE,
- self._nec_time_request_received)
- gajim.ged.remove_event_handler('time-revised-request-received',
- ged.CORE, self._nec_time_revised_request_received)
- gajim.ged.remove_event_handler('roster-set-received',
- ged.CORE, self._nec_roster_set_received)
- gajim.ged.remove_event_handler('private-storage-bookmarks-received',
- ged.CORE, self._nec_private_storate_bookmarks_received)
- gajim.ged.remove_event_handler('private-storage-rosternotes-received',
- ged.CORE, self._nec_private_storate_rosternotes_received)
- gajim.ged.remove_event_handler('roster-received', ged.CORE,
- self._nec_roster_received)
- gajim.ged.remove_event_handler('iq-error-received', ged.CORE,
- self._nec_iq_error_received)
- gajim.ged.remove_event_handler('gmail-new-mail-received', ged.CORE,
- self._nec_gmail_new_mail_received)
- gajim.ged.remove_event_handler('ping-received', ged.CORE,
- self._nec_ping_received)
- gajim.ged.remove_event_handler('subscribe-presence-received',
- ged.CORE, self._nec_subscribe_presence_received)
- gajim.ged.remove_event_handler('subscribed-presence-received',
- ged.CORE, self._nec_subscribed_presence_received)
- gajim.ged.remove_event_handler('subscribed-presence-received',
- ged.POSTGUI, self._nec_subscribed_presence_received_end)
- gajim.ged.remove_event_handler('unsubscribed-presence-received',
- ged.CORE, self._nec_unsubscribed_presence_received)
- gajim.ged.remove_event_handler('unsubscribed-presence-received',
- ged.POSTGUI, self._nec_unsubscribed_presence_received_end)
- gajim.ged.remove_event_handler('agent-removed', ged.CORE,
- self._nec_agent_removed)
- gajim.ged.remove_event_handler('stream-other-host-received', ged.CORE,
- self._nec_stream_other_host_received)
- gajim.ged.remove_event_handler('blocking', ged.CORE, self._nec_blocking)
-
- def build_http_auth_answer(self, iq_obj, answer):
- if not self.connection or self.connected < 2:
- return
- if answer == 'yes':
- confirm = iq_obj.getTag('confirm')
- reply = iq_obj.buildReply('result')
- if iq_obj.getName() == 'message':
- reply.addChild(node=confirm)
- self.connection.send(reply)
- elif answer == 'no':
- err = nbxmpp.Error(iq_obj, nbxmpp.protocol.ERR_NOT_AUTHORIZED)
- self.connection.send(err)
-
- def _nec_http_auth_received(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.opt in ('yes', 'no'):
- obj.conn.build_http_auth_answer(obj.stanza, obj.opt)
- return True
-
- def _HttpAuthCB(self, con, iq_obj):
- log.debug('HttpAuthCB')
- gajim.nec.push_incoming_event(HttpAuthReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _ErrorCB(self, con, iq_obj):
- log.debug('ErrorCB')
- gajim.nec.push_incoming_event(IqErrorReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _nec_iq_error_received(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.id_ in self.version_ids:
- gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
- if obj.id_ in self.entity_time_ids:
- gajim.nec.push_incoming_event(TimeResultReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
- if obj.id_ in self.disco_items_ids:
- gajim.nec.push_incoming_event(AgentItemsErrorReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
- if obj.id_ in self.disco_info_ids:
- gajim.nec.push_incoming_event(AgentInfoErrorReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
-
- def _nec_private_storate_bookmarks_received(self, obj):
- if obj.conn.name != self.name:
- return
- resend_to_pubsub = False
- bm_jids = [b['jid'] for b in self.bookmarks]
- for bm in obj.bookmarks:
- if bm['jid'] not in bm_jids:
- self.bookmarks.append(bm)
- # We got a bookmark that was not in pubsub
- resend_to_pubsub = True
- if self.pubsub_supported and resend_to_pubsub:
- self.store_bookmarks('pubsub')
-
- def _nec_private_storate_rosternotes_received(self, obj):
- if obj.conn.name != self.name:
- return
- for jid in obj.annotations:
- self.annotations[jid] = obj.annotations[jid]
-
- def _PrivateCB(self, con, iq_obj):
- """
- Private Data (XEP 048 and 049)
- """
- log.debug('PrivateCB')
- gajim.nec.push_incoming_event(PrivateStorageReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _SecLabelCB(self, con, iq_obj):
- """
- Security Label callback, used for catalogues.
- """
- log.debug('SecLabelCB')
- query = iq_obj.getTag('catalog')
- to = query.getAttr('to')
- items = query.getTags('item')
- labels = {}
- ll = []
- default = None
- for item in items:
- label = item.getAttr('selector')
- labels[label] = item.getTag('securitylabel')
- ll.append(label)
- if item.getAttr('default') == 'true':
- default = label
- if to not in self.seclabel_catalogues:
- self.seclabel_catalogues[to] = [[], None, None, None]
- self.seclabel_catalogues[to][1] = labels
- self.seclabel_catalogues[to][2] = ll
- self.seclabel_catalogues[to][3] = default
- for callback in self.seclabel_catalogues[to][0]:
- callback()
- self.seclabel_catalogues[to][0] = []
-
- def seclabel_catalogue_request(self, to, callback):
- if to not in self.seclabel_catalogues:
- self.seclabel_catalogues[to] = [[], None, None, None]
- self.seclabel_catalogues[to][0].append(callback)
-
- def _rosterSetCB(self, con, iq_obj):
- log.debug('rosterSetCB')
- gajim.nec.push_incoming_event(RosterSetReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_roster_set_received(self, obj):
- if obj.conn.name != self.name:
- return
- for jid in obj.items:
- item = obj.items[jid]
- gajim.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
- jid=jid, nickname=item['name'], sub=item['sub'],
- ask=item['ask'], groups=item['groups']))
- account_jid = gajim.get_jid_from_account(self.name)
- gajim.logger.add_or_update_contact(account_jid, jid, item['name'],
- item['sub'], item['ask'], item['groups'])
- if obj.version:
- gajim.config.set_per('accounts', self.name, 'roster_version',
- obj.version)
-
- def _VersionCB(self, con, iq_obj):
- log.debug('VersionCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(VersionRequestEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_version_request_received(self, obj):
- if obj.conn.name != self.name:
- return
- send_os = gajim.config.get_per('accounts', self.name, 'send_os_info')
- if send_os:
- iq_obj = obj.stanza.buildReply('result')
- qp = iq_obj.getQuery()
- qp.setTagData('name', 'Gajim')
- qp.setTagData('version', gajim.version)
- qp.setTagData('os', helpers.get_os_info())
- else:
- iq_obj = obj.stanza.buildReply('error')
- err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \
- ' service-unavailable')
- iq_obj.addChild(node=err)
- self.connection.send(iq_obj)
-
- def _LastCB(self, con, iq_obj):
- log.debug('LastCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(LastRequestEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_last_request_received(self, obj):
- global HAS_IDLE
- if obj.conn.name != self.name:
- return
- if HAS_IDLE and gajim.config.get_per('accounts', self.name,
- 'send_idle_time'):
- iq_obj = obj.stanza.buildReply('result')
- qp = iq_obj.setQuery()
- qp.attrs['seconds'] = int(self.sleeper.getIdleSec())
- else:
- iq_obj = obj.stanza.buildReply('error')
- err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \
- ' service-unavailable')
- iq_obj.addChild(node=err)
- self.connection.send(iq_obj)
-
- def _VersionResultCB(self, con, iq_obj):
- log.debug('VersionResultCB')
- gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _TimeCB(self, con, iq_obj):
- log.debug('TimeCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(TimeRequestEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_time_request_received(self, obj):
- if obj.conn.name != self.name:
- return
- if gajim.config.get_per('accounts', self.name, 'send_time_info'):
- iq_obj = obj.stanza.buildReply('result')
- qp = iq_obj.setQuery()
- qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime()))
- qp.setTagData('tz', tzname[daylight])
- qp.setTagData('display', strftime('%c', localtime()))
- else:
- iq_obj = obj.stanza.buildReply('error')
- err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \
- ' service-unavailable')
- iq_obj.addChild(node=err)
- self.connection.send(iq_obj)
-
- def _TimeRevisedCB(self, con, iq_obj):
- log.debug('TimeRevisedCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(TimeRevisedRequestEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_time_revised_request_received(self, obj):
- if obj.conn.name != self.name:
- return
- if gajim.config.get_per('accounts', self.name, 'send_time_info'):
- iq_obj = obj.stanza.buildReply('result')
- qp = iq_obj.setTag('time', namespace=nbxmpp.NS_TIME_REVISED)
- qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime()))
- isdst = localtime().tm_isdst
- zone = -(timezone, altzone)[isdst] / 60.0
- tzo = (zone / 60, abs(zone % 60))
- qp.setTagData('tzo', '%+03d:%02d' % (tzo))
- else:
- iq_obj = obj.stanza.buildReply('error')
- err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \
- ' service-unavailable')
- iq_obj.addChild(node=err)
- self.connection.send(iq_obj)
-
- def _TimeRevisedResultCB(self, con, iq_obj):
- log.debug('TimeRevisedResultCB')
- gajim.nec.push_incoming_event(TimeResultReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _gMailNewMailCB(self, con, iq_obj):
- """
- Called when we get notified of new mail messages in gmail account
- """
- log.debug('gMailNewMailCB')
- gajim.nec.push_incoming_event(GmailNewMailReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_gmail_new_mail_received(self, obj):
- if obj.conn.name != self.name:
- return
- if not self.connection or self.connected < 2:
- return
- # we'll now ask the server for the exact number of new messages
- jid = gajim.get_jid_from_account(self.name)
- log.debug('Got notification of new gmail e-mail on %s. Asking the '
- 'server for more info.' % jid)
- iq = nbxmpp.Iq(typ='get')
- query = iq.setTag('query')
- query.setNamespace(nbxmpp.NS_GMAILNOTIFY)
- # we want only be notified about newer mails
- if self.gmail_last_tid:
- query.setAttr('newer-than-tid', self.gmail_last_tid)
- if self.gmail_last_time:
- query.setAttr('newer-than-time', self.gmail_last_time)
- self.connection.send(iq)
-
- def _gMailQueryCB(self, con, iq_obj):
- """
- Called when we receive results from Querying the server for mail messages
- in gmail account
- """
- log.debug('gMailQueryCB')
- gajim.nec.push_incoming_event(GMailQueryReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _rosterItemExchangeCB(self, con, msg):
- """
- XEP-0144 Roster Item Echange
- """
- log.debug('rosterItemExchangeCB')
- gajim.nec.push_incoming_event(RosterItemExchangeEvent(None, conn=self,
- stanza=msg))
- raise nbxmpp.NodeProcessed
-
- def _messageCB(self, con, msg):
- """
- Called when we receive a message
- """
- log.debug('MessageCB')
-
- gajim.nec.push_incoming_event(NetworkEvent('raw-message-received',
- conn=self, stanza=msg, account=self.name))
-
- def _dispatch_gc_msg_with_captcha(self, stanza, msg_obj):
- msg_obj.stanza = stanza
- gajim.nec.push_incoming_event(GcMessageReceivedEvent(None,
- conn=self, msg_obj=msg_obj))
-
- def _on_bob_received(self, conn, result, cid):
- """
- Called when we receive BoB data
- """
- if cid not in self.awaiting_cids:
- return
-
- if result.getType() == 'result':
- data = msg.getTags('data', namespace=nbxmpp.NS_BOB)
- if data.getAttr('cid') == cid:
- for func in self.awaiting_cids[cid]:
- cb = func[0]
- args = func[1]
- pos = func[2]
- bob_data = data.getData()
- def recurs(node, cid, data):
- if node.getData() == 'cid:' + cid:
- node.setData(data)
- else:
- for child in node.getChildren():
- recurs(child, cid, data)
- recurs(args[pos], cid, bob_data)
- cb(*args)
- del self.awaiting_cids[cid]
- return
-
- # An error occured, call callback without modifying data.
- for func in self.awaiting_cids[cid]:
- cb = func[0]
- args = func[1]
- cb(*args)
- del self.awaiting_cids[cid]
-
- def get_bob_data(self, cid, to, callback, args, position):
- """
- Request for BoB (XEP-0231) and when data will arrive, call callback
- with given args, after having replaced cid by it's data in
- args[position]
- """
- if cid in self.awaiting_cids:
- self.awaiting_cids[cid].appends((callback, args, position))
- else:
- self.awaiting_cids[cid] = [(callback, args, position)]
- iq = nbxmpp.Iq(to=to, typ='get')
- data = iq.addChild(name='data', attrs={'cid': cid},
- namespace=nbxmpp.NS_BOB)
- self.connection.SendAndCallForResponse(iq, self._on_bob_received,
- {'cid': cid})
-
- def _presenceCB(self, con, prs):
- """
- Called when we receive a presence
- """
- log.debug('PresenceCB')
- gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received',
- conn=self, stanza=prs))
-
- def _nec_subscribe_presence_received(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- if gajim.jid_is_transport(obj.fjid) and obj.fjid in \
- self.agent_registrations:
- self.agent_registrations[obj.fjid]['sub_received'] = True
- if not self.agent_registrations[obj.fjid]['roster_push']:
- # We'll reply after roster push result
- return True
- if gajim.config.get_per('accounts', self.name, 'autoauth') or \
- gajim.jid_is_transport(obj.fjid) or obj.jid in self.jids_for_auto_auth \
- or obj.transport_auto_auth:
- if self.connection:
- p = nbxmpp.Presence(obj.fjid, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
- if gajim.jid_is_transport(obj.fjid) or obj.transport_auto_auth:
- #TODO!?!?
- #self.show = 'offline'
- #self.status = 'offline'
- #emit NOTIFY
- pass
- if obj.transport_auto_auth:
- self.automatically_added.append(obj.jid)
- self.request_subscription(obj.jid, name=obj.user_nick)
- return True
- if not obj.status:
- obj.status = _('I would like to add you to my roster.')
-
- def _nec_subscribed_presence_received(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- # BE CAREFUL: no con.updateRosterItem() in a callback
- if obj.jid in self.automatically_added:
- self.automatically_added.remove(obj.jid)
- return True
- # detect a subscription loop
- if obj.jid not in self.subscribed_events:
- self.subscribed_events[obj.jid] = []
- self.subscribed_events[obj.jid].append(time_time())
- block = False
- if len(self.subscribed_events[obj.jid]) > 5:
- if time_time() - self.subscribed_events[obj.jid][0] < 5:
- block = True
- self.subscribed_events[obj.jid] = \
- self.subscribed_events[obj.jid][1:]
- if block:
- gajim.config.set_per('account', self.name, 'dont_ack_subscription',
- True)
- return True
-
- def _nec_subscribed_presence_received_end(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- if not gajim.config.get_per('accounts', account,
- 'dont_ack_subscription'):
- self.ack_subscribed(obj.jid)
-
- def _nec_unsubscribed_presence_received(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- # detect a unsubscription loop
- if obj.jid not in self.subscribed_events:
- self.subscribed_events[obj.jid] = []
- self.subscribed_events[obj.jid].append(time_time())
- block = False
- if len(self.subscribed_events[obj.jid]) > 5:
- if time_time() - self.subscribed_events[obj.jid][0] < 5:
- block = True
- self.subscribed_events[obj.jid] = \
- self.subscribed_events[obj.jid][1:]
- if block:
- gajim.config.set_per('account', self.name, 'dont_ack_subscription',
- True)
- return True
-
- def _nec_unsubscribed_presence_received_end(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- if not gajim.config.get_per('accounts', account,
- 'dont_ack_subscription'):
- self.ack_unsubscribed(obj.jid)
-
- def _nec_agent_removed(self, obj):
- if obj.conn.name != self.name:
- return
- for jid in obj.jid_list:
- log.debug('Removing contact %s due to unregistered transport %s' % \
- (jid, obj.agent))
- self.unsubscribe(jid)
- # Transport contacts can't have 2 resources
- if jid in gajim.to_be_removed[self.name]:
- # This way we'll really remove it
- gajim.to_be_removed[self.name].remove(jid)
-
- def _StanzaArrivedCB(self, con, obj):
- self.last_io = gajim.idlequeue.current_time()
-
- def _MucOwnerCB(self, con, iq_obj):
- log.debug('MucOwnerCB')
- gajim.nec.push_incoming_event(MucOwnerReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _MucAdminCB(self, con, iq_obj):
- log.debug('MucAdminCB')
- gajim.nec.push_incoming_event(MucAdminReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _IqPingCB(self, con, iq_obj):
- log.debug('IqPingCB')
- gajim.nec.push_incoming_event(PingReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_ping_received(self, obj):
- if obj.conn.name != self.name:
- return
- if not self.connection or self.connected < 2:
- return
- iq_obj = obj.stanza.buildReply('result')
- q = iq_obj.getTag('ping')
- if q:
- iq_obj.delChild(q)
- self.connection.send(iq_obj)
-
- def _PrivacySetCB(self, con, iq_obj):
- """
- Privacy lists (XEP 016)
-
- A list has been set.
- """
- log.debug('PrivacySetCB')
- if not self.connection or self.connected < 2:
- return
- result = iq_obj.buildReply('result')
- q = result.getTag('query')
- if q:
- result.delChild(q)
- self.connection.send(result)
-
- for list_ in iq_obj.getQueryPayload():
- if list_.getName() == 'list':
- self.get_privacy_list(list_.getAttr('name'))
-
- raise nbxmpp.NodeProcessed
-
- def _getRoster(self):
- log.debug('getRosterCB')
- if not self.connection:
- return
- self.connection.getRoster(self._on_roster_set)
- self.discoverItems(gajim.config.get_per('accounts', self.name,
- 'hostname'), id_prefix='Gajim_')
- if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
- self.discover_ft_proxies()
-
- def discover_ft_proxies(self):
- cfg_proxies = gajim.config.get_per('accounts', self.name,
- 'file_transfer_proxies')
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + \
- '/' + self.server_resource)
- testit = gajim.config.get_per('accounts', self.name,
- 'test_ft_proxies_on_startup')
- if cfg_proxies:
- proxies = [e.strip() for e in cfg_proxies.split(',')]
- for proxy in proxies:
- gajim.proxy65_manager.resolve(proxy, self.connection, our_jid,
- testit=testit)
-
- def discover_servers(self):
- if not self.connection:
- return
- servers = []
- for c in gajim.contacts.iter_contacts(self.name):
- s = gajim.get_server_from_jid(c.jid)
- if s not in servers and s not in gajim.transport_type:
- servers.append(s)
- for s in servers:
- self.discoverInfo(s)
-
- def _on_roster_set(self, roster):
- gajim.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,
- xmpp_roster=roster))
-
- def _nec_roster_received(self, obj):
- if obj.conn.name != self.name:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- if self.connected > 1 and self.continue_connect_info:
- msg = self.continue_connect_info[1]
- sign_msg = self.continue_connect_info[2]
- signed = ''
- send_first_presence = True
- if sign_msg:
- signed = self.get_signed_presence(msg,
- self._send_first_presence)
- if signed is None:
- gajim.nec.push_incoming_event(GPGPasswordRequiredEvent(None,
- conn=self, callback=self._send_first_presence))
- # _send_first_presence will be called when user enter
- # passphrase
- send_first_presence = False
- if send_first_presence:
- self._send_first_presence(signed)
-
- if obj.received_from_server:
- for jid in obj.roster:
- if jid != our_jid and gajim.jid_is_transport(jid) and \
- not gajim.get_transport_name_from_jid(jid):
- # we can't determine which iconset to use
- self.discoverInfo(jid)
-
- gajim.logger.replace_roster(self.name, obj.version, obj.roster)
-
- for contact in gajim.contacts.iter_contacts(self.name):
- if not contact.is_groupchat() and contact.jid not in obj.roster\
- and contact.jid != our_jid:
- gajim.nec.push_incoming_event(RosterInfoEvent(None,
- conn=self, jid=contact.jid, nickname=None, sub=None,
- ask=None, groups=()))
- for jid, info in obj.roster.items():
- gajim.nec.push_incoming_event(RosterInfoEvent(None,
- conn=self, jid=jid, nickname=info['name'],
- sub=info['subscription'], ask=info['ask'],
- groups=info['groups']))
-
- def _send_first_presence(self, signed=''):
- show = self.continue_connect_info[0]
- msg = self.continue_connect_info[1]
- sign_msg = self.continue_connect_info[2]
- if sign_msg and not signed:
- signed = self.get_signed_presence(msg)
- if signed is None:
- gajim.nec.push_incoming_event(BadGPGPassphraseEvent(None,
- conn=self))
- self.USE_GPG = False
- signed = ''
- self.connected = gajim.SHOW_LIST.index(show)
- sshow = helpers.get_xmpp_show(show)
- # send our presence
- if show == 'invisible':
- self.send_invisible_presence(msg, signed, True)
- return
- if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
- return
- priority = gajim.get_priority(self.name, sshow)
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
- vcard = self.get_cached_vcard(our_jid)
- if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']:
- self.vcard_sha = vcard['PHOTO']['SHA']
- p = nbxmpp.Presence(typ=None, priority=priority, show=sshow)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
-
- if self.connection:
- self.connection.send(p)
- self.priority = priority
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=show))
- if self.vcard_supported:
- # ask our VCard
- self.request_vcard(None)
-
- # Get bookmarks from private namespace
- self.get_bookmarks()
-
- # Get annotations from private namespace
- self.get_annotations()
-
- # Inform GUI we just signed in
- gajim.nec.push_incoming_event(SignedInEvent(None, conn=self))
- self.send_awaiting_pep()
- self.continue_connect_info = None
- # hashes of already received messages
- self.received_message_hashes = []
-
- def request_gmail_notifications(self):
- if not self.connection or self.connected < 2:
- return
- # It's a gmail account,
- # inform the server that we want e-mail notifications
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
- log.debug(('%s is a gmail account. Setting option '
- 'to get e-mail notifications on the server.') % (our_jid))
- iq = nbxmpp.Iq(typ='set', to=our_jid)
- iq.setAttr('id', 'MailNotify')
- query = iq.setTag('usersetting')
- query.setNamespace(nbxmpp.NS_GTALKSETTING)
- query = query.setTag('mailnotifications')
- query.setAttr('value', 'true')
- self.connection.send(iq)
- # Ask how many messages there are now
- iq = nbxmpp.Iq(typ='get')
- iq.setID(self.connection.getAnID())
- query = iq.setTag('query')
- query.setNamespace(nbxmpp.NS_GMAILNOTIFY)
- self.connection.send(iq)
-
- def _SearchCB(self, con, iq_obj):
- log.debug('SearchCB')
- gajim.nec.push_incoming_event(SearchFormReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _search_fields_received(self, con, iq_obj):
- jid = jid = helpers.get_jid_from_iq(iq_obj)
- tag = iq_obj.getTag('query', namespace = nbxmpp.NS_SEARCH)
- if not tag:
- self.dispatch('SEARCH_FORM', (jid, None, False))
- return
- df = tag.getTag('x', namespace=nbxmpp.NS_DATA)
- if df:
- self.dispatch('SEARCH_FORM', (jid, df, True))
- return
- df = {}
- for i in iq_obj.getQueryPayload():
- df[i.getName()] = i.getData()
- self.dispatch('SEARCH_FORM', (jid, df, False))
-
- def _PubkeyGetCB(self, con, iq_obj):
- log.info('PubkeyGetCB')
- jid_from = helpers.get_full_jid_from_iq(iq_obj)
- sid = iq_obj.getAttr('id')
- jingle_xtls.send_cert(con, jid_from, sid)
- raise nbxmpp.NodeProcessed
-
- def _PubkeyResultCB(self, con, iq_obj):
- log.info('PubkeyResultCB')
- jid_from = helpers.get_full_jid_from_iq(iq_obj)
- jingle_xtls.handle_new_cert(con, iq_obj, jid_from)
-
- def _BlockingSetCB(self, con, iq_obj):
- log.debug('_BlockingSetCB')
- gajim.nec.push_incoming_event(BlockingEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_blocking(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.unblock_all:
- self.blocked_contacts = []
- else:
- for jid in obj.blocked_jids:
- if jid not in self.blocked_contacts:
- self.blocked_contacts.append(jid)
- for jid in obj.unblocked_jids:
- if jid in self.blocked_contacts:
- self.blocked_contacts.remove(jid)
-
- def _nec_stream_other_host_received(self, obj):
- if obj.conn.name != self.name:
- return
- self.redirected = obj.redirected
-
- def _StreamCB(self, con, obj):
- log.debug('StreamCB')
- gajim.nec.push_incoming_event(StreamReceivedEvent(None,
- conn=self, stanza=obj))
-
- def _register_handlers(self, con, con_type):
- # try to find another way to register handlers in each class
- # that defines handlers
- con.RegisterHandler('message', self._messageCB)
- con.RegisterHandler('presence', self._presenceCB)
- # We use makefirst so that this handler is called before _messageCB, and
- # can prevent calling it when it's not needed.
- # We also don't check for namespace, else it cannot stop _messageCB to
- # be called
- con.RegisterHandler('message', self._pubsubEventCB, makefirst=True)
- con.RegisterHandler('iq', self._vCardCB, 'result', nbxmpp.NS_VCARD)
- con.RegisterHandler('iq', self._rosterSetCB, 'set', nbxmpp.NS_ROSTER)
- con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI)
- con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set',
- nbxmpp.NS_ROSTERX)
- con.RegisterHandler('iq', self._siErrorCB, 'error', nbxmpp.NS_SI)
- con.RegisterHandler('iq', self._siResultCB, 'result', nbxmpp.NS_SI)
- con.RegisterHandler('iq', self._discoGetCB, 'get', nbxmpp.NS_DISCO)
- con.RegisterHandler('iq', self._bytestreamSetCB, 'set',
- nbxmpp.NS_BYTESTREAM)
- con.RegisterHandler('iq', self._bytestreamResultCB, 'result',
- nbxmpp.NS_BYTESTREAM)
- con.RegisterHandler('iq', self._bytestreamErrorCB, 'error',
- nbxmpp.NS_BYTESTREAM)
- con.RegisterHandlerOnce('iq', self.IBBAllIqHandler)
- con.RegisterHandler('iq', self.IBBIqHandler, ns=nbxmpp.NS_IBB)
- con.RegisterHandler('message', self.IBBMessageHandler, ns=nbxmpp.NS_IBB)
- con.RegisterHandler('iq', self._DiscoverItemsCB, 'result',
- nbxmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error',
- nbxmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._DiscoverInfoCB, 'result',
- nbxmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error',
- nbxmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._VersionCB, 'get', nbxmpp.NS_VERSION)
- con.RegisterHandler('iq', self._TimeCB, 'get', nbxmpp.NS_TIME)
- con.RegisterHandler('iq', self._TimeRevisedCB, 'get',
- nbxmpp.NS_TIME_REVISED)
- con.RegisterHandler('iq', self._LastCB, 'get', nbxmpp.NS_LAST)
- con.RegisterHandler('iq', self._LastResultCB, 'result', nbxmpp.NS_LAST)
- con.RegisterHandler('iq', self._VersionResultCB, 'result',
- nbxmpp.NS_VERSION)
- con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result',
- nbxmpp.NS_TIME_REVISED)
- con.RegisterHandler('iq', self._MucOwnerCB, 'result',
- nbxmpp.NS_MUC_OWNER)
- con.RegisterHandler('iq', self._MucAdminCB, 'result',
- nbxmpp.NS_MUC_ADMIN)
- con.RegisterHandler('iq', self._PrivateCB, 'result', nbxmpp.NS_PRIVATE)
- con.RegisterHandler('iq', self._SecLabelCB, 'result',
- nbxmpp.NS_SECLABEL_CATALOG)
- con.RegisterHandler('iq', self._HttpAuthCB, 'get', nbxmpp.NS_HTTP_AUTH)
- con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
- nbxmpp.NS_COMMANDS)
- con.RegisterHandler('iq', self._gMailNewMailCB, 'set',
- nbxmpp.NS_GMAILNOTIFY)
- con.RegisterHandler('iq', self._gMailQueryCB, 'result',
- nbxmpp.NS_GMAILNOTIFY)
- con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
- nbxmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
- nbxmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._IqPingCB, 'get', nbxmpp.NS_PING)
- con.RegisterHandler('iq', self._SearchCB, 'result', nbxmpp.NS_SEARCH)
- con.RegisterHandler('iq', self._PrivacySetCB, 'set', nbxmpp.NS_PRIVACY)
- con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_ARCHIVE)
- con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM)
- con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_1)
- con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_2)
- con.RegisterHandler('iq', self._PubSubCB, 'result')
- con.RegisterHandler('iq', self._PubSubErrorCB, 'error')
- con.RegisterHandler('iq', self._JingleCB, 'result')
- con.RegisterHandler('iq', self._JingleCB, 'error')
- con.RegisterHandler('iq', self._JingleCB, 'set', nbxmpp.NS_JINGLE)
- con.RegisterHandler('iq', self._ErrorCB, 'error')
- con.RegisterHandler('iq', self._IqCB)
- con.RegisterHandler('iq', self._StanzaArrivedCB)
- con.RegisterHandler('iq', self._ResultCB, 'result')
- con.RegisterHandler('presence', self._StanzaArrivedCB)
- con.RegisterHandler('message', self._StanzaArrivedCB)
- con.RegisterHandler('unknown', self._StreamCB,
- nbxmpp.NS_XMPP_STREAMS, xmlns=nbxmpp.NS_STREAMS)
- con.RegisterHandler('iq', self._PubkeyGetCB, 'get',
- nbxmpp.NS_PUBKEY_PUBKEY)
- con.RegisterHandler('iq', self._PubkeyResultCB, 'result',
- nbxmpp.NS_PUBKEY_PUBKEY)
- con.RegisterHandler('iq', self._BlockingSetCB, 'set',
- nbxmpp.NS_BLOCKING)
diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py
deleted file mode 100644
index f820db879..000000000
--- a/src/common/connection_handlers_events.py
+++ /dev/null
@@ -1,2834 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/connection_handlers_events.py
-##
-## Copyright (C) 2010-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## 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/>.
-##
-
-from calendar import timegm
-import datetime
-import hashlib
-import hmac
-import logging
-import sys
-import os
-from time import time as time_time
-
-import nbxmpp
-from nbxmpp.protocol import NS_CHATSTATES
-from common import atom
-from common import nec
-from common import helpers
-from common import gajim
-from common import i18n
-from common import dataforms
-from common import exceptions
-from common.zeroconf.zeroconf import Constant
-from common.logger import LOG_DB_PATH
-from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
-from common.jingle_transport import JingleTransportSocks5
-from common.file_props import FilesProp
-
-if gajim.HAVE_PYOPENSSL:
- import OpenSSL.crypto
-
-log = logging.getLogger('gajim.c.connection_handlers_events')
-
-CONDITION_TO_CODE = {
- 'realjid-public': 100,
- 'affiliation-changed': 101,
- 'unavailable-shown': 102,
- 'unavailable-not-shown': 103,
- 'configuration-changed': 104,
- 'self-presence': 110,
- 'logging-enabled': 170,
- 'logging-disabled': 171,
- 'non-anonymous': 172,
- 'semi-anonymous': 173,
- 'fully-anonymous': 174,
- 'room-created': 201,
- 'nick-assigned': 210,
- 'banned': 301,
- 'new-nick': 303,
- 'kicked': 307,
- 'removed-affiliation': 321,
- 'removed-membership': 322,
- 'removed-shutdown': 332,
-}
-
-class HelperEvent:
- def get_jid_resource(self, check_fake_jid=False):
- if check_fake_jid and hasattr(self, 'id_') and \
- self.id_ in self.conn.groupchat_jids:
- self.fjid = self.conn.groupchat_jids[self.id_]
- del self.conn.groupchat_jids[self.id_]
- else:
- self.fjid = helpers.get_full_jid_from_iq(self.stanza)
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
-
- def get_id(self):
- self.id_ = self.stanza.getID()
-
- def get_gc_control(self):
- self.gc_control = gajim.interface.msg_win_mgr.get_gc_control(self.jid,
- self.conn.name)
-
- # If gc_control is missing - it may be minimized. Try to get it
- # from there. If it's not there - then it's missing anyway and
- # will remain set to None.
- if not self.gc_control:
- minimized = gajim.interface.minimized_controls[self.conn.name]
- self.gc_control = minimized.get(self.jid)
-
- def _generate_timestamp(self, tag):
- # Make sure we use only int/float Epoch time
- if not isinstance(tag, str):
- self.timestamp = time_time()
- return
- try:
- tim = helpers.datetime_tuple(tag)
- self.timestamp = timegm(tim)
- except Exception:
- log.error('wrong timestamp, ignoring it: ' + tag)
- self.timestamp = time_time()
-
- def get_chatstate(self):
- """
- Extract chatstate from a <message/> stanza
- Requires self.stanza and self.msgtxt
- """
- self.chatstate = None
-
- # chatstates - look for chatstate tags in a message if not delayed
- delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not None
- if not delayed:
- children = self.stanza.getChildren()
- for child in children:
- if child.getNamespace() == NS_CHATSTATES:
- self.chatstate = child.getName()
- break
-
-class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
- name = 'http-auth-received'
- base_network_events = []
-
- def generate(self):
- self.opt = gajim.config.get_per('accounts', self.conn.name, 'http_auth')
- self.iq_id = self.stanza.getTagAttr('confirm', 'id')
- self.method = self.stanza.getTagAttr('confirm', 'method')
- self.url = self.stanza.getTagAttr('confirm', 'url')
- # In case it's a message with a body
- self.msg = self.stanza.getTagData('body')
- return True
-
-class LastResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'last-result-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource(check_fake_jid=True)
- if self.id_ in self.conn.last_ids:
- self.conn.last_ids.remove(self.id_)
-
- self.status = ''
- self.seconds = -1
-
- if self.stanza.getType() == 'error':
- return True
-
- qp = self.stanza.getTag('query')
- if not qp:
- return
- sec = qp.getAttr('seconds')
- self.status = qp.getData()
- try:
- self.seconds = int(sec)
- except Exception:
- return
-
- return True
-
-class VersionResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'version-result-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource(check_fake_jid=True)
- if self.id_ in self.conn.version_ids:
- self.conn.version_ids.remove(self.id_)
-
- self.client_info = ''
- self.os_info = ''
-
- if self.stanza.getType() == 'error':
- return True
-
- qp = self.stanza.getTag('query')
- if qp.getTag('name'):
- self.client_info += qp.getTag('name').getData()
- if qp.getTag('version'):
- self.client_info += ' ' + qp.getTag('version').getData()
- if qp.getTag('os'):
- self.os_info += qp.getTag('os').getData()
-
- return True
-
-class TimeResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'time-result-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource(check_fake_jid=True)
- if self.id_ in self.conn.entity_time_ids:
- self.conn.entity_time_ids.remove(self.id_)
-
- self.time_info = ''
-
- if self.stanza.getType() == 'error':
- return True
-
- qp = self.stanza.getTag('time')
- if not qp:
- # wrong answer
- return
- tzo = qp.getTag('tzo').getData()
- if tzo.lower() == 'z':
- tzo = '0:0'
- try:
- tzoh, tzom = tzo.split(':')
- except Exception as e:
- # wrong tzo
- return
- utc_time = qp.getTag('utc').getData()
- ZERO = datetime.timedelta(0)
- class UTC(datetime.tzinfo):
- def utcoffset(self, dt):
- return ZERO
- def tzname(self, dt):
- return "UTC"
- def dst(self, dt):
- return ZERO
-
- class contact_tz(datetime.tzinfo):
- def utcoffset(self, dt):
- return datetime.timedelta(hours=int(tzoh), minutes=int(tzom))
- def tzname(self, dt):
- return "remote timezone"
- def dst(self, dt):
- return ZERO
-
- if utc_time[-1:] == 'Z':
- # Remove the trailing 'Z'
- utc_time = utc_time[:-1]
- elif utc_time[-6:] == "+00:00":
- # Remove the trailing "+00:00"
- utc_time = utc_time[:-6]
- else:
- log.info("Wrong timezone defintion: %s" % utc_time)
- return
- try:
- t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%S')
- except ValueError:
- try:
- t = datetime.datetime.strptime(utc_time,
- '%Y-%m-%dT%H:%M:%S.%f')
- except ValueError as e:
- log.info('Wrong time format: %s' % str(e))
- return
-
- t = t.replace(tzinfo=UTC())
- self.time_info = t.astimezone(contact_tz()).strftime('%c')
- return True
-
-class GMailQueryReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gmail-notify'
- base_network_events = []
-
- def generate(self):
- if not self.stanza.getTag('mailbox'):
- return
- mb = self.stanza.getTag('mailbox')
- if not mb.getAttr('url'):
- return
- self.conn.gmail_url = mb.getAttr('url')
- if mb.getNamespace() != nbxmpp.NS_GMAILNOTIFY:
- return
- self.newmsgs = mb.getAttr('total-matched')
- if not self.newmsgs:
- return
- if self.newmsgs == '0':
- return
- # there are new messages
- self.gmail_messages_list = []
- if mb.getTag('mail-thread-info'):
- gmail_messages = mb.getTags('mail-thread-info')
- for gmessage in gmail_messages:
- unread_senders = []
- for sender in gmessage.getTag('senders').getTags('sender'):
- if sender.getAttr('unread') != '1':
- continue
- if sender.getAttr('name'):
- unread_senders.append(sender.getAttr('name') + \
- '< ' + sender.getAttr('address') + '>')
- else:
- unread_senders.append(sender.getAttr('address'))
-
- if not unread_senders:
- continue
- gmail_subject = gmessage.getTag('subject').getData()
- gmail_snippet = gmessage.getTag('snippet').getData()
- tid = int(gmessage.getAttr('tid'))
- if not self.conn.gmail_last_tid or \
- tid > self.conn.gmail_last_tid:
- self.conn.gmail_last_tid = tid
- self.gmail_messages_list.append({
- 'From': unread_senders,
- 'Subject': gmail_subject,
- 'Snippet': gmail_snippet,
- 'url': gmessage.getAttr('url'),
- 'participation': gmessage.getAttr('participation'),
- 'messages': gmessage.getAttr('messages'),
- 'date': gmessage.getAttr('date')})
- self.conn.gmail_last_time = int(mb.getAttr('result-time'))
-
- self.jid = gajim.get_jid_from_account(self.name)
- log.debug('You have %s new gmail e-mails on %s.', self.newmsgs, self.jid)
- return True
-
-class RosterItemExchangeEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'roster-item-exchange-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource()
- self.exchange_items_list = {}
- items_list = self.stanza.getTag('x').getChildren()
- if not items_list:
- return
- self.action = items_list[0].getAttr('action')
- if self.action is None:
- self.action = 'add'
- for item in self.stanza.getTag('x', namespace=nbxmpp.NS_ROSTERX).\
- getChildren():
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
- continue
- name = item.getAttr('name')
- contact = gajim.contacts.get_contact(self.conn.name, jid)
- groups = []
- same_groups = True
- for group in item.getTags('group'):
- groups.append(group.getData())
- # check that all suggested groups are in the groups we have for
- # this contact
- if not contact or group not in contact.groups:
- same_groups = False
- if contact:
- # check that all groups we have for this contact are in the
- # suggested groups
- for group in contact.groups:
- if group not in groups:
- same_groups = False
- if contact.sub in ('both', 'to') and same_groups:
- continue
- self.exchange_items_list[jid] = []
- self.exchange_items_list[jid].append(name)
- self.exchange_items_list[jid].append(groups)
- if self.exchange_items_list:
- return True
-
-class VersionRequestEvent(nec.NetworkIncomingEvent):
- name = 'version-request-received'
- base_network_events = []
-
-class LastRequestEvent(nec.NetworkIncomingEvent):
- name = 'last-request-received'
- base_network_events = []
-
-class TimeRequestEvent(nec.NetworkIncomingEvent):
- name = 'time-request-received'
- base_network_events = []
-
-class TimeRevisedRequestEvent(nec.NetworkIncomingEvent):
- name = 'time-revised-request-received'
- base_network_events = []
-
-class RosterReceivedEvent(nec.NetworkIncomingEvent):
- name = 'roster-received'
- base_network_events = []
-
- def generate(self):
- if hasattr(self, 'xmpp_roster'):
- self.version = self.xmpp_roster.version
- self.received_from_server = self.xmpp_roster.received_from_server
- self.roster = {}
- raw_roster = self.xmpp_roster.getRaw()
- our_jid = gajim.get_jid_from_account(self.conn.name)
-
- for jid in raw_roster:
- try:
- j = helpers.parse_jid(jid)
- except Exception:
- print(_('JID %s is not RFC compliant. It will not be added '
- 'to your roster. Use roster management tools such as '
- 'http://jru.jabberstudio.org/ to remove it') % jid,
- file=sys.stderr)
- else:
- infos = raw_roster[jid]
- if jid != our_jid and (not infos['subscription'] or \
- infos['subscription'] == 'none') and (not infos['ask'] or \
- infos['ask'] == 'none') and not infos['name'] and \
- not infos['groups']:
- # remove this useless item, it won't be shown in roster
- # anyway
- self.conn.connection.getRoster().delItem(jid)
- elif jid != our_jid: # don't add our jid
- self.roster[j] = raw_roster[jid]
- else:
- # Roster comes from DB
- self.received_from_server = False
- self.version = gajim.config.get_per('accounts', self.conn.name,
- 'roster_version')
- self.roster = gajim.logger.get_roster(gajim.get_jid_from_account(
- self.conn.name))
- return True
-
-class RosterSetReceivedEvent(nec.NetworkIncomingEvent):
- name = 'roster-set-received'
- base_network_events = []
-
- def generate(self):
- frm = helpers.get_jid_from_iq(self.stanza)
- our_jid = gajim.get_jid_from_account(self.conn.name)
- if frm and frm != our_jid and frm != gajim.get_server_from_jid(our_jid):
- return
- self.version = self.stanza.getTagAttr('query', 'ver')
- self.items = {}
- for item in self.stanza.getTag('query').getChildren():
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
- continue
- name = item.getAttr('name')
- sub = item.getAttr('subscription')
- ask = item.getAttr('ask')
- groups = []
- for group in item.getTags('group'):
- groups.append(group.getData())
- self.items[jid] = {'name': name, 'sub': sub, 'ask': ask,
- 'groups': groups}
- if len(self.items) > 1:
- reply = nbxmpp.Iq(typ='error', attrs={'id': self.stanza.getID()},
- to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None)
- self.conn.connection.send(reply)
- return
- if self.conn.connection and self.conn.connected > 1:
- reply = nbxmpp.Iq(typ='result', attrs={'id': self.stanza.getID()},
- to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None)
- self.conn.connection.send(reply)
- return True
-
-class RosterInfoEvent(nec.NetworkIncomingEvent):
- name = 'roster-info'
- base_network_events = []
-
-class MucOwnerReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'muc-owner-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- qp = self.stanza.getQueryPayload()
- self.form_node = None
- for q in qp:
- if q.getNamespace() == nbxmpp.NS_DATA:
- self.form_node = q
- self.dataform = dataforms.ExtendForm(node=self.form_node)
- return True
-
-class MucAdminReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'muc-admin-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- items = self.stanza.getTag('query',
- namespace=nbxmpp.NS_MUC_ADMIN).getTags('item')
- self.users_dict = {}
- for item in items:
- if item.has_attr('jid') and item.has_attr('affiliation'):
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % \
- item.getAttr('jid'))
- continue
- affiliation = item.getAttr('affiliation')
- self.users_dict[jid] = {'affiliation': affiliation}
- if item.has_attr('nick'):
- self.users_dict[jid]['nick'] = item.getAttr('nick')
- if item.has_attr('role'):
- self.users_dict[jid]['role'] = item.getAttr('role')
- reason = item.getTagData('reason')
- if reason:
- self.users_dict[jid]['reason'] = reason
- return True
-
-class PrivateStorageReceivedEvent(nec.NetworkIncomingEvent):
- name = 'private-storage-received'
- base_network_events = []
-
- def generate(self):
- query = self.stanza.getTag('query')
- self.storage_node = query.getTag('storage')
- if self.storage_node:
- self.namespace = self.storage_node.getNamespace()
- return True
-
-
-class BookmarksHelper:
- def parse_bookmarks(self):
- self.bookmarks = []
- NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks'
- confs = self.storage_node.getTags('conference')
- for conf in confs:
- autojoin_val = conf.getAttr('autojoin')
- if not autojoin_val: # not there (it's optional)
- autojoin_val = False
- minimize_val = conf.getTag('minimize', namespace=NS_GAJIM_BM)
- if not minimize_val: # not there, try old Gajim behaviour
- minimize_val = conf.getAttr('minimize')
- if not minimize_val: # not there (it's optional)
- minimize_val = False
- else:
- minimize_val = minimize_val.getData()
-
- print_status = conf.getTag('print_status', namespace=NS_GAJIM_BM)
- if not print_status: # not there, try old Gajim behaviour
- print_status = conf.getTagData('print_status')
- if not print_status: # not there, try old Gajim behaviour
- print_status = conf.getTagData('show_status')
- else:
- print_status = print_status.getData()
-
- try:
- jid = helpers.parse_jid(conf.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it'
- % conf.getAttr('jid'))
- continue
-
- bm = {'name': conf.getAttr('name'),
- 'jid': jid,
- 'autojoin': autojoin_val,
- 'minimize': minimize_val,
- 'password': conf.getTagData('password'),
- 'nick': conf.getTagData('nick'),
- 'print_status': print_status}
-
- bm_jids = [b['jid'] for b in self.bookmarks]
- if bm['jid'] not in bm_jids:
- self.bookmarks.append(bm)
-
-class PrivateStorageBookmarksReceivedEvent(nec.NetworkIncomingEvent,
-BookmarksHelper):
- name = 'private-storage-bookmarks-received'
- base_network_events = ['private-storage-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.storage_node = self.base_event.storage_node
- if self.base_event.namespace != nbxmpp.NS_BOOKMARKS:
- return
- self.parse_bookmarks()
- return True
-
-class BookmarksReceivedEvent(nec.NetworkIncomingEvent):
- name = 'bookmarks-received'
- base_network_events = ['private-storage-bookmarks-received',
- 'pubsub-bookmarks-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.bookmarks = self.base_event.bookmarks
- return True
-
-class PrivateStorageRosternotesReceivedEvent(nec.NetworkIncomingEvent):
- name = 'private-storage-rosternotes-received'
- base_network_events = ['private-storage-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- if self.base_event.namespace != nbxmpp.NS_ROSTERNOTES:
- return
- notes = self.base_event.storage_node.getTags('note')
- self.annotations = {}
- for note in notes:
- try:
- jid = helpers.parse_jid(note.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % note.getAttr('jid'))
- continue
- annotation = note.getData()
- self.annotations[jid] = annotation
- if self.annotations:
- return True
-
-class RosternotesReceivedEvent(nec.NetworkIncomingEvent):
- name = 'rosternotes-received'
- base_network_events = ['private-storage-rosternotes-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.annotations = self.base_event.annotations
- return True
-
-class PubsubReceivedEvent(nec.NetworkIncomingEvent):
- name = 'pubsub-received'
- base_network_events = []
-
- def generate(self):
- self.pubsub_node = self.stanza.getTag('pubsub')
- if not self.pubsub_node:
- return
- self.items_node = self.pubsub_node.getTag('items')
- if not self.items_node:
- return
- self.item_node = self.items_node.getTag('item')
- if not self.item_node:
- return
- children = self.item_node.getChildren()
- if not children:
- return
- self.node = children[0]
- return True
-
-class PubsubBookmarksReceivedEvent(nec.NetworkIncomingEvent, BookmarksHelper):
- name = 'pubsub-bookmarks-received'
- base_network_events = ['pubsub-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.storage_node = self.base_event.node
- ns = self.storage_node.getNamespace()
- if ns != nbxmpp.NS_BOOKMARKS:
- return
- self.parse_bookmarks()
- return True
-
-class SearchFormReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'search-form-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- self.data = None
- self.is_dataform = False
- tag = self.stanza.getTag('query', namespace=nbxmpp.NS_SEARCH)
- if not tag:
- return True
- self.data = tag.getTag('x', namespace=nbxmpp.NS_DATA)
- if self.data:
- self.is_dataform = True
- return True
- self.data = {}
- for i in self.stanza.getQueryPayload():
- self.data[i.getName()] = i.getData()
- return True
-
-
-class SearchResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'search-result-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- self.data = None
- self.is_dataform = False
- tag = self.stanza.getTag('query', namespace=nbxmpp.NS_SEARCH)
- if not tag:
- return True
- self.data = tag.getTag('x', namespace=nbxmpp.NS_DATA)
- if self.data:
- self.is_dataform = True
- return True
- self.data = []
- for item in tag.getTags('item'):
- # We also show attributes. jid is there
- f = item.attrs
- for i in item.getPayload():
- f[i.getName()] = i.getData()
- self.data.append(f)
- return True
-
-class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'iq-error-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource(check_fake_jid=True)
- self.errmsg = self.stanza.getErrorMsg()
- self.errcode = self.stanza.getErrorCode()
- return True
-
-class GmailNewMailReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gmail-new-mail-received'
- base_network_events = []
-
- def generate(self):
- if not self.stanza.getTag('new-mail'):
- return
- if self.stanza.getTag('new-mail').getNamespace() != \
- nbxmpp.NS_GMAILNOTIFY:
- return
- return True
-
-class PingReceivedEvent(nec.NetworkIncomingEvent):
- name = 'ping-received'
- base_network_events = []
-
-class StreamReceivedEvent(nec.NetworkIncomingEvent):
- name = 'stream-received'
- base_network_events = []
-
-class StreamConflictReceivedEvent(nec.NetworkIncomingEvent):
- name = 'stream-conflict-received'
- base_network_events = ['stream-received']
-
- def generate(self):
- if self.base_event.stanza.getTag('conflict'):
- self.conn = self.base_event.conn
- return True
-
-class StreamOtherHostReceivedEvent(nec.NetworkIncomingEvent):
- name = 'stream-other-host-received'
- base_network_events = ['stream-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- other_host = self.stanza.getTag('see-other-host')
- if other_host and self.conn._current_type in ('ssl', 'tls'):
- host = other_host.getData()
- if ':' in host:
- host_l = host.split(':', 1)
- h = host_l[0]
- p = host_l[1]
- else:
- h = host
- p = 5222
- if h.startswith('[') and h.endswith(']'):
- h = h[1:-1]
- self.redirected = {'host': h, 'port': p}
- return True
-
-class PresenceHelperEvent:
- def _generate_show(self):
- self.show = self.stanza.getShow()
- if self.show not in ('chat', 'away', 'xa', 'dnd'):
- self.show = '' # We ignore unknown show
- if not self.ptype and not self.show:
- self.show = 'online'
- elif self.ptype == 'unavailable':
- self.show = 'offline'
-
- def _generate_ptype(self):
- self.ptype = self.stanza.getType()
- if self.ptype == 'available':
- self.ptype = None
- rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed',
- 'unsubscribe', 'unsubscribed')
- if self.ptype and not self.ptype in rfc_types:
- self.ptype = None
-
-class PresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent,
-PresenceHelperEvent):
- name = 'presence-received'
- base_network_events = ['raw-pres-received']
-
- def _generate_keyID(self, sig_tag):
- self.keyID = ''
- if sig_tag and self.conn.USE_GPG and self.ptype != 'error':
- # error presences contain our own signature
- # verify
- sig_msg = sig_tag.getData()
- self.keyID = self.conn.gpg.verify(self.status, sig_msg)
- self.keyID = helpers.prepare_and_validate_gpg_keyID(self.conn.name,
- self.jid,
- self.keyID)
-
- def _generate_prio(self):
- self.prio = self.stanza.getPriority()
- try:
- self.prio = int(self.prio)
- except Exception:
- self.prio = 0
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
-
- self.need_add_in_roster = False
- self.need_redraw = False
-
- self.popup = False # Do we want to open chat window ?
-
- if not self.conn or self.conn.connected < 2:
- log.debug('account is no more connected')
- return
-
- self._generate_ptype()
- try:
- self.get_jid_resource()
- except Exception:
- log.warning('Invalid JID: %s, ignoring it' % self.stanza.getFrom())
- return
- jid_list = gajim.contacts.get_jid_list(self.conn.name)
- self.timestamp = None
- self.get_id()
- self.is_gc = False # is it a GC presence ?
- sig_tag = None
- self.avatar_sha = None
- # XEP-0172 User Nickname
- self.user_nick = self.stanza.getTagData('nick') or ''
- self.contact_nickname = None
- self.transport_auto_auth = False
- # XEP-0203
- delay_tag = self.stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2)
- if delay_tag:
- self._generate_timestamp(self.stanza.getTimestamp2())
- xtags = self.stanza.getTags('x')
- for x in xtags:
- namespace = x.getNamespace()
- if namespace.startswith(nbxmpp.NS_MUC):
- self.is_gc = True
- elif namespace == nbxmpp.NS_SIGNED:
- sig_tag = x
- elif namespace == nbxmpp.NS_VCARD_UPDATE:
- self.avatar_sha = x.getTagData('photo')
- self.contact_nickname = x.getTagData('nickname')
- elif namespace == nbxmpp.NS_DELAY and not self.timestamp:
- # XEP-0091
- self._generate_timestamp(self.stanza.getTimestamp())
- elif namespace == 'http://delx.cjb.net/protocol/roster-subsync':
- # see http://trac.gajim.org/ticket/326
- agent = gajim.get_server_from_jid(self.jid)
- if self.conn.connection.getRoster().getItem(agent):
- # to be sure it's a transport contact
- self.transport_auto_auth = True
-
- if not self.is_gc and self.id_ and self.id_.startswith('gajim_muc_') \
- and self.ptype == 'error':
- # Error presences may not include sent stanza, so we don't detect
- # it's a muc presence. So detect it by ID
- h = hmac.new(self.conn.secret_hmac, self.jid.encode('utf-8'),
- hashlib.md5).hexdigest()[:6]
- if self.id_.split('_')[-1] == h:
- self.is_gc = True
- self.status = self.stanza.getStatus() or ''
- self._generate_show()
- self._generate_prio()
- self._generate_keyID(sig_tag)
-
- self.errcode = self.stanza.getErrorCode()
- self.errmsg = self.stanza.getErrorMsg()
-
- if self.is_gc:
- gajim.nec.push_incoming_event(
- GcPresenceReceivedEvent(
- None, conn=self.conn, stanza=self.stanza,
- presence_obj=self))
- return
-
- if self.ptype == 'subscribe':
- gajim.nec.push_incoming_event(SubscribePresenceReceivedEvent(None,
- conn=self.conn, stanza=self.stanza, presence_obj=self))
- elif self.ptype == 'subscribed':
- # BE CAREFUL: no con.updateRosterItem() in a callback
- gajim.nec.push_incoming_event(SubscribedPresenceReceivedEvent(None,
- conn=self.conn, stanza=self.stanza, presence_obj=self))
- elif self.ptype == 'unsubscribe':
- log.debug(_('unsubscribe request from %s') % self.jid)
- elif self.ptype == 'unsubscribed':
- gajim.nec.push_incoming_event(UnsubscribedPresenceReceivedEvent(
- None, conn=self.conn, stanza=self.stanza, presence_obj=self))
- elif self.ptype == 'error':
- return
-
- if not self.ptype or self.ptype == 'unavailable':
- our_jid = gajim.get_jid_from_account(self.conn.name)
- if self.jid == our_jid and self.resource == self.conn.server_resource:
- # We got our own presence
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self.conn,
- show=self.show))
- elif self.jid in jid_list or self.jid == our_jid:
- return True
-
-class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent):
- name = 'presence-received'
- base_network_events = []
-
- def generate(self):
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.resource = 'local'
- self.prio = 0
- self.keyID = None
- self.timestamp = 0
- self.contact_nickname = None
- self.avatar_sha = None
- self.need_add_in_roster = False
- self.need_redraw = False
- if self.show == 'offline':
- self.ptype = 'unavailable'
- else:
- self.ptype = None
- self.is_gc = False
- self.user_nick = ''
- self.transport_auto_auth = False
- self.errcode = None
- self.errmsg = ''
- self.popup = False # Do we want to open chat window ?
- return True
-
-class GcPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'gc-presence-received'
- base_network_events = []
-
- def generate(self):
- self.ptype = self.presence_obj.ptype
- self.fjid = self.presence_obj.fjid
- self.jid = self.presence_obj.jid
- self.room_jid = self.presence_obj.jid
- self.nick = self.presence_obj.resource
- self.show = self.presence_obj.show
- self.status = self.presence_obj.status
- self.avatar_sha = self.presence_obj.avatar_sha
- self.errcode = self.presence_obj.errcode
- self.errmsg = self.presence_obj.errmsg
- self.errcon = self.stanza.getError()
- self.get_gc_control()
- self.gc_contact = gajim.contacts.get_gc_contact(self.conn.name,
- self.room_jid, self.nick)
-
- if self.ptype == 'error':
- return True
-
- if self.ptype and self.ptype != 'unavailable':
- return
- if gajim.config.get('log_contact_status_changes') and \
- gajim.config.should_log(self.conn.name, self.room_jid):
- if self.gc_contact:
- jid = self.gc_contact.jid
- else:
- jid = self.stanza.getJid()
- st = self.status
- if jid:
- # we know real jid, save it in db
- st += ' (%s)' % jid
- gajim.logger.write('gcstatus', self.fjid, st, self.show)
- if self.avatar_sha == '':
- # contact has no avatar
- puny_nick = helpers.sanitize_filename(self.nick)
- gajim.interface.remove_avatar_files(self.room_jid, puny_nick)
- # NOTE: if it's a gc presence, don't ask vcard here.
- # We may ask it to real jid in gui part.
- self.status_code = []
- ns_muc_user_x = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
- if ns_muc_user_x:
- destroy = ns_muc_user_x.getTag('destroy')
- else:
- destroy = None
- if ns_muc_user_x and destroy:
- # Room has been destroyed. see
- # http://www.xmpp.org/extensions/xep-0045.html#destroyroom
- self.reason = _('Room has been destroyed')
- r = destroy.getTagData('reason')
- if r:
- self.reason += ' (%s)' % r
- if destroy.getAttr('jid'):
- try:
- jid = helpers.parse_jid(destroy.getAttr('jid'))
- self.reason += '\n' + \
- _('You can join this room instead: %s') % jid
- except helpers.InvalidFormat:
- pass
- self.status_code = ['destroyed']
- else:
- self.reason = self.stanza.getReason()
- conditions = self.stanza.getStatusConditions()
- if conditions:
- self.status_code = []
- for condition in conditions:
- if condition in CONDITION_TO_CODE:
- self.status_code.append(CONDITION_TO_CODE[condition])
- else:
- self.status_code = self.stanza.getStatusCode()
-
- self.role = self.stanza.getRole()
- self.affiliation = self.stanza.getAffiliation()
- self.real_jid = self.stanza.getJid()
- self.actor = self.stanza.getActor()
- self.new_nick = self.stanza.getNewNick()
- return True
-
-class SubscribePresenceReceivedEvent(nec.NetworkIncomingEvent):
- name = 'subscribe-presence-received'
- base_network_events = []
-
- def generate(self):
- self.jid = self.presence_obj.jid
- self.fjid = self.presence_obj.fjid
- self.status = self.presence_obj.status
- self.transport_auto_auth = self.presence_obj.transport_auto_auth
- self.user_nick = self.presence_obj.user_nick
- return True
-
-class SubscribedPresenceReceivedEvent(nec.NetworkIncomingEvent):
- name = 'subscribed-presence-received'
- base_network_events = []
-
- def generate(self):
- self.jid = self.presence_obj.jid
- self.resource = self.presence_obj.resource
- return True
-
-class UnsubscribedPresenceReceivedEvent(nec.NetworkIncomingEvent):
- name = 'unsubscribed-presence-received'
- base_network_events = []
-
- def generate(self):
- self.jid = self.presence_obj.jid
- return True
-
-class OurShowEvent(nec.NetworkIncomingEvent):
- name = 'our-show'
- base_network_events = []
-
-class BeforeChangeShowEvent(nec.NetworkIncomingEvent):
- name = 'before-change-show'
- base_network_events = []
-
-class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'mam-message-received'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
- self.encrypted = False
-
- def generate(self):
- if not self.stanza:
- return
- account = self.conn.name
- self.msg_ = self.stanza.getTag('message')
- # use timestamp of archived message, if available and archive timestamp otherwise
- delay = self.stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2)
- delay2 = self.msg_.getTag('delay', namespace=nbxmpp.NS_DELAY2)
- if delay2:
- delay = delay2
- if not delay:
- return
- tim = delay.getAttr('stamp')
- tim = helpers.datetime_tuple(tim)
- self.tim = timegm(tim)
- to_ = self.msg_.getAttr('to')
- if to_:
- to_ = gajim.get_jid_without_resource(to_)
- else:
- to_ = gajim.get_jid_from_account(account)
- frm_ = gajim.get_jid_without_resource(self.msg_.getAttr('from'))
- self.msgtxt = self.msg_.getTagData('body')
- if to_ == gajim.get_jid_from_account(account):
- self.with_ = frm_
- self.direction = 'from'
- self.resource = gajim.get_resource_from_jid(
- self.msg_.getAttr('from'))
- else:
- self.with_ = to_
- self.direction = 'to'
- self.resource = gajim.get_resource_from_jid(self.msg_.getAttr('to'))
- return True
-
-class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'mam-decrypted-message-received'
- base_network_events = []
-
- def generate(self):
- self.nick = None
- msg_ = self.msg_obj.msg_
- if not hasattr(self, 'additional_data'):
- self.additional_data = self.msg_obj.additional_data
- self.with_ = self.msg_obj.with_
- self.direction = self.msg_obj.direction
- self.tim = self.msg_obj.tim
- res = self.msg_obj.resource
- self.msgtxt = self.msg_obj.msgtxt
- is_pm = gajim.logger.jid_is_room_jid(self.with_)
- if msg_.getAttr('type') == 'groupchat':
- if is_pm == False:
- log.warn('JID %s is marked as normal contact in database '
- 'but we got a groupchat message from it.')
- return
- if is_pm == None:
- gajim.logger.get_jid_id(self.with_, 'ROOM')
- self.nick = res
- else:
- if is_pm == None:
- # we don't know this JID, we need to disco it.
- server = gajim.get_server_from_jid(self.with_)
- if server not in self.conn.mam_awaiting_disco_result:
- self.conn.mam_awaiting_disco_result[server] = [
- [self.with_, self.direction, self.tim, self.msgtxt,
- res]]
- self.conn.discoverInfo(server)
- else:
- self.conn.mam_awaiting_disco_result[server].append(
- [self.with_, self.direction, self.tim, self.msgtxt,
- res])
- return
- return True
-
-class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'message-received'
- base_network_events = ['raw-message-received']
-
- def init(self):
- self.additional_data = {}
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.get_id()
- self.forwarded = False
- self.sent = False
- self.encrypted = False
- account = self.conn.name
-
- our_full_jid = gajim.get_jid_from_account(account, full=True)
- if self.stanza.getFrom() == our_full_jid:
- # Drop messages sent from our own full jid
- # It can happen that when we sent message to our own bare jid
- # that the server routes that message back to us
- log.info('Received message from self: %s, message is dropped'
- % self.stanza.getFrom())
- return
-
- # check if the message is a roster item exchange (XEP-0144)
- if self.stanza.getTag('x', namespace=nbxmpp.NS_ROSTERX):
- gajim.nec.push_incoming_event(RosterItemExchangeEvent(None,
- conn=self.conn, stanza=self.stanza))
- return
-
- # check if the message is a XEP-0070 confirmation request
- if self.stanza.getTag('confirm', namespace=nbxmpp.NS_HTTP_AUTH):
- gajim.nec.push_incoming_event(HttpAuthReceivedEvent(None,
- conn=self.conn, stanza=self.stanza))
- return
-
- try:
- self.get_jid_resource()
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- self.stanza.getFrom())
- return
-
- address_tag = self.stanza.getTag('addresses',
- namespace=nbxmpp.NS_ADDRESS)
- # Be sure it comes from one of our resource, else ignore address element
- if address_tag and self.jid == gajim.get_jid_from_account(account):
- address = address_tag.getTag('address', attrs={'type': 'ofrom'})
- if address:
- try:
- self.fjid = helpers.parse_jid(address.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- address.getAttr('jid'))
- return
- self.jid = gajim.get_jid_without_resource(self.fjid)
-
- carbon_marker = self.stanza.getTag('sent', namespace=nbxmpp.NS_CARBONS)
- if not carbon_marker:
- carbon_marker = self.stanza.getTag('received',
- namespace=nbxmpp.NS_CARBONS)
- # Be sure it comes from one of our resource, else ignore forward element
- if carbon_marker and self.jid == gajim.get_jid_from_account(account):
- forward_tag = carbon_marker.getTag('forwarded',
- namespace=nbxmpp.NS_FORWARD)
- if forward_tag:
- msg = forward_tag.getTag('message')
- self.stanza = nbxmpp.Message(node=msg)
- self.get_id()
- if carbon_marker.getName() == 'sent':
- to = self.stanza.getTo()
- frm = self.stanza.getFrom()
- if not frm:
- frm = gajim.get_jid_from_account(account)
- self.stanza.setTo(frm)
- if not to:
- to = gajim.get_jid_from_account(account)
- self.stanza.setFrom(to)
- self.sent = True
- elif carbon_marker.getName() == 'received':
- full_frm = str(self.stanza.getFrom())
- frm = gajim.get_jid_without_resource(full_frm)
- if frm == gajim.get_jid_from_account(account):
- # Drop 'received' Carbons from ourself, we already
- # got the message with the 'sent' Carbon or via the
- # message itself
- log.info(
- 'Drop "received"-Carbon from ourself: %s'
- % full_frm)
- return
- try:
- self.get_jid_resource()
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- self.stanza.getFrom())
- return
- self.forwarded = True
-
- result = self.stanza.getTag('result')
- if result and result.getNamespace() in (nbxmpp.NS_MAM,
- nbxmpp.NS_MAM_1,
- nbxmpp.NS_MAM_2):
- forwarded = result.getTag('forwarded', namespace=nbxmpp.NS_FORWARD)
- gajim.nec.push_incoming_event(MamMessageReceivedEvent(None,
- conn=self.conn, stanza=forwarded))
- return
-
- # Mediated invitation?
- muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
- if muc_user:
- if muc_user.getTag('decline'):
- gajim.nec.push_incoming_event(
- GcDeclineReceivedEvent(
- None, conn=self.conn,
- room_jid=self.fjid, stanza=muc_user))
- return
- if muc_user.getTag('invite'):
- gajim.nec.push_incoming_event(
- GcInvitationReceivedEvent(
- None, conn=self.conn, jid_from=self.fjid,
- mediated=True, stanza=muc_user))
- return
- else:
- # Direct invitation?
- direct = self.stanza.getTag(
- 'x', namespace=nbxmpp.NS_CONFERENCE)
- if direct:
- gajim.nec.push_incoming_event(
- GcInvitationReceivedEvent(
- None, conn=self.conn, jid_from=self.fjid,
- mediated=False, stanza=direct))
- return
-
- self.thread_id = self.stanza.getThread()
- self.mtype = self.stanza.getType()
- if not self.mtype or self.mtype not in ('chat', 'groupchat', 'error'):
- self.mtype = 'normal'
-
- self.msgtxt = self.stanza.getBody()
-
- self.get_gc_control()
-
- if self.gc_control and self.jid == self.fjid:
- if self.mtype == 'error':
- self.msgtxt = _('error while sending %(message)s ( %(error)s )'\
- ) % {'message': self.msgtxt,
- 'error': self.stanza.getErrorMsg()}
- if self.stanza.getTag('html'):
- self.stanza.delChild('html')
- # message from a gc without a resource
- self.mtype = 'groupchat'
-
- self.session = None
- if self.mtype != 'groupchat':
- if gajim.interface.is_pm_contact(self.fjid, account) and \
- self.mtype == 'error':
- self.session = self.conn.find_session(self.fjid, self.thread_id)
- if not self.session:
- self.session = self.conn.get_latest_session(self.fjid)
- if not self.session:
- self.session = self.conn.make_new_session(self.fjid,
- self.thread_id,
- type_='pm')
- else:
- self.session = self.conn.get_or_create_session(self.fjid,
- self.thread_id)
-
- if self.thread_id and not self.session.received_thread_id:
- self.session.received_thread_id = True
-
- self.session.last_receive = time_time()
-
- self._generate_timestamp(self.stanza.getTimestamp())
-
- return True
-
-class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
- name = 'message-received'
- base_network_events = []
-
- def get_jid_resource(self):
- self.fjid =self.stanza.getFrom()
-
- if self.fjid is None:
- for key in self.conn.connection.zeroconf.contacts:
- if self.ip == self.conn.connection.zeroconf.contacts[key][
- Constant.ADDRESS]:
- self.fjid = key
- break
-
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
-
- def generate(self):
- self.base_event = nec.NetworkIncomingEvent(None, conn=self.conn,
- stanza=self.stanza)
- return super(ZeroconfMessageReceivedEvent, self).generate()
-
-class GcInvitationReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-invitation-received'
- base_network_events = []
-
- def generate(self):
- account = self.conn.name
- if not self.mediated:
- # direct invitation
- try:
- self.room_jid = helpers.parse_jid(self.stanza.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- self.stanza.getAttr('jid'))
- return
- self.reason = self.stanza.getAttr('reason')
- self.password = self.stanza.getAttr('password')
- self.is_continued = False
- self.is_continued = self.stanza.getAttr('continue') == 'true'
- else:
- self.invite = self.stanza.getTag('invite')
- self.room_jid = self.jid_from
- try:
- self.jid_from = helpers.parse_jid(self.invite.getAttr('from'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- self.invite.getAttr('from'))
- return
-
- self.reason = self.invite.getTagData('reason')
- self.password = self.stanza.getTagData('password')
- self.is_continued = self.stanza.getTag('continue') is not None
-
- if self.room_jid in gajim.gc_connected[account] and \
- gajim.gc_connected[account][self.room_jid]:
- # We are already in groupchat. Ignore invitation
- return
- jid = gajim.get_jid_without_resource(self.jid_from)
-
- ignore = gajim.config.get_per(
- 'accounts', account, 'ignore_unknown_contacts')
- if ignore and not gajim.contacts.get_contacts(account, jid):
- return
-
- return True
-
-class GcDeclineReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-decline-received'
- base_network_events = []
-
- def generate(self):
- account = self.conn.name
- decline = self.stanza.getTag('decline')
- try:
- self.jid_from = helpers.parse_jid(decline.getAttr('from'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- decline.getAttr('from'))
- return
- jid = gajim.get_jid_without_resource(self.jid_from)
- ignore = gajim.config.get_per(
- 'accounts', account, 'ignore_unknown_contacts')
- if ignore and not gajim.contacts.get_contacts(account, jid):
- return
- self.reason = decline.getTagData('reason')
-
- return True
-
-class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'decrypted-message-received'
- base_network_events = []
-
- def generate(self):
- self.stanza = self.msg_obj.stanza
- if not hasattr(self, 'additional_data'):
- self.additional_data = self.msg_obj.additional_data
- self.id_ = self.msg_obj.id_
- self.jid = self.msg_obj.jid
- self.fjid = self.msg_obj.fjid
- self.resource = self.msg_obj.resource
- self.mtype = self.msg_obj.mtype
- self.thread_id = self.msg_obj.thread_id
- self.msgtxt = self.msg_obj.msgtxt
- self.gc_control = self.msg_obj.gc_control
- self.session = self.msg_obj.session
- self.timestamp = self.msg_obj.timestamp
- self.encrypted = self.msg_obj.encrypted
- self.forwarded = self.msg_obj.forwarded
- self.sent = self.msg_obj.sent
- self.conn = self.msg_obj.conn
- self.popup = False
- self.msg_log_id = None # id in log database
- self.attention = False # XEP-0224
- self.correct_id = None # XEP-0308
- self.msghash = None
-
- self.receipt_request_tag = self.stanza.getTag('request',
- namespace=nbxmpp.NS_RECEIPTS)
- self.receipt_received_tag = self.stanza.getTag('received',
- namespace=nbxmpp.NS_RECEIPTS)
-
- self.subject = self.stanza.getSubject()
-
- self.displaymarking = None
- self.seclabel = self.stanza.getTag('securitylabel',
- namespace=nbxmpp.NS_SECLABEL)
- if self.seclabel:
- self.displaymarking = self.seclabel.getTag('displaymarking')
-
- if self.stanza.getTag('attention', namespace=nbxmpp.NS_ATTENTION):
- delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not\
- None
- if not delayed:
- self.attention = True
-
- self.form_node = self.stanza.getTag('x', namespace=nbxmpp.NS_DATA)
-
- if gajim.config.get('ignore_incoming_xhtml'):
- self.xhtml = None
- else:
- self.xhtml = self.stanza.getXHTML()
-
- # XEP-0172 User Nickname
- self.user_nick = self.stanza.getTagData('nick') or ''
-
- self.get_chatstate()
-
- oob_node = self.stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
- self.oob_url = None
- self.oob_desc = None
- if oob_node:
- self.oob_url = oob_node.getTagData('url')
- self.oob_desc = oob_node.getTagData('desc')
- if self.oob_url:
- self.msgtxt += '\n'
- if self.oob_desc:
- self.msgtxt += self.oob_desc
- else:
- self.msgtxt += _('URL:')
- self.msgtxt += ' ' + self.oob_url
-
- replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
- if replace:
- self.correct_id = replace.getAttr('id')
-
- # ignore message duplicates
- if self.msgtxt and self.id_ and self.jid:
- self.msghash = hashlib.sha256(("%s|%s|%s" % (
- hashlib.sha256(self.msgtxt.encode('utf-8')).hexdigest(),
- hashlib.sha256(self.id_.encode('utf-8')).hexdigest(),
- hashlib.sha256(self.jid.encode('utf-8')).hexdigest())).encode(
- 'utf-8')).digest()
- if self.msghash in self.conn.received_message_hashes:
- log.info("Ignoring duplicated message from '%s' with id '%s'" % (self.jid, self.id_))
- return False
- else:
- log.debug("subhashes: msgtxt, id_, jid = ('%s', '%s', '%s')" % (hashlib.sha256(self.msgtxt.encode('utf-8')).hexdigest(), hashlib.sha256(self.id_.encode('utf-8')).hexdigest(), hashlib.sha256(self.jid.encode('utf-8')).hexdigest()))
- self.conn.received_message_hashes.append(self.msghash)
- # only record the last 20000 hashes (should be about 1MB [32 bytes per hash]
- # and about 24 hours if you receive a message every 5 seconds)
- self.conn.received_message_hashes = self.conn.received_message_hashes[-20000:]
- return True
-
-class ChatstateReceivedEvent(nec.NetworkIncomingEvent):
- name = 'chatstate-received'
- base_network_events = []
-
- def generate(self):
- self.stanza = self.msg_obj.stanza
- self.jid = self.msg_obj.jid
- self.fjid = self.msg_obj.fjid
- self.resource = self.msg_obj.resource
- self.chatstate = self.msg_obj.chatstate
- return True
-
-class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-message-received'
- base_network_events = []
-
- def generate(self):
- self.stanza = self.msg_obj.stanza
- if not hasattr(self, 'additional_data'):
- self.additional_data = self.msg_obj.additional_data
- self.id_ = self.msg_obj.stanza.getID()
- self.fjid = self.msg_obj.fjid
- self.msgtxt = self.msg_obj.msgtxt
- self.jid = self.msg_obj.jid
- self.room_jid = self.msg_obj.jid
- self.nickname = self.msg_obj.resource
- self.timestamp = self.msg_obj.timestamp
- self.xhtml_msgtxt = self.stanza.getXHTML()
- self.encrypted = self.msg_obj.encrypted
- self.correct_id = None # XEP-0308
-
- if gajim.config.get('ignore_incoming_xhtml'):
- self.xhtml_msgtxt = None
-
- if self.msg_obj.resource:
- # message from someone
- self.nick = self.msg_obj.resource
- else:
- # message from server
- self.nick = ''
-
- self.has_timestamp = bool(self.stanza.timestamp)
-
- self.subject = self.stanza.getSubject()
-
- if self.subject is not None:
- gajim.nec.push_incoming_event(GcSubjectReceivedEvent(None,
- conn=self.conn, msg_event=self))
- return
-
- conditions = self.stanza.getStatusConditions()
- if conditions:
- self.status_code = []
- for condition in conditions:
- if condition in CONDITION_TO_CODE:
- self.status_code.append(CONDITION_TO_CODE[condition])
- else:
- self.status_code = self.stanza.getStatusCode()
-
- if not self.stanza.getTag('body'): # no <body>
- # It could be a config change. See
- # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
- if self.stanza.getTag('x'):
- if self.status_code != []:
- gajim.nec.push_incoming_event(GcConfigChangedReceivedEvent(
- None, conn=self.conn, msg_event=self))
- if self.msg_obj.form_node:
- return True
- return
-
- self.displaymarking = None
- seclabel = self.stanza.getTag('securitylabel')
- if seclabel and seclabel.getNamespace() == nbxmpp.NS_SECLABEL:
- # Ignore message from room in which we are not
- self.displaymarking = seclabel.getTag('displaymarking')
-
- if self.jid not in self.conn.last_history_time:
- return
-
- self.captcha_form = None
- captcha_tag = self.stanza.getTag('captcha', namespace=nbxmpp.NS_CAPTCHA)
- if captcha_tag:
- self.captcha_form = captcha_tag.getTag('x',
- namespace=nbxmpp.NS_DATA)
- for field in self.captcha_form.getTags('field'):
- for media in field.getTags('media'):
- for uri in media.getTags('uri'):
- uri_data = uri.getData()
- if uri_data.startswith('cid:'):
- uri_data = uri_data[4:]
- found = False
- for data in self.stanza.getTags('data',
- namespace=nbxmpp.NS_BOB):
- if data.getAttr('cid') == uri_data:
- uri.setData(data.getData())
- found = True
- if not found:
- self.conn.get_bob_data(uri_data, self.fjid,
- self.conn._dispatch_gc_msg_with_captcha,
- [self.stanza, self.msg_obj], 0)
- return
-
- replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
- if replace:
- self.correct_id = replace.getAttr('id')
-
- return True
-
-class GcSubjectReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-subject-received'
- base_network_events = []
-
- def generate(self):
- self.conn = self.msg_event.conn
- self.stanza = self.msg_event.stanza
- self.room_jid = self.msg_event.room_jid
- self.nickname = self.msg_event.nickname
- self.fjid = self.msg_event.fjid
- self.subject = self.msg_event.subject
- self.msgtxt = self.msg_event.msgtxt
- self.has_timestamp = self.msg_event.has_timestamp
- return True
-
-class GcConfigChangedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-config-changed-received'
- base_network_events = []
-
- def generate(self):
- self.conn = self.msg_event.conn
- self.stanza = self.msg_event.stanza
- self.room_jid = self.msg_event.room_jid
- self.status_code = self.msg_event.status_code
- return True
-
-class MessageSentEvent(nec.NetworkIncomingEvent):
- name = 'message-sent'
- base_network_events = []
-
- def generate(self):
- if not self.automatic_message:
- self.conn.sent_message_ids.append(self.msg_id)
- # only record the last 20000 message ids (should be about 1MB [36 byte per uuid]
- # and about 24 hours if you send out a message every 5 seconds)
- self.conn.sent_message_ids = self.conn.sent_message_ids[-20000:]
- return True
-
-class MessageNotSentEvent(nec.NetworkIncomingEvent):
- name = 'message-not-sent'
- base_network_events = []
-
-class MessageErrorEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'message-error'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- #only alert for errors of explicitly sent messages (see https://trac.gajim.org/ticket/8222)
- if self.id_ in self.conn.sent_message_ids:
- self.conn.sent_message_ids.remove(self.id_)
- return True
- return False
-
-class AnonymousAuthEvent(nec.NetworkIncomingEvent):
- name = 'anonymous-auth'
- base_network_events = []
-
-class JingleRequestReceivedEvent(nec.NetworkIncomingEvent):
- name = 'jingle-request-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class JingleConnectedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'jingle-connected-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class JingleDisconnectedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'jingle-disconnected-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class JingleTransferCancelledEvent(nec.NetworkIncomingEvent):
- name = 'jingleFT-cancelled-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class JingleErrorReceivedEvent(nec.NetworkIncomingEvent):
- name = 'jingle-error-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class ArchivingReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-received'
- base_network_events = []
-
- def generate(self):
- self.type_ = self.stanza.getType()
- if self.type_ not in ('result', 'set', 'error'):
- return
- return True
-
-class ArchivingErrorReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-error-received'
- base_network_events = ['archiving-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.type_ = self.base_event.type_
-
- if self.type_ == 'error':
- self.error_msg = self.stanza.getErrorMsg()
- return True
-
-class ArchivingPreferencesChangedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-preferences-changed-received'
- base_network_events = ['archiving-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.type_ = self.base_event.type_
-
- if self.type_ not in ('result', 'set'):
- return
-
- self.conf = {}
- self.new_items = {}
- self.removed_items = []
- pref = self.stanza.getTag('pref', namespace=nbxmpp.NS_ARCHIVE)
- if pref:
- if pref.getTag('auto'):
- self.conf['auto'] = pref.getTagAttr('auto', 'save')
-
- method_auto = pref.getTag('method', attrs={'type': 'auto'})
- if method_auto:
- self.conf['method_auto'] = method_auto.getAttr('use')
-
- method_local = pref.getTag('method', attrs={'type': 'local'})
- if method_local:
- self.conf['method_local'] = method_local.getAttr('use')
-
- method_manual = pref.getTag('method', attrs={'type': 'manual'})
- if method_manual:
- self.conf['method_manual'] = method_manual.getAttr('use')
-
- default = pref.getTag('default')
- if default:
- self.conf['default'] = {
- 'expire': default.getAttr('expire'),
- 'otr': default.getAttr('otr'),
- 'save': default.getAttr('save'),
- 'unset': default.getAttr('unset')}
-
- for item in pref.getTags('item'):
- self.new_items[item.getAttr('jid')] = {
- 'expire': item.getAttr('expire'),
- 'otr': item.getAttr('otr'),
- 'save': item.getAttr('save')}
-
- elif self.stanza.getTag('itemremove'):
- for item in pref.getTags('item'):
- self.removed_items.append(item.getAttr('jid'))
- else:
- return
- return True
-
-class Archiving313PreferencesChangedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-313-preferences-changed-received'
- base_network_events = ['archiving-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.type_ = self.base_event.type_
- self.items = []
- self.default = None
- self.id = self.stanza.getID()
- self.answer = None
- prefs = self.stanza.getTag('prefs')
-
- if self.type_ != 'result' or not prefs:
- return
-
- self.default = prefs.getAttr('default')
-
- for item in prefs.getTag('always').getTags('jid'):
- self.items.append((item.getData(), 'Always'))
-
- for item in prefs.getTag('never').getTags('jid'):
- self.items.append((item.getData(), 'Never'))
-
- return True
-
-class ArchivingFinishedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-finished'
- base_network_events = ['archiving-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.type_ = self.base_event.type_
- self.fin = self.stanza.getTag('fin')
-
- if self.type_ != 'result' or not self.fin:
- return
-
- self.queryid = self.fin.getAttr('queryid')
- if not self.queryid:
- return
-
- return True
-
-class ArchivingFinishedLegacyReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-finished-legacy'
- base_network_events = ['raw-message-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.fin = self.stanza.getTag('fin', namespace=nbxmpp.NS_MAM)
-
- if not self.fin:
- return
-
- self.queryid = self.fin.getAttr('queryid')
- if not self.queryid:
- return
-
- return True
-
-class AccountCreatedEvent(nec.NetworkIncomingEvent):
- name = 'account-created'
- base_network_events = []
-
-class AccountNotCreatedEvent(nec.NetworkIncomingEvent):
- name = 'account-not-created'
- base_network_events = []
-
-class NewAccountConnectedEvent(nec.NetworkIncomingEvent):
- name = 'new-account-connected'
- base_network_events = []
-
- def generate(self):
- try:
- self.errnum = self.conn.connection.Connection.ssl_errnum
- except AttributeError:
- self.errnum = 0 # we don't have an errnum
- self.ssl_msg = ''
- if self.errnum > 0:
- from common.connection import ssl_error
- self.ssl_msg = ssl_error.get(self.errnum,
- _('Unknown SSL error: %d') % self.errnum)
- self.ssl_cert = ''
- self.ssl_fingerprint_sha1 = ''
- self.ssl_fingerprint_sha256 = ''
- if self.conn.connection.Connection.ssl_certificate:
- cert = self.conn.connection.Connection.ssl_certificate
- self.ssl_cert = OpenSSL.crypto.dump_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert).decode('utf-8')
- self.ssl_fingerprint_sha1 = cert.digest('sha1').decode('utf-8')
- self.ssl_fingerprint_sha256 = cert.digest('sha256').decode('utf-8')
- return True
-
-class NewAccountNotConnectedEvent(nec.NetworkIncomingEvent):
- name = 'new-account-not-connected'
- base_network_events = []
-
-class ConnectionTypeEvent(nec.NetworkIncomingEvent):
- name = 'connection-type'
- base_network_events = []
-
-class VcardPublishedEvent(nec.NetworkIncomingEvent):
- name = 'vcard-published'
- base_network_events = []
-
-class VcardNotPublishedEvent(nec.NetworkIncomingEvent):
- name = 'vcard-not-published'
- base_network_events = []
-
-class StanzaReceivedEvent(nec.NetworkIncomingEvent):
- name = 'stanza-received'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
-
- def generate(self):
- return True
-
-class StanzaSentEvent(nec.NetworkIncomingEvent):
- name = 'stanza-sent'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
-
-class AgentRemovedEvent(nec.NetworkIncomingEvent):
- name = 'agent-removed'
- base_network_events = []
-
- def generate(self):
- self.jid_list = []
- for jid in gajim.contacts.get_jid_list(self.conn.name):
- if jid.endswith('@' + self.agent):
- self.jid_list.append(jid)
- return True
-
-class BadGPGPassphraseEvent(nec.NetworkIncomingEvent):
- name = 'bad-gpg-passphrase'
- base_network_events = []
-
- def generate(self):
- self.account = self.conn.name
- self.use_gpg_agent = gajim.config.get('use_gpg_agent')
- self.keyID = gajim.config.get_per('accounts', self.conn.name, 'keyid')
- return True
-
-class ConnectionLostEvent(nec.NetworkIncomingEvent):
- name = 'connection-lost'
- base_network_events = []
-
- def generate(self):
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self.conn,
- show='offline'))
- return True
-
-class PingSentEvent(nec.NetworkIncomingEvent):
- name = 'ping-sent'
- base_network_events = []
-
-class PingReplyEvent(nec.NetworkIncomingEvent):
- name = 'ping-reply'
- base_network_events = []
-
-class PingErrorEvent(nec.NetworkIncomingEvent):
- name = 'ping-error'
- base_network_events = []
-
-class CapsPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent,
-PresenceHelperEvent):
- name = 'caps-presence-received'
- base_network_events = ['raw-pres-received']
-
- def _extract_caps_from_presence(self):
- caps_tag = self.stanza.getTag('c', namespace=nbxmpp.NS_CAPS)
- if caps_tag:
- self.hash_method = caps_tag['hash']
- self.node = caps_tag['node']
- self.caps_hash = caps_tag['ver']
- else:
- self.hash_method = self.node = self.caps_hash = None
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- try:
- self.get_jid_resource()
- except Exception:
- return
- self._generate_ptype()
- self._generate_show()
- self._extract_caps_from_presence()
- return True
-
-class CapsDiscoReceivedEvent(nec.NetworkIncomingEvent):
- name = 'caps-disco-received'
- base_network_events = []
-
-class CapsReceivedEvent(nec.NetworkIncomingEvent):
- name = 'caps-received'
- base_network_events = ['caps-presence-received', 'caps-disco-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.fjid = self.base_event.fjid
- self.jid = self.base_event.jid
- self.resource = self.base_event.resource
- self.client_caps = self.base_event.client_caps
- return True
-
-class GPGTrustKeyEvent(nec.NetworkIncomingEvent):
- name = 'gpg-trust-key'
- base_network_events = []
-
-class GPGPasswordRequiredEvent(nec.NetworkIncomingEvent):
- name = 'gpg-password-required'
- base_network_events = []
-
- def generate(self):
- self.keyid = gajim.config.get_per('accounts', self.conn.name, 'keyid')
- return True
-
-class PEPReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'pep-received'
- base_network_events = []
-
- def generate(self):
- if not self.stanza.getTag('event'):
- return
- if self.stanza.getTag('error'):
- log.debug('PEPReceivedEvent received error stanza. Ignoring')
- return
-
- try:
- self.get_jid_resource()
- except Exception:
- return
-
- self.event_tag = self.stanza.getTag('event')
-
- for pep_class in SUPPORTED_PERSONAL_USER_EVENTS:
- pep = pep_class.get_tag_as_PEP(self.fjid, self.conn.name,
- self.event_tag)
- if pep:
- self.pep_type = pep.type_
- return True
-
- items = self.event_tag.getTag('items')
- if items:
- # for each entry in feed (there shouldn't be more than one, but to
- # be sure...
- for item in items.getTags('item'):
- entry = item.getTag('entry', namespace=nbxmpp.NS_ATOM)
- if entry:
- gajim.nec.push_incoming_event(AtomEntryReceived(None,
- conn=self.conn, node=entry))
- raise nbxmpp.NodeProcessed
-
-class AtomEntryReceived(nec.NetworkIncomingEvent):
- name = 'atom-entry-received'
- base_network_events = []
-
- def generate(self):
- self.atom_entry = atom.OldEntry(node=self.node)
- return True
-
-class PlainConnectionEvent(nec.NetworkIncomingEvent):
- name = 'plain-connection'
- base_network_events = []
-
-class InsecurePasswordEvent(nec.NetworkIncomingEvent):
- name = 'insecure-password'
- base_network_events = []
-
-class InsecureSSLConnectionEvent(nec.NetworkIncomingEvent):
- name = 'insecure-ssl-connection'
- base_network_events = []
-
-class SSLErrorEvent(nec.NetworkIncomingEvent):
- name = 'ssl-error'
- base_network_events = []
-
-class FingerprintErrorEvent(nec.NetworkIncomingEvent):
- name = 'fingerprint-error'
- base_network_events = []
-
-class UniqueRoomIdSupportedEvent(nec.NetworkIncomingEvent):
- name = 'unique-room-id-supported'
- base_network_events = []
-
-class UniqueRoomIdNotSupportedEvent(nec.NetworkIncomingEvent):
- name = 'unique-room-id-not-supported'
- base_network_events = []
-
-class PrivacyListsReceivedEvent(nec.NetworkIncomingEvent):
- name = 'privacy-lists-received'
- base_network_events = []
-
-class PrivacyListReceivedEvent(nec.NetworkIncomingEvent):
- name = 'privacy-list-received'
- base_network_events = []
-
-class PrivacyListRemovedEvent(nec.NetworkIncomingEvent):
- name = 'privacy-list-removed'
- base_network_events = []
-
-class PrivacyListActiveDefaultEvent(nec.NetworkIncomingEvent):
- name = 'privacy-list-active-default'
- base_network_events = []
-
-class NonAnonymousServerErrorEvent(nec.NetworkIncomingEvent):
- name = 'non-anonymous-server-error'
- base_network_events = []
-
-class VcardReceivedEvent(nec.NetworkIncomingEvent):
- name = 'vcard-received'
- base_network_events = []
-
- def generate(self):
- self.nickname = None
- if 'NICKNAME' in self.vcard_dict:
- self.nickname = self.vcard_dict['NICKNAME']
- elif 'FN' in self.vcard_dict:
- self.nickname = self.vcard_dict['FN']
- self.jid = self.vcard_dict['jid']
- self.resource = self.vcard_dict['resource']
- self.fjid = self.jid
- if self.resource:
- self.fjid += '/' + self.resource
- return True
-
-class PEPConfigReceivedEvent(nec.NetworkIncomingEvent):
- name = 'pep-config-received'
- base_network_events = []
-
-class MetacontactsReceivedEvent(nec.NetworkIncomingEvent):
- name = 'metacontacts-received'
- base_network_events = []
-
- def generate(self):
- # Metacontact tags
- # http://www.xmpp.org/extensions/xep-0209.html
- self.meta_list = {}
- query = self.stanza.getTag('query')
- storage = query.getTag('storage')
- metas = storage.getTags('meta')
- for meta in metas:
- try:
- jid = helpers.parse_jid(meta.getAttr('jid'))
- except helpers.InvalidFormat:
- continue
- tag = meta.getAttr('tag')
- data = {'jid': jid}
- order = meta.getAttr('order')
- try:
- order = int(order)
- except Exception:
- order = 0
- if order is not None:
- data['order'] = order
- if tag in self.meta_list:
- self.meta_list[tag].append(data)
- else:
- self.meta_list[tag] = [data]
- return True
-
-class ZeroconfNameConflictEvent(nec.NetworkIncomingEvent):
- name = 'zeroconf-name-conflict'
- base_network_events = []
-
-class PasswordRequiredEvent(nec.NetworkIncomingEvent):
- name = 'password-required'
- base_network_events = []
-
-class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
- name = 'oauth2-credentials-required'
- base_network_events = []
-
-class FailedDecryptEvent(nec.NetworkIncomingEvent):
- name = 'failed-decrypt'
- base_network_events = []
-
- def generate(self):
- self.conn = self.msg_obj.conn
- self.fjid = self.msg_obj.fjid
- self.timestamp = self.msg_obj.timestamp
- self.session = self.msg_obj.session
- return True
-
-class SignedInEvent(nec.NetworkIncomingEvent):
- name = 'signed-in'
- base_network_events = []
-
-class RegisterAgentInfoReceivedEvent(nec.NetworkIncomingEvent):
- name = 'register-agent-info-received'
- base_network_events = []
-
-class AgentItemsReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'agent-items-received'
- base_network_events = []
-
- def generate(self):
- q = self.stanza.getTag('query')
- self.node = q.getAttr('node')
- if not self.node:
- self.node = ''
- qp = self.stanza.getQueryPayload()
- self.items = []
- if not qp:
- qp = []
- for i in qp:
- # CDATA payload is not processed, only nodes
- if not isinstance(i, nbxmpp.simplexml.Node):
- continue
- attr = {}
- for key in i.getAttrs():
- attr[key] = i.getAttrs()[key]
- if 'jid' not in attr:
- continue
- try:
- attr['jid'] = helpers.parse_jid(attr['jid'])
- except helpers.InvalidFormat:
- # jid is not conform
- continue
- self.items.append(attr)
- self.get_jid_resource()
- hostname = gajim.config.get_per('accounts', self.conn.name, 'hostname')
- self.get_id()
- if self.id_ in self.conn.disco_items_ids:
- self.conn.disco_items_ids.remove(self.id_)
- if self.fjid == hostname and self.id_[:6] == 'Gajim_':
- for item in self.items:
- self.conn.discoverInfo(item['jid'], id_prefix='Gajim_')
- else:
- return True
-
-class AgentItemsErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'agent-items-error-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- self.get_id()
- if self.id_ in self.conn.disco_items_ids:
- self.conn.disco_items_ids.remove(self.id_)
- return True
-
-class AgentInfoReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'agent-info-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- if self.id_ in self.conn.disco_info_ids:
- self.conn.disco_info_ids.remove(self.id_)
- if self.id_ is None:
- log.warning('Invalid IQ received without an ID. Ignoring it: %s' % \
- self.stanza)
- return
- # According to XEP-0030:
- # For identity: category, type is mandatory, name is optional.
- # For feature: var is mandatory
- self.identities, self.features, self.data = [], [], []
- q = self.stanza.getTag('query')
- self.node = q.getAttr('node')
- if not self.node:
- self.node = ''
- qc = self.stanza.getQueryChildren()
- if not qc:
- qc = []
-
- for i in qc:
- if i.getName() == 'identity':
- attr = {}
- for key in i.getAttrs().keys():
- attr[key] = i.getAttr(key)
- self.identities.append(attr)
- elif i.getName() == 'feature':
- var = i.getAttr('var')
- if var:
- self.features.append(var)
- elif i.getName() == 'x' and i.getNamespace() == nbxmpp.NS_DATA:
- self.data.append(nbxmpp.DataForm(node=i))
-
- if not self.identities:
- # ejabberd doesn't send identities when we browse online users
- # see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225
- self.identities = [{'category': 'server', 'type': 'im',
- 'name': self.node}]
- self.get_jid_resource()
- return True
-
-class AgentInfoErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'agent-info-error-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- self.get_id()
- if self.id_ in self.conn.disco_info_ids:
- self.conn.disco_info_ids.remove(self.id_)
- return True
-
-class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'file-request-received'
- base_network_events = []
-
- def init(self):
- self.jingle_content = None
- self.FT_content = None
-
- def generate(self):
- self.get_id()
- self.fjid = self.conn._ft_get_from(self.stanza)
- self.jid = gajim.get_jid_without_resource(self.fjid)
- if self.jingle_content:
- secu = self.jingle_content.getTag('security')
- self.FT_content.use_security = bool(secu)
- if secu:
- fingerprint = secu.getTag('fingerprint')
- if fingerprint:
- self.FT_content.x509_fingerprint = fingerprint.getData()
- if not self.FT_content.transport:
- self.FT_content.transport = JingleTransportSocks5()
- self.FT_content.transport.set_our_jid(
- self.FT_content.session.ourjid)
- self.FT_content.transport.set_connection(
- self.FT_content.session.connection)
- sid = self.stanza.getTag('jingle').getAttr('sid')
- self.file_props = FilesProp.getNewFileProp(self.conn.name, sid)
- self.file_props.transport_sid = self.FT_content.transport.sid
- self.FT_content.file_props = self.file_props
- self.FT_content.transport.set_file_props(self.file_props)
- self.file_props.streamhosts.extend(
- self.FT_content.transport.remote_candidates)
- for host in self.file_props.streamhosts:
- host['initiator'] = self.FT_content.session.initiator
- host['target'] = self.FT_content.session.responder
- self.file_props.session_type = 'jingle'
- self.file_props.stream_methods = nbxmpp.NS_BYTESTREAM
- desc = self.jingle_content.getTag('description')
- if self.jingle_content.getAttr('creator') == 'initiator':
- file_tag = desc.getTag('file')
- self.file_props.sender = self.fjid
- self.file_props.receiver = self.conn._ft_get_our_jid()
- else:
- file_tag = desc.getTag('file')
- h = file_tag.getTag('hash')
- h = h.getData() if h else None
- n = file_tag.getTag('name')
- n = n.getData() if n else None
- pjid = gajim.get_jid_without_resource(self.fjid)
- file_info = self.conn.get_file_info(pjid, hash_=h,
- name=n,account=self.conn.name)
- self.file_props.file_name = file_info['file-name']
- self.file_props.sender = self.conn._ft_get_our_jid()
- self.file_props.receiver = self.fjid
- self.file_props.type_ = 's'
- for child in file_tag.getChildren():
- name = child.getName()
- val = child.getData()
- if val is None:
- continue
- if name == 'name':
- self.file_props.name = val
- if name == 'size':
- self.file_props.size = int(val)
- if name == 'hash':
- self.file_props.algo = child.getAttr('algo')
- self.file_props.hash_ = val
- if name == 'date':
- self.file_props.date = val
- else:
- si = self.stanza.getTag('si')
- self.file_props = FilesProp.getNewFileProp(self.conn.name,
- si.getAttr('id'))
- profile = si.getAttr('profile')
- if profile != nbxmpp.NS_FILE:
- self.conn.send_file_rejection(self.file_props, code='400',
- typ='profile')
- raise nbxmpp.NodeProcessed
- feature_tag = si.getTag('feature', namespace=nbxmpp.NS_FEATURE)
- if not feature_tag:
- return
- form_tag = feature_tag.getTag('x', namespace=nbxmpp.NS_DATA)
- if not form_tag:
- return
- self.dataform = dataforms.ExtendForm(node=form_tag)
- for f in self.dataform.iter_fields():
- if f.var == 'stream-method' and f.type_ == 'list-single':
- values = [o[1] for o in f.options]
- self.file_props.stream_methods = ' '.join(values)
- if nbxmpp.NS_BYTESTREAM in values or \
- nbxmpp.NS_IBB in values:
- break
- else:
- self.conn.send_file_rejection(self.file_props, code='400',
- typ='stream')
- raise nbxmpp.NodeProcessed
- file_tag = si.getTag('file')
- for name, val in file_tag.getAttrs().items():
- if val is None:
- continue
- if name == 'name':
- self.file_props.name = val
- if name == 'size':
- self.file_props.size = int(val)
- mime_type = si.getAttr('mime-type')
- if mime_type is not None:
- self.file_props.mime_type = mime_type
- self.file_props.sender = self.fjid
- self.file_props.receiver = self.conn._ft_get_our_jid()
- self.file_props.request_id = self.id_
- file_desc_tag = file_tag.getTag('desc')
- if file_desc_tag is not None:
- self.file_props.desc = file_desc_tag.getData()
- self.file_props.transfered_size = []
- return True
-
-class FileRequestErrorEvent(nec.NetworkIncomingEvent):
- name = 'file-request-error'
- base_network_events = []
-
- def generate(self):
- self.jid = gajim.get_jid_without_resource(self.jid)
- return True
-
-class FileTransferCompletedEvent(nec.NetworkIncomingEvent):
- name = 'file-transfer-completed'
- base_network_events = []
-
- def generate(self):
- jid = str(self.file_props.receiver)
- self.jid = gajim.get_jid_without_resource(jid)
- return True
-
-class GatewayPromptReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'gateway-prompt-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- query = self.stanza.getTag('query')
- if query:
- self.desc = query.getTagData('desc')
- self.prompt = query.getTagData('prompt')
- self.prompt_jid = query.getTagData('jid')
- else:
- self.desc = None
- self.prompt = None
- self.prompt_jid = None
- return True
-
-class NotificationEvent(nec.NetworkIncomingEvent):
- name = 'notification'
- base_network_events = ['decrypted-message-received', 'gc-message-received',
- 'presence-received']
-
- def detect_type(self):
- if self.base_event.name == 'decrypted-message-received':
- self.notif_type = 'msg'
- if self.base_event.name == 'gc-message-received':
- self.notif_type = 'gc-msg'
- if self.base_event.name == 'presence-received':
- self.notif_type = 'pres'
-
- def get_focused(self):
- self.control_focused = False
- if self.control:
- parent_win = self.control.parent_win
- if parent_win and self.control == parent_win.get_active_control() \
- and parent_win.window.get_property('has-toplevel-focus'):
- self.control_focused = True
-
- def handle_incoming_msg_event(self, msg_obj):
- # don't alert for carbon copied messages from ourselves
- if msg_obj.sent:
- return
- if not msg_obj.msgtxt:
- return
- self.jid = msg_obj.jid
- if msg_obj.session:
- self.control = msg_obj.session.control
- else:
- self.control = None
- self.get_focused()
- # This event has already been added to event list
- if not self.control and len(gajim.events.get_events(self.conn.name, \
- self.jid, [msg_obj.mtype])) <= 1:
- self.first_unread = True
-
- if msg_obj.mtype == 'pm':
- nick = msg_obj.resource
- else:
- nick = gajim.get_name_from_jid(self.conn.name, self.jid)
-
- if self.first_unread:
- self.sound_event = 'first_message_received'
- elif self.control_focused:
- self.sound_event = 'next_message_received_focused'
- else:
- self.sound_event = 'next_message_received_unfocused'
-
- if gajim.config.get('notification_preview_message'):
- self.popup_text = msg_obj.msgtxt
- if self.popup_text and (self.popup_text.startswith('/me ') or \
- self.popup_text.startswith('/me\n')):
- self.popup_text = '* ' + nick + self.popup_text[3:]
- else:
- # We don't want message preview, do_preview = False
- self.popup_text = ''
- if msg_obj.mtype == 'normal': # single message
- self.popup_msg_type = 'normal'
- self.popup_event_type = _('New Single Message')
- self.popup_image = 'gajim-single_msg_recv'
- self.popup_title = _('New Single Message from %(nickname)s') % \
- {'nickname': nick}
- elif msg_obj.mtype == 'pm':
- self.popup_msg_type = 'pm'
- self.popup_event_type = _('New Private Message')
- self.popup_image = 'gajim-priv_msg_recv'
- self.popup_title = _('New Private Message from group chat %s') % \
- msg_obj.jid
- if self.popup_text:
- self.popup_text = _('%(nickname)s: %(message)s') % \
- {'nickname': nick, 'message': self.popup_text}
- else:
- self.popup_text = _('Messaged by %(nickname)s') % \
- {'nickname': nick}
- else: # chat message
- self.popup_msg_type = 'chat'
- self.popup_event_type = _('New Message')
- self.popup_image = 'gajim-chat_msg_recv'
- self.popup_title = _('New Message from %(nickname)s') % \
- {'nickname': nick}
-
-
- if gajim.config.get('notify_on_new_message'):
- if self.first_unread or (gajim.config.get('autopopup_chat_opened') \
- and not self.control_focused):
- if gajim.config.get('autopopupaway'):
- # always show notification
- self.do_popup = True
- if gajim.connections[self.conn.name].connected in (2, 3):
- # we're online or chat
- self.do_popup = True
-
- if msg_obj.attention and not gajim.config.get(
- 'ignore_incoming_attention'):
- self.popup_timeout = 0
- self.do_popup = True
- else:
- self.popup_timeout = gajim.config.get('notification_timeout')
-
- if msg_obj.attention and not gajim.config.get(
- 'ignore_incoming_attention') and gajim.config.get_per('soundevents',
- 'attention_received', 'enabled'):
- self.sound_event = 'attention_received'
- self.do_sound = True
- elif self.first_unread and helpers.allow_sound_notification(
- self.conn.name, 'first_message_received'):
- self.do_sound = True
- elif not self.first_unread and self.control_focused and \
- helpers.allow_sound_notification(self.conn.name,
- 'next_message_received_focused'):
- self.do_sound = True
- elif not self.first_unread and not self.control_focused and \
- helpers.allow_sound_notification(self.conn.name,
- 'next_message_received_unfocused'):
- self.do_sound = True
-
- def handle_incoming_gc_msg_event(self, msg_obj):
- if not msg_obj.msg_obj.gc_control:
- # we got a message from a room we're not in? ignore it
- return
- self.jid = msg_obj.jid
- sound = msg_obj.msg_obj.gc_control.highlighting_for_message(
- msg_obj.msgtxt, msg_obj.timestamp)[1]
-
- if msg_obj.nickname != msg_obj.msg_obj.gc_control.nick:
- self.do_sound = True
- if sound == 'received':
- self.sound_event = 'muc_message_received'
- elif sound == 'highlight':
- self.sound_event = 'muc_message_highlight'
- else:
- self.do_sound = False
- else:
- self.do_sound = False
-
- self.do_popup = False
-
- def get_path_to_generic_or_avatar(self, generic, jid=None, suffix=None):
- """
- Choose between avatar image and default image
-
- Returns full path to the avatar image if it exists, otherwise returns full
- path to the image. generic must be with extension and suffix without
- """
- if jid:
- # we want an avatar
- puny_jid = helpers.sanitize_filename(jid)
- path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix
- path_to_local_file = path_to_file + '_local'
- for extension in ('.png', '.jpeg'):
- path_to_local_file_full = path_to_local_file + extension
- if os.path.exists(path_to_local_file_full):
- return path_to_local_file_full
- for extension in ('.png', '.jpeg'):
- path_to_file_full = path_to_file + extension
- if os.path.exists(path_to_file_full):
- return path_to_file_full
- return os.path.abspath(generic)
-
- def handle_incoming_pres_event(self, pres_obj):
- if gajim.jid_is_transport(pres_obj.jid):
- return True
- account = pres_obj.conn.name
- self.jid = pres_obj.jid
- resource = pres_obj.resource or ''
- # It isn't an agent
- for c in pres_obj.contact_list:
- if c.resource == resource:
- # we look for other connected resources
- continue
- if c.show not in ('offline', 'error'):
- return True
-
-
- # no other resource is connected, let's look in metacontacts
- family = gajim.contacts.get_metacontacts_family(account, self.jid)
- for info in family:
- acct_ = info['account']
- jid_ = info['jid']
- c_ = gajim.contacts.get_contact_with_highest_priority(acct_, jid_)
- if not c_:
- continue
- if c_.jid == self.jid:
- continue
- if c_.show not in ('offline', 'error'):
- return True
-
- if pres_obj.old_show < 2 and pres_obj.new_show > 1:
- event = 'contact_connected'
- show_image = 'online.png'
- suffix = '_notif_size_colored'
- server = gajim.get_server_from_jid(self.jid)
- account_server = account + '/' + server
- block_transport = False
- if account_server in gajim.block_signed_in_notifications and \
- gajim.block_signed_in_notifications[account_server]:
- block_transport = True
- if helpers.allow_showing_notification(account, 'notify_on_signin') \
- and not gajim.block_signed_in_notifications[account] and \
- not block_transport:
- self.do_popup = True
- if gajim.config.get_per('soundevents', 'contact_connected',
- 'enabled') and not gajim.block_signed_in_notifications[account] and\
- not block_transport and helpers.allow_sound_notification(account,
- 'contact_connected'):
- self.sound_event = event
- self.do_sound = True
-
- elif pres_obj.old_show > 1 and pres_obj.new_show < 2:
- event = 'contact_disconnected'
- show_image = 'offline.png'
- suffix = '_notif_size_bw'
- if helpers.allow_showing_notification(account, 'notify_on_signout'):
- self.do_popup = True
- if gajim.config.get_per('soundevents', 'contact_disconnected',
- 'enabled') and helpers.allow_sound_notification(account, event):
- self.sound_event = event
- self.do_sound = True
- # Status change (not connected/disconnected or error (<1))
- elif pres_obj.new_show > 1:
- event = 'status_change'
- # FIXME: we don't always 'online.png', but we first need 48x48 for
- # all status
- show_image = 'online.png'
- suffix = '_notif_size_colored'
- else:
- return True
-
- transport_name = gajim.get_transport_name_from_jid(self.jid)
- img_path = None
- if transport_name:
- img_path = os.path.join(helpers.get_transport_path(
- transport_name), '48x48', show_image)
- if not img_path or not os.path.isfile(img_path):
- iconset = gajim.config.get('iconset')
- img_path = os.path.join(helpers.get_iconset_path(iconset),
- '48x48', show_image)
- self.popup_image_path = self.get_path_to_generic_or_avatar(img_path,
- jid=self.jid, suffix=suffix)
-
- self.popup_timeout = gajim.config.get('notification_timeout')
-
- nick = i18n.direction_mark + gajim.get_name_from_jid(account, self.jid)
- if event == 'status_change':
- self.popup_title = _('%(nick)s Changed Status') % \
- {'nick': nick}
- self.popup_text = _('%(nick)s is now %(status)s') % \
- {'nick': nick, 'status': helpers.get_uf_show(pres_obj.show)}
- if pres_obj.status:
- self.popup_text = self.popup_text + " : " + pres_obj.status
- self.popup_event_type = _('Contact Changed Status')
- elif event == 'contact_connected':
- self.popup_title = _('%(nickname)s Signed In') % {'nickname': nick}
- self.popup_text = ''
- if pres_obj.status:
- self.popup_text = pres_obj.status
- self.popup_event_type = _('Contact Signed In')
- elif event == 'contact_disconnected':
- self.popup_title = _('%(nickname)s Signed Out') % {'nickname': nick}
- self.popup_text = ''
- if pres_obj.status:
- self.popup_text = pres_obj.status
- self.popup_event_type = _('Contact Signed Out')
-
- def generate(self):
- # what's needed to compute output
- self.conn = self.base_event.conn
- self.jid = ''
- self.control = None
- self.control_focused = False
- self.first_unread = False
-
- # For output
- self.do_sound = False
- self.sound_file = ''
- self.sound_event = '' # gajim sound played if not sound_file is set
- self.show_popup = False
-
- self.do_popup = False
- self.popup_title = ''
- self.popup_text = ''
- self.popup_event_type = ''
- self.popup_msg_type = ''
- self.popup_image = ''
- self.popup_image_path = ''
- self.popup_timeout = -1
-
- self.do_command = False
- self.command = ''
-
- self.show_in_notification_area = False
- self.show_in_roster = False
-
- self.detect_type()
-
- if self.notif_type == 'msg':
- self.handle_incoming_msg_event(self.base_event)
- elif self.notif_type == 'gc-msg':
- self.handle_incoming_gc_msg_event(self.base_event)
- elif self.notif_type == 'pres':
- self.handle_incoming_pres_event(self.base_event)
- return True
-
-class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
- name = 'message-outgoing'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
- self.message = ''
- self.keyID = None
- self.type_ = 'chat'
- self.subject = ''
- self.chatstate = None
- self.msg_id = None
- self.resource = None
- self.user_nick = None
- self.xhtml = None
- self.label = None
- self.session = None
- self.forward_from = None
- self.form_node = None
- self.original_message = None
- self.delayed = None
- self.callback = None
- self.callback_args = []
- self.now = False
- self.is_loggable = True
- self.control = None
- self.attention = False
- self.correct_id = None
- self.automatic_message = True
- self.encryption = ''
-
- def get_full_jid(self):
- if self.resource:
- return self.jid + '/' + self.resource
- if self.session:
- return self.session.get_to()
- return self.jid
-
- def generate(self):
- return True
-
-class StanzaMessageOutgoingEvent(nec.NetworkOutgoingEvent):
- name='stanza-message-outgoing'
- base_network_events = []
-
- def generate(self):
- return True
-
-class GcStanzaMessageOutgoingEvent(nec.NetworkOutgoingEvent):
- name='gc-stanza-message-outgoing'
- base_network_events = []
-
- def generate(self):
- return True
-
-class GcMessageOutgoingEvent(nec.NetworkOutgoingEvent):
- name = 'gc-message-outgoing'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
- self.message = ''
- self.chatstate = None
- self.xhtml = None
- self.label = None
- self.callback = None
- self.callback_args = []
- self.is_loggable = True
- self.control = None
- self.correct_id = None
- self.automatic_message = True
-
- def generate(self):
- return True
-
-
-class ClientCertPassphraseEvent(nec.NetworkIncomingEvent):
- name = 'client-cert-passphrase'
- base_network_events = []
-
-class InformationEvent(nec.NetworkIncomingEvent):
- name = 'information'
- base_network_events = []
-
- def init(self):
- self.popup = True
-
-class BlockingEvent(nec.NetworkIncomingEvent):
- name = 'blocking'
- base_network_events = []
-
- def init(self):
- self.blocked_jids = []
- self.unblocked_jids = []
- self.unblock_all = False
-
- def generate(self):
- block_tag = self.stanza.getTag('block', namespace=nbxmpp.NS_BLOCKING)
- if block_tag:
- for item in block_tag.getTags('item'):
- self.blocked_jids.append(item.getAttr('jid'))
- unblock_tag = self.stanza.getTag('unblock',
- namespace=nbxmpp.NS_BLOCKING)
- if unblock_tag:
- if not unblock_tag.getTags('item'): # unblock all
- self.unblock_all = True
- for item in unblock_tag.getTags('item'):
- self.unblocked_jids.append(item.getAttr('jid'))
- return True
diff --git a/src/common/contacts.py b/src/common/contacts.py
deleted file mode 100644
index 483131e12..000000000
--- a/src/common/contacts.py
+++ /dev/null
@@ -1,868 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/contacts.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Tomasz Melcer <liori AT exroot.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-from functools import cmp_to_key
-
-try:
- from common import caps_cache
- from common.account import Account
- import common.gajim
-except ImportError as e:
- if __name__ != "__main__":
- raise ImportError(str(e))
-
-class XMPPEntity(object):
- """
- Base representation of entities in XMPP
- """
-
- def __init__(self, jid, account, resource):
- self.jid = jid
- self.resource = resource
- self.account = account
-
-class CommonContact(XMPPEntity):
-
- def __init__(self, jid, account, resource, show, status, name,
- our_chatstate, chatstate, client_caps=None):
-
- XMPPEntity.__init__(self, jid, account, resource)
-
- self.show = show
- self.status = status
- self.name = name
-
- self.client_caps = client_caps or caps_cache.NullClientCaps()
-
- # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
- # this holds what WE SEND to contact (our current chatstate)
- self.our_chatstate = our_chatstate
- # this is contact's chatstate
- self.chatstate = chatstate
-
- def get_full_jid(self):
- raise NotImplementedError
-
- def get_shown_name(self):
- raise NotImplementedError
-
- def supports(self, requested_feature):
- """
- Return True if the contact has advertised to support the feature
- identified by the given namespace. False otherwise.
- """
- if self.show == 'offline':
- # Unfortunately, if all resources are offline, the contact
- # includes the last resource that was online. Check for its
- # show, so we can be sure it's existant. Otherwise, we still
- # return caps for a contact that has no resources left.
- return False
- else:
- return caps_cache.client_supports(self.client_caps, requested_feature)
-
-
-class Contact(CommonContact):
- """
- Information concerning a contact
- """
- def __init__(self, jid, account, name='', groups=None, show='', status='',
- sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
- our_chatstate=None, chatstate=None, last_status_time=None, msg_log_id=None,
- last_activity_time=None):
- if not isinstance(jid, str):
- print('no str')
- if groups is None:
- groups = []
-
- CommonContact.__init__(self, jid, account, resource, show, status, name,
- our_chatstate, chatstate, client_caps=client_caps)
-
- self.contact_name = '' # nick choosen by contact
- self.groups = [i if i else _('General') for i in set(groups)] # filter duplicate values
-
- self.sub = sub
- self.ask = ask
-
- self.priority = priority
- self.keyID = keyID
- self.msg_log_id = msg_log_id
- self.last_status_time = last_status_time
- self.last_activity_time = last_activity_time
-
- self.pep = {}
-
- def get_full_jid(self):
- if self.resource:
- return self.jid + '/' + self.resource
- return self.jid
-
- def get_shown_name(self):
- if self.name:
- return self.name
- if self.contact_name:
- return self.contact_name
- return self.jid.split('@')[0]
-
- def get_shown_groups(self):
- if self.is_observer():
- return [_('Observers')]
- elif self.is_groupchat():
- return [_('Groupchats')]
- elif self.is_transport():
- return [_('Transports')]
- elif not self.groups:
- return [_('General')]
- else:
- return self.groups
-
- def is_hidden_from_roster(self):
- """
- If contact should not be visible in roster
- """
- # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
- if self.is_transport():
- return False
- if self.sub in ('both', 'to'):
- return False
- if self.sub in ('none', 'from') and self.ask == 'subscribe':
- return False
- if self.sub in ('none', 'from') and (self.name or len(self.groups)):
- return False
- if _('Not in Roster') in self.groups:
- return False
- return True
-
- def is_observer(self):
- # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
- is_observer = False
- if self.sub == 'from' and not self.is_transport()\
- and self.is_hidden_from_roster():
- is_observer = True
- return is_observer
-
- def is_groupchat(self):
- for account in common.gajim.gc_connected:
- if self.jid in common.gajim.gc_connected[account]:
- return True
- return False
-
- def is_transport(self):
- # if not '@' or '@' starts the jid then contact is transport
- return self.jid.find('@') <= 0
-
-
-class GC_Contact(CommonContact):
- """
- Information concerning each groupchat contact
- """
-
- def __init__(self, room_jid, account, name='', show='', status='', role='',
- affiliation='', jid='', resource='', our_chatstate=None,
- chatstate=None):
-
- CommonContact.__init__(self, jid, account, resource, show, status, name,
- our_chatstate, chatstate)
-
- self.room_jid = room_jid
- self.role = role
- self.affiliation = affiliation
-
- def get_full_jid(self):
- return self.room_jid + '/' + self.name
-
- def get_shown_name(self):
- return self.name
-
- def as_contact(self):
- """
- Create a Contact instance from this GC_Contact instance
- """
- return Contact(jid=self.get_full_jid(), account=self.account,
- name=self.name, groups=[], show=self.show, status=self.status,
- sub='none', client_caps=self.client_caps)
-
-
-class LegacyContactsAPI:
- """
- This is a GOD class for accessing contact and groupchat information.
- The API has several flaws:
-
- * it mixes concerns because it deals with contacts, groupchats,
- groupchat contacts and metacontacts
- * some methods like get_contact() may return None. This leads to
- a lot of duplication all over Gajim because it is not sure
- if we receive a proper contact or just None.
-
- It is a long way to cleanup this API. Therefore just stick with it
- and use it as before. We will try to figure out a migration path.
- """
- def __init__(self):
- self._metacontact_manager = MetacontactManager(self)
- self._accounts = {}
-
- def change_account_name(self, old_name, new_name):
- self._accounts[new_name] = self._accounts[old_name]
- self._accounts[new_name].name = new_name
- del self._accounts[old_name]
-
- self._metacontact_manager.change_account_name(old_name, new_name)
-
- def add_account(self, account_name):
- self._accounts[account_name] = Account(account_name, Contacts(),
- GC_Contacts())
- self._metacontact_manager.add_account(account_name)
-
- def get_accounts(self):
- return list(self._accounts.keys())
-
- def remove_account(self, account):
- del self._accounts[account]
- self._metacontact_manager.remove_account(account)
-
- def create_contact(self, jid, account, name='', groups=None, show='',
- status='', sub='', ask='', resource='', priority=0, keyID='',
- client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None,
- last_activity_time=None):
- if groups is None:
- groups = []
- # Use Account object if available
- account = self._accounts.get(account, account)
- return Contact(jid=jid, account=account, name=name, groups=groups,
- show=show, status=status, sub=sub, ask=ask, resource=resource,
- priority=priority, keyID=keyID, client_caps=client_caps,
- our_chatstate=our_chatstate, chatstate=chatstate,
- last_status_time=last_status_time,
- last_activity_time=last_activity_time)
-
- def create_self_contact(self, jid, account, resource, show, status, priority,
- name='', keyID=''):
- conn = common.gajim.connections[account]
- nick = name or common.gajim.nicks[account]
- account = self._accounts.get(account, account) # Use Account object if available
- self_contact = self.create_contact(jid=jid, account=account,
- name=nick, groups=['self_contact'], show=show, status=status,
- sub='both', ask='none', priority=priority, keyID=keyID,
- resource=resource)
- self_contact.pep = conn.pep
- return self_contact
-
- def create_not_in_roster_contact(self, jid, account, resource='', name='',
- keyID=''):
- # Use Account object if available
- account = self._accounts.get(account, account)
- return self.create_contact(jid=jid, account=account, resource=resource,
- name=name, groups=[_('Not in Roster')], show='not in roster',
- status='', sub='none', keyID=keyID)
-
- def copy_contact(self, contact):
- return self.create_contact(contact.jid, contact.account,
- name=contact.name, groups=contact.groups, show=contact.show,
- status=contact.status, sub=contact.sub, ask=contact.ask,
- resource=contact.resource, priority=contact.priority,
- keyID=contact.keyID, client_caps=contact.client_caps,
- our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
- last_status_time=contact.last_status_time,
- last_activity_time=contact.last_activity_time)
-
- def add_contact(self, account, contact):
- if account not in self._accounts:
- self.add_account(account)
- return self._accounts[account].contacts.add_contact(contact)
-
- def remove_contact(self, account, contact):
- if account not in self._accounts:
- return
- return self._accounts[account].contacts.remove_contact(contact)
-
- def remove_jid(self, account, jid, remove_meta=True):
- self._accounts[account].contacts.remove_jid(jid)
- if remove_meta:
- self._metacontact_manager.remove_metacontact(account, jid)
-
- def get_contacts(self, account, jid):
- return self._accounts[account].contacts.get_contacts(jid)
-
- def get_contact(self, account, jid, resource=None):
- return self._accounts[account].contacts.get_contact(jid, resource=resource)
-
- def iter_contacts(self, account):
- for contact in self._accounts[account].contacts.iter_contacts():
- yield contact
-
- def get_contact_from_full_jid(self, account, fjid):
- return self._accounts[account].contacts.get_contact_from_full_jid(fjid)
-
- def get_first_contact_from_jid(self, account, jid):
- return self._accounts[account].contacts.get_first_contact_from_jid(jid)
-
- def get_contacts_from_group(self, account, group):
- return self._accounts[account].contacts.get_contacts_from_group(group)
-
- def get_contacts_jid_list(self, account):
- return self._accounts[account].contacts.get_contacts_jid_list()
-
- def get_jid_list(self, account):
- return self._accounts[account].contacts.get_jid_list()
-
- def change_contact_jid(self, old_jid, new_jid, account):
- return self._accounts[account].change_contact_jid(old_jid, new_jid)
-
- def get_highest_prio_contact_from_contacts(self, contacts):
- if not contacts:
- return None
- prim_contact = contacts[0]
- for contact in contacts[1:]:
- if int(contact.priority) > int(prim_contact.priority):
- prim_contact = contact
- return prim_contact
-
- def get_contact_with_highest_priority(self, account, jid):
- contacts = self.get_contacts(account, jid)
- if not contacts and '/' in jid:
- # jid may be a fake jid, try it
- room, nick = jid.split('/', 1)
- contact = self.get_gc_contact(account, room, nick)
- return contact
- return self.get_highest_prio_contact_from_contacts(contacts)
-
- def get_nb_online_total_contacts(self, accounts=None, groups=None):
- """
- Return the number of online contacts and the total number of contacts
- """
- if not accounts:
- accounts = self.get_accounts()
- if groups is None:
- groups = []
- nbr_online = 0
- nbr_total = 0
- for account in accounts:
- our_jid = common.gajim.get_jid_from_account(account)
- for jid in self.get_jid_list(account):
- if jid == our_jid:
- continue
- if common.gajim.jid_is_transport(jid) and not \
- _('Transports') in groups:
- # do not count transports
- continue
- if self.has_brother(account, jid, accounts) and not \
- self.is_big_brother(account, jid, accounts):
- # count metacontacts only once
- continue
- contact = self._accounts[account].contacts._contacts[jid][0]
- if _('Not in roster') in contact.groups:
- continue
- in_groups = False
- if groups == []:
- in_groups = True
- else:
- for group in groups:
- if group in contact.get_shown_groups():
- in_groups = True
- break
-
- if in_groups:
- if contact.show not in ('offline', 'error'):
- nbr_online += 1
- nbr_total += 1
- return nbr_online, nbr_total
-
- def __getattr__(self, attr_name):
- # Only called if self has no attr_name
- if hasattr(self._metacontact_manager, attr_name):
- return getattr(self._metacontact_manager, attr_name)
- else:
- raise AttributeError(attr_name)
-
- def create_gc_contact(self, room_jid, account, name='', show='', status='',
- role='', affiliation='', jid='', resource=''):
- account = self._accounts.get(account, account) # Use Account object if available
- return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid,
- resource)
-
- def add_gc_contact(self, account, gc_contact):
- return self._accounts[account].gc_contacts.add_gc_contact(gc_contact)
-
- def remove_gc_contact(self, account, gc_contact):
- return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact)
-
- def remove_room(self, account, room_jid):
- return self._accounts[account].gc_contacts.remove_room(room_jid)
-
- def get_gc_list(self, account):
- return self._accounts[account].gc_contacts.get_gc_list()
-
- def get_nick_list(self, account, room_jid):
- return self._accounts[account].gc_contacts.get_nick_list(room_jid)
-
- def get_gc_contact(self, account, room_jid, nick):
- return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick)
-
- def is_gc_contact(self, account, jid):
- return self._accounts[account].gc_contacts.is_gc_contact(jid)
-
- def get_nb_role_total_gc_contacts(self, account, room_jid, role):
- return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role)
-
-
-class Contacts():
- """
- This is a breakout of the contact related behavior of the old
- Contacts class (which is not called LegacyContactsAPI)
- """
- def __init__(self):
- # list of contacts {jid1: [C1, C2]}, } one Contact per resource
- self._contacts = {}
-
- def add_contact(self, contact):
- if contact.jid not in self._contacts:
- self._contacts[contact.jid] = [contact]
- return
- contacts = self._contacts[contact.jid]
- # We had only one that was offline, remove it
- if len(contacts) == 1 and contacts[0].show == 'offline':
- # Do not use self.remove_contact: it deteles
- # self._contacts[account][contact.jid]
- contacts.remove(contacts[0])
- # If same JID with same resource already exists, use the new one
- for c in contacts:
- if c.resource == contact.resource:
- self.remove_contact(c)
- break
- contacts.append(contact)
-
- def remove_contact(self, contact):
- if contact.jid not in self._contacts:
- return
- if contact in self._contacts[contact.jid]:
- self._contacts[contact.jid].remove(contact)
- if len(self._contacts[contact.jid]) == 0:
- del self._contacts[contact.jid]
-
- def remove_jid(self, jid):
- """
- Remove all contacts for a given jid
- """
- if jid in self._contacts:
- del self._contacts[jid]
-
- def get_contacts(self, jid):
- """
- Return the list of contact instances for this jid
- """
- return self._contacts.get(jid, [])
-
- def get_contact(self, jid, resource=None):
- ### WARNING ###
- # This function returns a *RANDOM* resource if resource = None!
- # Do *NOT* use if you need to get the contact to which you
- # send a message for example, as a bare JID in Jabber means
- # highest available resource, which this function ignores!
- """
- Return the contact instance for the given resource if it's given else the
- first contact is no resource is given or None if there is not
- """
- if jid in self._contacts:
- if not resource:
- return self._contacts[jid][0]
- for c in self._contacts[jid]:
- if c.resource == resource:
- return c
- return self._contacts[jid][0]
-
- def iter_contacts(self):
- for jid in list(self._contacts.keys()):
- for contact in self._contacts[jid][:]:
- yield contact
-
- def get_jid_list(self):
- return list(self._contacts.keys())
-
- def get_contacts_jid_list(self):
- return [jid for jid, contact in self._contacts.items() if not
- contact[0].is_groupchat()]
-
- def get_contact_from_full_jid(self, fjid):
- """
- Get Contact object for specific resource of given jid
- """
- barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid)
- return self.get_contact(barejid, resource)
-
- def get_first_contact_from_jid(self, jid):
- if jid in self._contacts:
- return self._contacts[jid][0]
-
- def get_contacts_from_group(self, group):
- """
- Return all contacts in the given group
- """
- group_contacts = []
- for jid in self._contacts:
- contacts = self.get_contacts(jid)
- if group in contacts[0].groups:
- group_contacts += contacts
- return group_contacts
-
- def change_contact_jid(self, old_jid, new_jid):
- if old_jid not in self._contacts:
- return
- self._contacts[new_jid] = []
- for _contact in self._contacts[old_jid]:
- _contact.jid = new_jid
- self._contacts[new_jid].append(_contact)
- del self._contacts[old_jid]
-
-
-class GC_Contacts():
-
- def __init__(self):
- # list of contacts that are in gc {room_jid: {nick: C}}}
- self._rooms = {}
-
- def add_gc_contact(self, gc_contact):
- if gc_contact.room_jid not in self._rooms:
- self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact}
- else:
- self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact
-
- def remove_gc_contact(self, gc_contact):
- if gc_contact.room_jid not in self._rooms:
- return
- if gc_contact.name not in self._rooms[gc_contact.room_jid]:
- return
- del self._rooms[gc_contact.room_jid][gc_contact.name]
- # It was the last nick in room ?
- if not len(self._rooms[gc_contact.room_jid]):
- del self._rooms[gc_contact.room_jid]
-
- def remove_room(self, room_jid):
- if room_jid in self._rooms:
- del self._rooms[room_jid]
-
- def get_gc_list(self):
- return self._rooms.keys()
-
- def get_nick_list(self, room_jid):
- gc_list = self.get_gc_list()
- if not room_jid in gc_list:
- return []
- return list(self._rooms[room_jid].keys())
-
- def get_gc_contact(self, room_jid, nick):
- nick_list = self.get_nick_list(room_jid)
- if not nick in nick_list:
- return None
- return self._rooms[room_jid][nick]
-
- def is_gc_contact(self, jid):
- """
- >>> gc = GC_Contacts()
- >>> gc._rooms = {'gajim@conference.gajim.org' : {'test' : True}}
- >>> gc.is_gc_contact('gajim@conference.gajim.org/test')
- True
- >>> gc.is_gc_contact('test@jabbim.com')
- False
- """
- jid = jid.split('/')
- if len(jid) != 2:
- return False
- gcc = self.get_gc_contact(jid[0], jid[1])
- return gcc != None
-
- def get_nb_role_total_gc_contacts(self, room_jid, role):
- """
- Return the number of group chat contacts for the given role and the total
- number of group chat contacts
- """
- if room_jid not in self._rooms:
- return 0, 0
- nb_role = nb_total = 0
- for nick in self._rooms[room_jid]:
- if self._rooms[room_jid][nick].role == role:
- nb_role += 1
- nb_total += 1
- return nb_role, nb_total
-
-
-class MetacontactManager():
-
- def __init__(self, contacts):
- self._metacontacts_tags = {}
- self._contacts = contacts
-
- def change_account_name(self, old_name, new_name):
- self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name]
- del self._metacontacts_tags[old_name]
-
- def add_account(self, account):
- if account not in self._metacontacts_tags:
- self._metacontacts_tags[account] = {}
-
- def remove_account(self, account):
- del self._metacontacts_tags[account]
-
- def define_metacontacts(self, account, tags_list):
- self._metacontacts_tags[account] = tags_list
-
- def _get_new_metacontacts_tag(self, jid):
- if not jid in self._metacontacts_tags:
- return jid
- #FIXME: can this append ?
- assert False
-
- def iter_metacontacts_families(self, account):
- for tag in self._metacontacts_tags[account]:
- family = self._get_metacontacts_family_from_tag(account, tag)
- yield family
-
- def _get_metacontacts_tag(self, account, jid):
- """
- Return the tag of a jid
- """
- if not account in self._metacontacts_tags:
- return None
- for tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- if data['jid'] == jid:
- return tag
- return None
-
- def add_metacontact(self, brother_account, brother_jid, account, jid, order=None):
- tag = self._get_metacontacts_tag(brother_account, brother_jid)
- if not tag:
- tag = self._get_new_metacontacts_tag(brother_jid)
- self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid,
- 'tag': tag}]
- if brother_account != account:
- common.gajim.connections[brother_account].store_metacontacts(
- self._metacontacts_tags[brother_account])
- # be sure jid has no other tag
- old_tag = self._get_metacontacts_tag(account, jid)
- while old_tag:
- self.remove_metacontact(account, jid)
- old_tag = self._get_metacontacts_tag(account, jid)
- if tag not in self._metacontacts_tags[account]:
- self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}]
- else:
- if order:
- self._metacontacts_tags[account][tag].append({'jid': jid,
- 'tag': tag, 'order': order})
- else:
- self._metacontacts_tags[account][tag].append({'jid': jid,
- 'tag': tag})
- common.gajim.connections[account].store_metacontacts(
- self._metacontacts_tags[account])
-
- def remove_metacontact(self, account, jid):
- if not account in self._metacontacts_tags:
- return
-
- found = None
- for tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- if data['jid'] == jid:
- found = data
- break
- if found:
- self._metacontacts_tags[account][tag].remove(found)
- common.gajim.connections[account].store_metacontacts(
- self._metacontacts_tags[account])
- break
-
- def has_brother(self, account, jid, accounts):
- tag = self._get_metacontacts_tag(account, jid)
- if not tag:
- return False
- meta_jids = self._get_metacontacts_jids(tag, accounts)
- return len(meta_jids) > 1 or len(meta_jids[account]) > 1
-
- def is_big_brother(self, account, jid, accounts):
- family = self.get_metacontacts_family(account, jid)
- if family:
- nearby_family = [data for data in family
- if account in accounts]
- bb_data = self._get_metacontacts_big_brother(nearby_family)
- if bb_data['jid'] == jid and bb_data['account'] == account:
- return True
- return False
-
- def _get_metacontacts_jids(self, tag, accounts):
- """
- Return all jid for the given tag in the form {acct: [jid1, jid2],.}
- """
- answers = {}
- for account in self._metacontacts_tags:
- if tag in self._metacontacts_tags[account]:
- if account not in accounts:
- continue
- answers[account] = []
- for data in self._metacontacts_tags[account][tag]:
- answers[account].append(data['jid'])
- return answers
-
- def get_metacontacts_family(self, account, jid):
- """
- Return the family of the given jid, including jid in the form:
- [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional
- """
- tag = self._get_metacontacts_tag(account, jid)
- return self._get_metacontacts_family_from_tag(account, tag)
-
- def _get_metacontacts_family_from_tag(self, account, tag):
- if not tag:
- return []
- answers = []
- for account in self._metacontacts_tags:
- if tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- data['account'] = account
- answers.append(data)
- return answers
-
- def _compare_metacontacts(self, data1, data2):
- """
- Compare 2 metacontacts
-
- Data is {'jid': jid, 'account': account, 'order': order} order is
- optional
- """
- jid1 = data1['jid']
- jid2 = data2['jid']
- account1 = data1['account']
- account2 = data2['account']
- contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1)
- contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2)
- show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd',
- 'xa', 'away', 'chat', 'online', 'requested', 'message']
- # contact can be null when a jid listed in the metacontact data
- # is not in our roster
- if not contact1:
- if contact2:
- return -1 # prefer the known contact
- else:
- show1 = 0
- priority1 = 0
- else:
- show1 = show_list.index(contact1.show)
- priority1 = contact1.priority
- if not contact2:
- if contact1:
- return 1 # prefer the known contact
- else:
- show2 = 0
- priority2 = 0
- else:
- show2 = show_list.index(contact2.show)
- priority2 = contact2.priority
- # If only one is offline, it's always second
- if show1 > 2 and show2 < 3:
- return 1
- if show2 > 2 and show1 < 3:
- return -1
- if 'order' in data1 and 'order' in data2:
- if data1['order'] > data2['order']:
- return 1
- if data1['order'] < data2['order']:
- return -1
- if 'order' in data1:
- return 1
- if 'order' in data2:
- return -1
- transport1 = common.gajim.get_transport_name_from_jid(jid1)
- transport2 = common.gajim.get_transport_name_from_jid(jid2)
- if transport2 and not transport1:
- return 1
- if transport1 and not transport2:
- return -1
- if show1 > show2:
- return 1
- if show2 > show1:
- return -1
- if priority1 > priority2:
- return 1
- if priority2 > priority1:
- return -1
- server1 = common.gajim.get_server_from_jid(jid1)
- server2 = common.gajim.get_server_from_jid(jid2)
- myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname')
- myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname')
- if server1 == myserver1:
- if server2 != myserver2:
- return 1
- elif server2 == myserver2:
- return -1
- if jid1 > jid2:
- return 1
- if jid2 > jid1:
- return -1
- # If all is the same, compare accounts, they can't be the same
- if account1 > account2:
- return 1
- if account2 > account1:
- return -1
- return 0
-
- def get_nearby_family_and_big_brother(self, family, account):
- """
- Return the nearby family and its Big Brother
-
- Nearby family is the part of the family that is grouped with the
- metacontact. A metacontact may be over different accounts. If accounts
- are not merged then the given family is split account wise.
-
- (nearby_family, big_brother_jid, big_brother_account)
- """
- if common.gajim.config.get('mergeaccounts'):
- # group all together
- nearby_family = family
- else:
- # we want one nearby_family per account
- nearby_family = [data for data in family if account == data['account']]
-
- if not nearby_family:
- return (None, None, None)
- big_brother_data = self._get_metacontacts_big_brother(nearby_family)
- big_brother_jid = big_brother_data['jid']
- big_brother_account = big_brother_data['account']
-
- return (nearby_family, big_brother_jid, big_brother_account)
-
- def _get_metacontacts_big_brother(self, family):
- """
- Which of the family will be the big brother under wich all others will be
- ?
- """
- family.sort(key=cmp_to_key(self._compare_metacontacts))
- return family[-1]
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/src/common/crypto.py b/src/common/crypto.py
deleted file mode 100644
index 2e99cde1a..000000000
--- a/src/common/crypto.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# common crypto functions (mostly specific to XEP-0116, but useful elsewhere)
-# -*- coding:utf-8 -*-
-## src/common/crypto.py
-##
-## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
-##
-## 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/>.
-##
-
-import sys
-import os
-import math
-
-from hashlib import sha256 as SHA256
-
-# convert a large integer to a big-endian bitstring
-def encode_mpi(n):
- if n >= 256:
- return encode_mpi(n // 256) + bytes([n % 256])
- else:
- return bytes([n])
-
-# convert a large integer to a big-endian bitstring, padded with \x00s to
-# a multiple of 16 bytes
-def encode_mpi_with_padding(n):
- return pad_to_multiple(encode_mpi(n), 16, '\x00', True)
-
-# pad 'string' to a multiple of 'multiple_of' with 'char'.
-# pad on the left if 'left', otherwise pad on the right.
-def pad_to_multiple(string, multiple_of, char, left):
- mod = len(string) % multiple_of
- if mod == 0:
- return string
- else:
- padding = (multiple_of - mod) * char
-
- if left:
- return padding + string
- else:
- return string + padding
-
-# convert a big-endian bitstring to an integer
-def decode_mpi(s):
- if len(s) == 0:
- return 0
- else:
- return 256 * decode_mpi(s[:-1]) + s[-1]
-
-def sha256(string):
- sh = SHA256()
- sh.update(string)
- return sh.digest()
-
-base28_chr = "acdefghikmopqruvwxy123456789"
-
-def sas_28x5(m_a, form_b):
- sha = sha256(m_a + form_b + b'Short Authentication String')
- lsb24 = decode_mpi(sha[-3:])
- return base28(lsb24)
-
-def base28(n):
- if n >= 28:
- return base28(n // 28) + base28_chr[n % 28]
- else:
- return base28_chr[n]
-
-def add_entropy_sources_OpenSSL():
- # Other possibly variable data. This are very low quality sources of
- # entropy, but some of them are installation dependent and can be hard
- # to guess for the attacker.
- # Data available on all platforms Unix, Windows
- sources = [sys.argv, sys.builtin_module_names,
- sys.copyright, sys.getfilesystemencoding(), sys.hexversion,
- sys.modules, sys.path, sys.version, sys.api_version,
- os.environ, os.getcwd(), os.getpid()]
-
- for s in sources:
- OpenSSL.rand.add(str(s).encode('utf-8'), 1)
-
- # On Windows add the current contents of the screen to the PRNG state.
-# if os.name == 'nt':
-# OpenSSL.rand.screen()
- # The /proc filesystem on POSIX systems contains many random variables:
- # memory statistics, interrupt counts, network packet counts
- if os.name == 'posix':
- dirs = ['/proc', '/proc/net', '/proc/self']
- for d in dirs:
- if os.access(d, os.R_OK):
- for filename in os.listdir(d):
- OpenSSL.rand.add(filename.encode('utf-8'), 0)
- try:
- with open(d + os.sep + filename, "r") as fp:
- # Limit the ammount of read bytes, in case a memory
- # file was opened
- OpenSSL.rand.add(str(fp.read(5000)).encode('utf-8'),
- 1)
- except:
- # Ignore all read and access errors
- pass
-
-PYOPENSSL_PRNG_PRESENT = False
-try:
- import OpenSSL.rand
- PYOPENSSL_PRNG_PRESENT = True
-except ImportError:
- # PyOpenSSL PRNG not available
- pass
-
-def random_bytes(bytes_):
- if PYOPENSSL_PRNG_PRESENT:
- OpenSSL.rand.add(os.urandom(bytes_), bytes_)
- return OpenSSL.rand.bytes(bytes_)
- else:
- return os.urandom(bytes_)
-
-def generate_nonce():
- return random_bytes(8)
-
-# generate a random number between 'bottom' and 'top'
-def srand(bottom, top):
- # minimum number of bytes needed to represent that range
- bytes = int(math.ceil(math.log(top - bottom, 256)))
-
- # in retrospect, this is horribly inadequate.
- return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom
-
-# a faster version of (base ** exp) % mod
-# taken from <http://lists.danga.com/pipermail/yadis/2005-September/001445.html>
-def powmod(base, exp, mod):
- square = base % mod
- result = 1
-
- while exp > 0:
- if exp & 1: # exponent is odd
- result = (result * square) % mod
-
- square = (square * square) % mod
- exp //= 2
- return result
diff --git a/src/common/dataforms.py b/src/common/dataforms.py
deleted file mode 100644
index 6dd6b896c..000000000
--- a/src/common/dataforms.py
+++ /dev/null
@@ -1,754 +0,0 @@
-# this will go to src/common/xmpp later, for now it is in src/common
-# -*- coding:utf-8 -*-
-## src/common/dataforms.py
-##
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.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/>.
-##
-
-"""
-This module contains wrappers for different parts of data forms (JEP 0004). For
-information how to use them, read documentation
-"""
-
-import nbxmpp
-from common import helpers
-
-# exceptions used in this module
-# base class
-class Error(Exception): pass
-# when we get nbxmpp.Node which we do not understand
-class UnknownDataForm(Error): pass
-# when we get nbxmpp.Node which contains bad fields
-class WrongFieldValue(Error): pass
-
-# helper class to change class of already existing object
-class ExtendedNode(nbxmpp.Node, object):
- @classmethod
- def __new__(cls, *a, **b):
- if 'extend' not in b.keys() or not b['extend']:
- return object.__new__(cls)
-
- extend = b['extend']
- assert issubclass(cls, extend.__class__)
- extend.__class__ = cls
- return extend
-
-# helper decorator to create properties in cleaner way
-def nested_property(f):
- ret = f()
- p = {'doc': f.__doc__}
- for v in ('fget', 'fset', 'fdel', 'doc'):
- if v in ret.keys(): p[v]=ret[v]
- return property(**p)
-
-# helper to create fields from scratch
-def Field(typ, **attrs):
- ''' Helper function to create a field of given type. '''
- f = {
- 'boolean': BooleanField,
- 'fixed': StringField,
- 'hidden': StringField,
- 'text-private': StringField,
- 'text-single': StringField,
- 'jid-multi': JidMultiField,
- 'jid-single': JidSingleField,
- 'list-multi': ListMultiField,
- 'list-single': ListSingleField,
- 'text-multi': TextMultiField,
- }[typ](typ=typ, **attrs)
- return f
-
-def ExtendField(node):
- """
- Helper function to extend a node to field of appropriate type
- """
- # when validation (XEP-122) will go in, we could have another classes
- # like DateTimeField - so that dicts in Field() and ExtendField() will
- # be different...
- typ=node.getAttr('type')
- f = {
- 'boolean': BooleanField,
- 'fixed': StringField,
- 'hidden': StringField,
- 'text-private': StringField,
- 'text-single': StringField,
- 'jid-multi': JidMultiField,
- 'jid-single': JidSingleField,
- 'list-multi': ListMultiField,
- 'list-single': ListSingleField,
- 'text-multi': TextMultiField,
- }
- if typ not in f:
- typ = 'text-single'
- return f[typ](extend=node)
-
-def ExtendForm(node):
- """
- Helper function to extend a node to form of appropriate type
- """
- if node.getTag('reported') is not None:
- return MultipleDataForm(extend=node)
- else:
- return SimpleDataForm(extend=node)
-
-class DataField(ExtendedNode):
- """
- Keeps data about one field - var, field type, labels, instructions... Base
- class for different kinds of fields. Use Field() function to construct one
- of these
- """
-
- def __init__(self, typ=None, var=None, value=None, label=None, desc=None,
- required=False, options=None, extend=None):
-
- if extend is None:
- ExtendedNode.__init__(self, 'field')
-
- self.type_ = typ
- self.var = var
- if value is not None:
- self.value = value
- if label is not None:
- self.label = label
- if desc is not None:
- self.desc = desc
- self.required = required
- self.options = options
-
- @nested_property
- def type_():
- """
- Type of field. Recognized values are: 'boolean', 'fixed', 'hidden',
- 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi',
- 'text-private', 'text-single'. If you set this to something different,
- DataField will store given name, but treat all data as text-single
- """
- def fget(self):
- t = self.getAttr('type')
- if t is None:
- return 'text-single'
- return t
-
- def fset(self, value):
- assert isinstance(value, str)
- self.setAttr('type', value)
-
- return locals()
-
- @nested_property
- def var():
- """
- Field identifier
- """
- def fget(self):
- return self.getAttr('var')
-
- def fset(self, value):
- assert isinstance(value, str)
- self.setAttr('var', value)
-
- def fdel(self):
- self.delAttr('var')
-
- return locals()
-
- @nested_property
- def label():
- """
- Human-readable field name
- """
- def fget(self):
- l = self.getAttr('label')
- if not l:
- l = self.var
- return l
-
- def fset(self, value):
- assert isinstance(value, str)
- self.setAttr('label', value)
-
- def fdel(self):
- if self.getAttr('label'):
- self.delAttr('label')
-
- return locals()
-
- @nested_property
- def description():
- """
- Human-readable description of field meaning
- """
- def fget(self):
- return self.getTagData('desc') or ''
-
- def fset(self, value):
- assert isinstance(value, str)
- if value == '':
- fdel(self)
- else:
- self.setTagData('desc', value)
-
- def fdel(self):
- t = self.getTag('desc')
- if t is not None:
- self.delChild(t)
-
- return locals()
-
- @nested_property
- def required():
- """
- Controls whether this field required to fill. Boolean
- """
- def fget(self):
- return bool(self.getTag('required'))
-
- def fset(self, value):
- t = self.getTag('required')
- if t and not value:
- self.delChild(t)
- elif not t and value:
- self.addChild('required')
-
- return locals()
-
- @nested_property
- def media():
- """
- Media data
- """
- def fget(self):
- media = self.getTag('media', namespace=nbxmpp.NS_DATA_MEDIA)
- if media:
- return Media(media)
-
- def fset(self, value):
- fdel(self)
- self.addChild(node=value)
-
- def fdel(self):
- t = self.getTag('media')
- if t is not None:
- self.delChild(t)
-
- return locals()
-
- def is_valid(self):
- return True
-
-class Uri(nbxmpp.Node):
- def __init__(self, uri_tag):
- nbxmpp.Node.__init__(self, node=uri_tag)
-
- @nested_property
- def type_():
- """
- uri type
- """
- def fget(self):
- return self.getAttr('type')
-
- def fset(self, value):
- self.setAttr('type', value)
-
- def fdel(self):
- self.delAttr('type')
-
- return locals()
-
- @nested_property
- def uri_data():
- """
- uri data
- """
- def fget(self):
- return self.getData()
-
- def fset(self, value):
- self.setData(value)
-
- def fdel(self):
- self.setData(None)
-
- return locals()
-
-class Media(nbxmpp.Node):
- def __init__(self, media_tag):
- nbxmpp.Node.__init__(self, node=media_tag)
-
- @nested_property
- def uris():
- """
- URIs of the media element.
- """
- def fget(self):
- return map(Uri, self.getTags('uri'))
-
- def fset(self, value):
- fdel(self)
- for uri in value:
- self.addChild(node=uri)
-
- def fdel(self, value):
- for element in self.getTags('uri'):
- self.delChild(element)
-
- return locals()
-
-class BooleanField(DataField):
- @nested_property
- def value():
- """
- Value of field. May contain True, False or None
- """
- def fget(self):
- v = self.getTagData('value')
- if v in ('0', 'false'):
- return False
- if v in ('1', 'true'):
- return True
- if v is None:
- return False # default value is False
- raise WrongFieldValue
-
- def fset(self, value):
- self.setTagData('value', value and '1' or '0')
-
- def fdel(self, value):
- t = self.getTag('value')
- if t is not None:
- self.delChild(t)
-
- return locals()
-
-class StringField(DataField):
- """
- Covers fields of types: fixed, hidden, text-private, text-single
- """
-
- @nested_property
- def value():
- """
- Value of field. May be any string
- """
- def fget(self):
- return self.getTagData('value') or ''
-
- def fset(self, value):
- assert isinstance(value, str)
- if value == '' and not self.required:
- return fdel(self)
- self.setTagData('value', value)
-
- def fdel(self):
- try:
- self.delChild(self.getTag('value'))
- except ValueError: # if there already were no value tag
- pass
-
- return locals()
-
-class ListField(DataField):
- """
- Covers fields of types: jid-multi, jid-single, list-multi, list-single
- """
-
- @nested_property
- def options():
- """
- Options
- """
- def fget(self):
- options = []
- for element in self.getTags('option'):
- v = element.getTagData('value')
- if v is None:
- raise WrongFieldValue
- l = element.getAttr('label')
- if not l:
- l = v
- options.append((l, v))
- return options
-
- def fset(self, values):
- fdel(self)
- for value, label in values:
- self.addChild('option', {'label': label}).setTagData('value', value)
-
- def fdel(self):
- for element in self.getTags('option'):
- self.delChild(element)
-
- return locals()
-
- def iter_options(self):
- for element in self.iterTags('option'):
- v = element.getTagData('value')
- if v is None:
- raise WrongFieldValue
- l = element.getAttr('label')
- if not l:
- l = v
- yield (v, l)
-
-class ListSingleField(ListField, StringField):
- """
- Covers list-single field
- """
- def is_valid(self):
- if not self.required:
- return True
- if not self.value:
- return False
- return True
-
-class JidSingleField(ListSingleField):
- """
- Covers jid-single fields
- """
- def is_valid(self):
- if self.value:
- try:
- helpers.parse_jid(self.value)
- return True
- except:
- return False
- if self.required:
- return False
- return True
-
-class ListMultiField(ListField):
- """
- Covers list-multi fields
- """
-
- @nested_property
- def values():
- """
- Values held in field
- """
- def fget(self):
- values = []
- for element in self.getTags('value'):
- values.append(element.getData())
- return values
-
- def fset(self, values):
- fdel(self)
- for value in values:
- self.addChild('value').setData(value)
-
- def fdel(self):
- for element in self.getTags('value'):
- self.delChild(element)
-
- return locals()
-
- def iter_values(self):
- for element in self.getTags('value'):
- yield element.getData()
-
- def is_valid(self):
- if not self.required:
- return True
- if not self.values:
- return False
- return True
-
-class JidMultiField(ListMultiField):
- """
- Covers jid-multi fields
- """
- def is_valid(self):
- if len(self.values):
- for value in self.values:
- try:
- helpers.parse_jid(value)
- except:
- return False
- return True
- if self.required:
- return False
- return True
-
-class TextMultiField(DataField):
- @nested_property
- def value():
- """
- Value held in field
- """
- def fget(self):
- value = ''
- for element in self.iterTags('value'):
- value += '\n' + element.getData()
- return value[1:]
-
- def fset(self, value):
- fdel(self)
- if value == '':
- return
- for line in value.split('\n'):
- self.addChild('value').setData(line)
-
- def fdel(self):
- for element in self.getTags('value'):
- self.delChild(element)
-
- return locals()
-
-class DataRecord(ExtendedNode):
- """
- The container for data fields - an xml element which has DataField elements
- as children
- """
- def __init__(self, fields=None, associated=None, extend=None):
- self.associated = associated
- self.vars = {}
- if extend is None:
- # we have to build this object from scratch
- nbxmpp.Node.__init__(self)
-
- if fields is not None:
- self.fields = fields
- else:
- # we already have nbxmpp.Node inside - try to convert all
- # fields into DataField objects
- if fields is None:
- for field in self.iterTags('field'):
- if not isinstance(field, DataField):
- ExtendField(field)
- self.vars[field.var] = field
- else:
- for field in self.getTags('field'):
- self.delChild(field)
- self.fields = fields
-
- @nested_property
- def fields():
- """
- List of fields in this record
- """
- def fget(self):
- return self.getTags('field')
-
- def fset(self, fields):
- fdel(self)
- for field in fields:
- if not isinstance(field, DataField):
- ExtendField(extend=field)
- self.addChild(node=field)
-
- def fdel(self):
- for element in self.getTags('field'):
- self.delChild(element)
-
- return locals()
-
- def iter_fields(self):
- """
- Iterate over fields in this record. Do not take associated into account
- """
- for field in self.iterTags('field'):
- yield field
-
- def iter_with_associated(self):
- """
- Iterate over associated, yielding both our field and associated one
- together
- """
- for field in self.associated.iter_fields():
- yield self[field.var], field
-
- def __getitem__(self, item):
- return self.vars[item]
-
- def is_valid(self):
- for f in self.iter_fields():
- if not f.is_valid():
- return False
- return True
-
-class DataForm(ExtendedNode):
- def __init__(self, type_=None, title=None, instructions=None, extend=None):
- if extend is None:
- # we have to build form from scratch
- nbxmpp.Node.__init__(self, 'x', attrs={'xmlns': nbxmpp.NS_DATA})
-
- if type_ is not None:
- self.type_=type_
- if title is not None:
- self.title=title
- if instructions is not None:
- self.instructions=instructions
-
- @nested_property
- def type_():
- """
- Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'.
- 'form' - this form is to be filled in; you will be able soon to do:
- filledform = DataForm(replyto=thisform)
- """
- def fget(self):
- return self.getAttr('type')
-
- def fset(self, type_):
- assert type_ in ('form', 'submit', 'cancel', 'result')
- self.setAttr('type', type_)
-
- return locals()
-
- @nested_property
- def title():
- """
- Title of the form
-
- Human-readable, should not contain any \\r\\n.
- """
- def fget(self):
- return self.getTagData('title')
-
- def fset(self, title):
- self.setTagData('title', title)
-
- def fdel(self):
- try:
- self.delChild('title')
- except ValueError:
- pass
-
- return locals()
-
- @nested_property
- def instructions():
- """
- Instructions for this form
-
- Human-readable, may contain \\r\\n.
- """
- # TODO: the same code is in TextMultiField. join them
- def fget(self):
- value = ''
- for valuenode in self.getTags('instructions'):
- value += '\n' + valuenode.getData()
- return value[1:]
-
- def fset(self, value):
- fdel(self)
- if value == '': return
- for line in value.split('\n'):
- self.addChild('instructions').setData(line)
-
- def fdel(self):
- for value in self.getTags('instructions'):
- self.delChild(value)
-
- return locals()
-
-class SimpleDataForm(DataForm, DataRecord):
- def __init__(self, type_=None, title=None, instructions=None, fields=None, \
- extend=None):
- DataForm.__init__(self, type_=type_, title=title,
- instructions=instructions, extend=extend)
- DataRecord.__init__(self, fields=fields, extend=self, associated=self)
-
- def get_purged(self):
- c = SimpleDataForm(extend=self)
- del c.title
- c.instructions = ''
- to_be_removed = []
- for f in c.iter_fields():
- if f.required:
- # add <value> if there is not
- if hasattr(f, 'value') and not f.value:
- f.value = ''
- # Keep all required fields
- continue
- if (hasattr(f, 'value') and not f.value and f.value != 0) or (
- hasattr(f, 'values') and len(f.values) == 0):
- to_be_removed.append(f)
- else:
- del f.label
- del f.description
- del f.media
- for f in to_be_removed:
- c.delChild(f)
- return c
-
-class MultipleDataForm(DataForm):
- def __init__(self, type_=None, title=None, instructions=None, items=None,
- extend=None):
- DataForm.__init__(self, type_=type_, title=title,
- instructions=instructions, extend=extend)
- # all records, recorded into DataRecords
- if extend is None:
- if items is not None:
- self.items = items
- else:
- # we already have nbxmpp.Node inside - try to convert all
- # fields into DataField objects
- if items is None:
- self.items = list(self.iterTags('item'))
- else:
- for item in self.getTags('item'):
- self.delChild(item)
- self.items = items
- reported_tag = self.getTag('reported')
- self.reported = DataRecord(extend=reported_tag)
-
- @nested_property
- def items():
- """
- A list of all records
- """
- def fget(self):
- return list(self.iter_records())
-
- def fset(self, records):
- fdel(self)
- for record in records:
- if not isinstance(record, DataRecord):
- DataRecord(extend=record)
- self.addChild(node=record)
-
- def fdel(self):
- for record in self.getTags('item'):
- self.delChild(record)
-
- return locals()
-
- def iter_records(self):
- for record in self.getTags('item'):
- yield record
-
-# @nested_property
-# def reported():
-# """
-# DataRecord that contains descriptions of fields in records
-# """
-# def fget(self):
-# return self.getTag('reported')
-# def fset(self, record):
-# try:
-# self.delChild('reported')
-# except:
-# pass
-#
-# record.setName('reported')
-# self.addChild(node=record)
-# return locals()
diff --git a/src/common/dbus_support.py b/src/common/dbus_support.py
deleted file mode 100644
index cd4611152..000000000
--- a/src/common/dbus_support.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/dbus_support.py
-##
-## Copyright (C) 2005 Andrew Sayman <lorien420 AT myrealbox.com>
-## Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import os
-
-from common import gajim
-from common import exceptions
-
-_GAJIM_ERROR_IFACE = 'org.gajim.dbus.Error'
-
-try:
- import dbus
- from dbus.mainloop.glib import DBusGMainLoop
- DBusGMainLoop(set_as_default=True)
-except ImportError:
- supported = False
- if not os.name == 'nt': # only say that to non Windows users
- print(_('D-Bus python bindings are missing in this computer'))
- print(_('D-Bus capabilities of Gajim cannot be used'))
-else:
- try:
- # test if dbus-x11 is installed
- bus = dbus.SystemBus()
- bus = dbus.SessionBus()
- supported = True # does user have D-Bus bindings?
- except dbus.DBusException:
- supported = False
- if not os.name == 'nt': # only say that to non Windows users
- print(_('D-Bus does not run correctly on this machine'))
- print(_('D-Bus capabilities of Gajim cannot be used'))
- except exceptions.SystemBusNotPresent:
- print(_('D-Bus does not run correctly on this machine: system bus not '
- 'present'))
- except exceptions.SessionBusNotPresent:
- print(_('D-Bus does not run correctly on this machine: session bus not '
- 'present'))
-
-class SystemBus:
- """
- A Singleton for the DBus SystemBus
- """
-
- def __init__(self):
- self.system_bus = None
-
- def SystemBus(self):
- if not supported:
- raise exceptions.DbusNotSupported
-
- if not self.present():
- raise exceptions.SystemBusNotPresent
- return self.system_bus
-
- def bus(self):
- return self.SystemBus()
-
- def present(self):
- if not supported:
- return False
- if self.system_bus is None:
- try:
- self.system_bus = dbus.SystemBus()
- except dbus.DBusException:
- self.system_bus = None
- return False
- if self.system_bus is None:
- return False
- # Don't exit Gajim when dbus is stopped
- self.system_bus.set_exit_on_disconnect(False)
- return True
-
-system_bus = SystemBus()
-
-class SessionBus:
- """
- A Singleton for the D-Bus SessionBus
- """
-
- def __init__(self):
- self.session_bus = None
-
- def SessionBus(self):
- if not supported:
- raise exceptions.DbusNotSupported
-
- if not self.present():
- raise exceptions.SessionBusNotPresent
- return self.session_bus
-
- def bus(self):
- return self.SessionBus()
-
- def present(self):
- if not supported:
- return False
- if self.session_bus is None:
- try:
- self.session_bus = dbus.SessionBus()
- except dbus.DBusException:
- self.session_bus = None
- return False
- if self.session_bus is None:
- return False
- return True
-
-session_bus = SessionBus()
-
-def get_interface(interface, path, start_service=True):
- """
- Get an interface on the current SessionBus. If the interface isn't running,
- try to start it first
- """
- if not supported:
- return None
- if session_bus.present():
- bus = session_bus.SessionBus()
- else:
- return None
- try:
- obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
- dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
- running_services = dbus_iface.ListNames()
- started = True
- if interface not in running_services:
- # try to start the service
- if start_service and dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1:
- started = True
- else:
- started = False
- if not started:
- return None
- obj = bus.get_object(interface, path)
- return dbus.Interface(obj, interface)
- except Exception as e:
- gajim.log.debug(str(e))
- return None
-
-
-def get_notifications_interface(notif=None):
- """
- Get the notifications interface
-
- :param notif: DesktopNotification instance
- """
- # try to see if KDE notifications are available
- iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications',
- start_service=False)
- if iface != None:
- if notif != None:
- notif.kde_notifications = True
- return iface
- # KDE notifications don't seem to be available, falling back to
- # notification-daemon
- else:
- if notif != None:
- notif.kde_notifications = False
- return get_interface('org.freedesktop.Notifications',
- '/org/freedesktop/Notifications')
-
-if supported:
- class MissingArgument(dbus.DBusException):
- _dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument'
-
- class InvalidArgument(dbus.DBusException):
- '''Raised when one of the provided arguments is invalid.'''
- _dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument'
diff --git a/src/common/defs.py b/src/common/defs.py
deleted file mode 100644
index c5212348b..000000000
--- a/src/common/defs.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/defs.py
-##
-## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
-## Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import subprocess
-import sys
-import os.path
-
-docdir = '../'
-basedir = '../'
-localedir = '../po'
-version = '0.16.10.3'
-
-try:
- node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
- stdout=subprocess.PIPE).communicate()[0]
- if node:
- version += '-' + node.decode('utf-8').strip()
-except Exception:
- pass
-
-for base in ('.', 'common'):
- sys.path.append(os.path.join(base, '.libs'))
diff --git a/src/common/dh.py b/src/common/dh.py
deleted file mode 100644
index aeba2d288..000000000
--- a/src/common/dh.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/dh.py
-##
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-##
-## 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/>.
-##
-
-"""
-This module defines a number of constants; specifically, large primes suitable
-for use with the Diffie-Hellman key exchange
-
-These constants have been obtained from RFC2409 and RFC3526.
-"""
-
-import string
-
-generators = [
- None, # one to get the right offset
- 2,
- 2,
- None,
- None,
- 2,
- None,
- None,
- None,
- None,
- None,
- None,
- None,
- None,
- 2, # group 14
- 2,
- 2,
- 2,
- 2
-]
-
-_HEX_PRIMES = [
- None,
-
- # group 1
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A63A3620 FFFFFFFF FFFFFFFF''',
-
- # group 2
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE65381
- FFFFFFFF FFFFFFFF''',
-
- # XXX how do I obtain these?
- None,
- None,
-
- # group 5
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF''',
-
- None,
- None,
- None,
- None,
- None,
- None,
- None,
- None,
-
- # group 14
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
- E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
- DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
- 15728E5A 8AACAA68 FFFFFFFF FFFFFFFF''',
-
- # group 15
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
- E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
- DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
- 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64
- ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7
- ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B
- F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
- BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31
- 43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF''',
-
- # group 16
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
- E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
- DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
- 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64
- ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7
- ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B
- F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
- BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31
- 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7
- 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA
- 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6
- 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED
- 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9
- 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199
- FFFFFFFF FFFFFFFF''',
-
- # group 17
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
- 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
- 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
- A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
- 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
- FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
- 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
- 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
- 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
- B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
- 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
- BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
- E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26
- 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB
- 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2
- 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127
- D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492
- 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406
- AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918
- DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151
- 2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03
- F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F
- BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA
- CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B
- B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632
- 387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E
- 6DCC4024 FFFFFFFF FFFFFFFF''',
-
- # group 18
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
- E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
- DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
- 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64
- ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7
- ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B
- F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
- BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31
- 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7
- 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA
- 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6
- 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED
- 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9
- 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492
- 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD
- F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831
- 179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B
- DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF
- 5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6
- D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3
- 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA
- CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328
- 06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C
- DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE
- 12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4
- 38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300
- 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568
- 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9
- 22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B
- 4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A
- 062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36
- 4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1
- B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92
- 4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47
- 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71
- 60C980DD 98EDD3DF FFFFFFFF FFFFFFFF'''
-]
-
-_ALL_ASCII = ''.join(map(chr, range(256)))
-
-def hex_to_decimal(stripee):
- if not stripee:
- return None
-
- return int(stripee.translate(_ALL_ASCII).translate(
- str.maketrans("", "", string.whitespace)), 16)
-
-primes = list(map(hex_to_decimal, _HEX_PRIMES))
diff --git a/src/common/events.py b/src/common/events.py
deleted file mode 100644
index 3896cc69b..000000000
--- a/src/common/events.py
+++ /dev/null
@@ -1,454 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/events.py
-##
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import time
-
-class Event:
- """
- Information concerning each event
- """
-
- def __init__(self, time_=None, show_in_roster=False, show_in_systray=True):
- """
- type_ in chat, normal, file-request, file-error, file-completed,
- file-request-error, file-send-error, file-stopped, gc_msg, pm,
- printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm,
- gc-invitation, subscription_request, unsubscribedm jingle-incoming
-
- parameters is (per type_):
- chat, normal, pm: [message, subject, kind, time, encrypted, resource,
- msg_log_id]
- where kind in error, incoming
- file-*: file_props
- gc_msg: None
- printed_chat: [message, subject, control, msg_log_id]
- printed_*: None
- messages that are already printed in chat, but not read
- gc-invitation: [room_jid, reason, password, is_continued, jid_from]
- subscription_request: [text, nick]
- unsubscribed: contact
- jingle-incoming: (fulljid, sessionid, content_types)
- """
- if time_:
- self.time_ = time_
- else:
- self.time_ = time.time()
- self.show_in_roster = show_in_roster
- self.show_in_systray = show_in_systray
- # Set when adding the event
- self.jid = None
- self.account = None
-
-class ChatEvent(Event):
- type_ = 'chat'
- def __init__ (self, message, subject, kind, time, encrypted, resource,
- msg_log_id, correct_id=None, xhtml=None, session=None, form_node=None,
- displaymarking=None, sent_forwarded=False, time_=None, show_in_roster=False,
- show_in_systray=True, additional_data=None):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.message = message
- self.subject = subject
- self.kind = kind
- self.time = time
- self.encrypted = encrypted
- self.resource = resource
- self.msg_log_id = msg_log_id
- self.correct_id = correct_id
- self.xhtml = xhtml
- self.session = session
- self.form_node = form_node
- self.displaymarking = displaymarking
- self.sent_forwarded = sent_forwarded
- self.additional_data = additional_data
-
-class NormalEvent(ChatEvent):
- type_ = 'normal'
-
-class PmEvent(ChatEvent):
- type_ = 'pm'
-
-class PrintedChatEvent(Event):
- type_ = 'printed_chat'
- def __init__(self, message, subject, control, msg_log_id, time_=None,
- show_in_roster=False, show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.message = message
- self.subject = subject
- self.control = control
- self.msg_log_id = msg_log_id
-
-class PrintedGcMsgEvent(PrintedChatEvent):
- type_ = 'printed_gc_msg'
-
-class PrintedMarkedGcMsgEvent(PrintedChatEvent):
- type_ = 'printed_marked_gc_msg'
-
-class PrintedPmEvent(PrintedChatEvent):
- type_ = 'printed_pm'
-
-class SubscriptionRequestEvent(Event):
- type_ = 'subscription_request'
- def __init__(self, text, nick, time_=None, show_in_roster=False,
- show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.text = text
- self.nick = nick
-
-class UnsubscribedEvent(Event):
- type_ = 'unsubscribed'
- def __init__(self, contact, time_=None, show_in_roster=False,
- show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.contact = contact
-
-class GcInvitationtEvent(Event):
- type_ = 'gc-invitation'
- def __init__(self, room_jid, reason, password, is_continued, jid_from,
- time_=None, show_in_roster=False, show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.room_jid = room_jid
- self.reason = reason
- self.password = password
- self.is_continued = is_continued
- self.jid_from = jid_from
-
-class FileRequestEvent(Event):
- type_ = 'file-request'
- def __init__(self, file_props, time_=None, show_in_roster=False, show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.file_props = file_props
-
-class FileSendErrorEvent(FileRequestEvent):
- type_ = 'file-send-error'
-
-class FileErrorEvent(FileRequestEvent):
- type_ = 'file-error'
-
-class FileRequestErrorEvent(FileRequestEvent):
- type_ = 'file-request-error'
-
-class FileCompletedEvent(FileRequestEvent):
- type_ = 'file-completed'
-
-class FileStoppedEvent(FileRequestEvent):
- type_ = 'file-stopped'
-
-class FileHashErrorEvent(FileRequestEvent):
- type_ = 'file-hash-error'
-
-class JingleIncomingEvent(Event):
- type_ = 'jingle-incoming'
- def __init__(self, peerjid, sid, content_types, time_=None, show_in_roster=False, show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.peerjid = peerjid
- self.sid = sid
- self.content_types = content_types
-
-class Events:
- """
- Information concerning all events
- """
-
- def __init__(self):
- self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
- self._event_added_listeners = []
- self._event_removed_listeners = []
-
- def event_added_subscribe(self, listener):
- """
- Add a listener when an event is added to the queue
- """
- if not listener in self._event_added_listeners:
- self._event_added_listeners.append(listener)
-
- def event_added_unsubscribe(self, listener):
- """
- Remove a listener when an event is added to the queue
- """
- if listener in self._event_added_listeners:
- self._event_added_listeners.remove(listener)
-
- def event_removed_subscribe(self, listener):
- """
- Add a listener when an event is removed from the queue
- """
- if not listener in self._event_removed_listeners:
- self._event_removed_listeners.append(listener)
-
- def event_removed_unsubscribe(self, listener):
- """
- Remove a listener when an event is removed from the queue
- """
- if listener in self._event_removed_listeners:
- self._event_removed_listeners.remove(listener)
-
- def fire_event_added(self, event):
- for listener in self._event_added_listeners:
- listener(event)
-
- def fire_event_removed(self, event_list):
- for listener in self._event_removed_listeners:
- listener(event_list)
-
- def change_account_name(self, old_name, new_name):
- if old_name in self._events:
- self._events[new_name] = self._events[old_name]
- del self._events[old_name]
-
- def add_account(self, account):
- self._events[account] = {}
-
- def get_accounts(self):
- return self._events.keys()
-
- def remove_account(self, account):
- del self._events[account]
-
- def add_event(self, account, jid, event):
- # No such account before ?
- if account not in self._events:
- self._events[account] = {jid: [event]}
- # no such jid before ?
- elif jid not in self._events[account]:
- self._events[account][jid] = [event]
- else:
- self._events[account][jid].append(event)
- event.jid = jid
- event.account = account
- self.fire_event_added(event)
-
- def remove_events(self, account, jid, event=None, types=None):
- """
- If event is not specified, remove all events from this jid, optionally
- only from given type return True if no such event found
- """
- if types is None:
- types = []
- if account not in self._events:
- return True
- if jid not in self._events[account]:
- return True
- if event: # remove only one event
- if event in self._events[account][jid]:
- if len(self._events[account][jid]) == 1:
- del self._events[account][jid]
- else:
- self._events[account][jid].remove(event)
- self.fire_event_removed([event])
- return
- else:
- return True
- if types:
- new_list = [] # list of events to keep
- removed_list = [] # list of removed events
- for ev in self._events[account][jid]:
- if ev.type_ not in types:
- new_list.append(ev)
- else:
- removed_list.append(ev)
- if len(new_list) == len(self._events[account][jid]):
- return True
- if new_list:
- self._events[account][jid] = new_list
- else:
- del self._events[account][jid]
- self.fire_event_removed(removed_list)
- return
- # no event nor type given, remove them all
- self.fire_event_removed(self._events[account][jid])
- del self._events[account][jid]
-
- def change_jid(self, account, old_jid, new_jid):
- if account not in self._events:
- return
- if old_jid not in self._events[account]:
- return
- if new_jid in self._events[account]:
- self._events[account][new_jid] += self._events[account][old_jid]
- else:
- self._events[account][new_jid] = self._events[account][old_jid]
- del self._events[account][old_jid]
-
- def get_nb_events(self, types=None, account=None):
- if types is None:
- types = []
- return self._get_nb_events(types = types, account = account)
-
- def get_events(self, account, jid=None, types=None):
- """
- Return all events from the given account of the form {jid1: [], jid2:
- []}. If jid is given, returns all events from the given jid in a list: []
- optionally only from given type
- """
- if types is None:
- types = []
- if account not in self._events:
- return []
- if not jid:
- events_list = {} # list of events
- for jid_ in self._events[account]:
- events = []
- for ev in self._events[account][jid_]:
- if not types or ev.type_ in types:
- events.append(ev)
- if events:
- events_list[jid_] = events
- return events_list
- if jid not in self._events[account]:
- return []
- events_list = [] # list of events
- for ev in self._events[account][jid]:
- if not types or ev.type_ in types:
- events_list.append(ev)
- return events_list
-
- def get_first_event(self, account=None, jid=None, type_=None):
- """
- Return the first event of type type_ if given
- """
- if not account:
- return self._get_first_event_with_attribute(self._events)
- events_list = self.get_events(account, jid, type_)
- # be sure it's bigger than latest event
- first_event_time = time.time() + 1
- first_event = None
- for event in events_list:
- if event.time_ < first_event_time:
- first_event_time = event.time_
- first_event = event
- return first_event
-
- def _get_nb_events(self, account=None, jid=None, attribute=None, types=None):
- """
- Return the number of pending events
- """
- if types is None:
- types = []
- nb = 0
- if account:
- accounts = [account]
- else:
- accounts = self._events.keys()
- for acct in accounts:
- if acct not in self._events:
- continue
- if jid:
- jids = [jid]
- else:
- jids = self._events[acct].keys()
- for j in jids:
- if j not in self._events[acct]:
- continue
- for event in self._events[acct][j]:
- if types and event.type_ not in types:
- continue
- if not attribute or \
- attribute == 'systray' and event.show_in_systray or \
- attribute == 'roster' and event.show_in_roster:
- nb += 1
- return nb
-
- def _get_some_events(self, attribute):
- """
- Attribute in systray, roster
- """
- events = {}
- for account in self._events:
- events[account] = {}
- for jid in self._events[account]:
- events[account][jid] = []
- for event in self._events[account][jid]:
- if attribute == 'systray' and event.show_in_systray or \
- attribute == 'roster' and event.show_in_roster:
- events[account][jid].append(event)
- if not events[account][jid]:
- del events[account][jid]
- if not events[account]:
- del events[account]
- return events
-
- def _get_first_event_with_attribute(self, events):
- """
- Get the first event
-
- events is in the form {account1: {jid1: [ev1, ev2], },. }
- """
- # be sure it's bigger than latest event
- first_event_time = time.time() + 1
- first_account = None
- first_jid = None
- first_event = None
- for account in events:
- for jid in events[account]:
- for event in events[account][jid]:
- if event.time_ < first_event_time:
- first_event_time = event.time_
- first_account = account
- first_jid = jid
- first_event = event
- return first_account, first_jid, first_event
-
- def get_nb_systray_events(self, types=None):
- """
- Return the number of events displayed in roster
- """
- if types is None:
- types = []
- return self._get_nb_events(attribute='systray', types=types)
-
- def get_systray_events(self):
- """
- Return all events that must be displayed in systray:
- {account1: {jid1: [ev1, ev2], },. }
- """
- return self._get_some_events('systray')
-
- def get_first_systray_event(self):
- events = self.get_systray_events()
- return self._get_first_event_with_attribute(events)
-
- def get_nb_roster_events(self, account=None, jid=None, types=None):
- """
- Return the number of events displayed in roster
- """
- if types is None:
- types = []
- return self._get_nb_events(attribute='roster', account=account,
- jid=jid, types=types)
-
- def get_roster_events(self):
- """
- Return all events that must be displayed in roster:
- {account1: {jid1: [ev1, ev2], },. }
- """
- return self._get_some_events('roster')
diff --git a/src/common/exceptions.py b/src/common/exceptions.py
deleted file mode 100644
index 79c418b56..000000000
--- a/src/common/exceptions.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/exceptions.py
-##
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
-##
-## 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/>.
-##
-
-class PysqliteOperationalError(Exception):
- """
- Sqlite2 raised pysqlite2.dbapi2.OperationalError
- """
-
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
-
- def __str__(self):
- return self.text
-
-class DatabaseMalformed(Exception):
- """
- The databas can't be read
- """
-
- def __init__(self, path=''):
- Exception.__init__(self)
- self.path = path
-
- def __str__(self):
- return _('The database file (%s) cannot be read. '
- 'Try to repair it (see '
- 'https://dev.gajim.org/gajim/gajim/wikis/help/DatabaseBackup)'
- ' or remove it (all history will be lost).') % self.path
-
-class ServiceNotAvailable(Exception):
- """
- This exception is raised when we cannot use Gajim remotely'
- """
-
- def __init__(self):
- Exception.__init__(self)
-
- def __str__(self):
- return _('Service not available: Gajim is not running, or remote_control is False')
-
-class DbusNotSupported(Exception):
- """
- D-Bus is not installed or python bindings are missing
- """
-
- def __init__(self):
- Exception.__init__(self)
-
- def __str__(self):
- return _('D-Bus is not present on this machine or python module is missing')
-
-class SessionBusNotPresent(Exception):
- """
- This exception indicates that there is no session daemon
- """
-
- def __init__(self):
- Exception.__init__(self)
-
- def __str__(self):
- return _('Session bus is not available.\nTry reading %(url)s') % \
- {'url': 'http://trac.gajim.org/wiki/GajimDBus'}
-
-class SystemBusNotPresent(Exception):
- """
- This exception indicates that there is no session daemon
- """
-
- def __init__(self):
- Exception.__init__(self)
-
- def __str__(self):
- return _('System bus is not available.\nTry reading %(url)s') % \
- {'url': 'http://trac.gajim.org/wiki/GajimDBus'}
-
-class NegotiationError(Exception):
- """
- A session negotiation failed
- """
- pass
-
-class DecryptionError(Exception):
- """
- A message couldn't be decrypted into usable XML
- """
- pass
-
-class Cancelled(Exception):
- """
- The user cancelled an operation
- """
- pass
-
-class LatexError(Exception):
- """
- LaTeX processing failed for some reason
- """
-
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
-
- def __str__(self):
- return self.text
-
-class GajimGeneralException(Exception):
- """
- This exception is our general exception
- """
-
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
-
- def __str__(self):
- return self.text
-
-class PluginsystemError(Exception):
- """
- Error in the pluginsystem
- """
-
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
-
- def __str__(self):
- return self.text
diff --git a/src/common/file_props.py b/src/common/file_props.py
deleted file mode 100644
index a5d7a5fb6..000000000
--- a/src/common/file_props.py
+++ /dev/null
@@ -1,166 +0,0 @@
-"""
-This module is in charge of taking care of all the infomation related to
-individual files. Files are identified by the account name and its sid.
-
-
->>> print FilesProp.getFileProp('jabberid', '10')
-None
->>> fp = FilesProp()
-Traceback (most recent call last):
- ...
-Exception: this class should not be instatiated
->>> print FilesProp.getAllFileProp()
-[]
->>> fp = FilesProp.getNewFileProp('jabberid', '10')
->>> fp2 = FilesProp.getFileProp('jabberid', '10')
->>> fp == fp2
-True
-"""
-
-class FilesProp:
- _files_props = {}
-
- def __init__(self):
- raise Exception('this class should not be instatiated')
-
- @classmethod
- def getNewFileProp(cls, account, sid):
- fp = FileProp(account, sid)
- cls.setFileProp(fp, account, sid)
- return fp
-
- @classmethod
- def getFileProp(cls, account, sid):
- if (account, sid) in cls._files_props.keys():
- return cls._files_props[account, sid]
-
- @classmethod
- def getFilePropByAccount(cls, account):
- # Returns a list of file_props in one account
- file_props = []
- for account, sid in cls._files_props:
- if account == account:
- file_props.append(cls._files_props[account, sid])
- return file_props
-
- @classmethod
- def getFilePropByType(cls, type_, sid):
- # This method should be deleted. Getting fileprop by type and sid is not
- # unique enough. More than one fileprop might have the same type and sid
- files_prop = cls.getAllFileProp()
- for fp in files_prop:
- if fp.type_ == type_ and fp.sid == sid:
- return fp
-
- @classmethod
- def getFilePropBySid(cls, sid):
- # This method should be deleted. It is kept to make things compatible
- # This method should be replaced and instead get the file_props by
- # account and sid
- files_prop = cls.getAllFileProp()
- for fp in files_prop:
- if fp.sid == sid:
- return fp
-
- @classmethod
- def getFilePropByTransportSid(cls, account, sid):
- files_prop = cls.getAllFileProp()
- for fp in files_prop:
- if fp.account == account and fp.transport_sid == sid:
- return fp
-
- @classmethod
- def getAllFileProp(cls):
- return list(cls._files_props.values())
-
- @classmethod
- def setFileProp(cls, fp, account, sid):
- cls._files_props[account, sid] = fp
-
- @classmethod
- def deleteFileProp(cls, file_prop):
- files_props = cls._files_props
- a = s = None
- for account, sid in files_props:
- fp = files_props[account, sid]
- if fp is file_prop:
- a = account
- s = sid
- if a != None and s != None:
- del files_props[a, s]
-
-
-class FileProp(object):
-
- def __init__(self, account, sid):
- # Do not instatiate this class directly. Call FilesProp.getNeFileProp
- # instead
- self.streamhosts = []
- self.transfered_size = []
- self.started = False
- self.completed = False
- self.paused = False
- self.stalled = False
- self.connected = False
- self.stopped = False
- self.is_a_proxy = False
- self.proxyhost = None
- self.proxy_sender = None
- self.proxy_receiver = None
- self.streamhost_used = None
- # method callback called in case of transfer failure
- self.failure_cb = None
- # method callback called when disconnecting
- self.disconnect_cb = None
- self.continue_cb = None
- self.sha_str = None
- # transfer type: 's' for sending and 'r' for receiving
- self.type_ = None
- self.error = None
- # Elapsed time of the file transfer
- self.elapsed_time = 0
- self.last_time = None
- self.received_len = None
- # full file path
- self.file_name = None
- self.name = None
- self.date = None
- self.desc = None
- self.offset = None
- self.sender = None
- self.receiver = None
- self.tt_account = None
- self.size = None
- self._sid = sid
- self.transport_sid = None
- self.account = account
- self.mime_type = None
- self.algo = None
- self.direction = None
- self.syn_id = None
- self.seq = None
- self.hash_ = None
- self.fd = None
- self.startexmpp = None
- # Type of the session, if it is 'jingle' or 'si'
- self.session_type = None
- self.request_id = None
- self.proxyhosts = None
- self.dstaddr = None
-
- def getsid(self):
- # Getter of the property sid
- return self._sid
-
- def setsid(self, value):
- # The sid value will change
- # we need to change the in _files_props key as well
- del FilesProp._files_props[self.account, self._sid]
- self._sid = value
- FilesProp._files_props[self.account, self._sid] = self
-
- sid = property(getsid, setsid)
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/src/common/fuzzyclock.py b/src/common/fuzzyclock.py
deleted file mode 100755
index 79fded35f..000000000
--- a/src/common/fuzzyclock.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/fuzzyclock.py
-##
-## Copyright (C) 2006 Christoph Neuroth <delmonico AT gmx.net>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2009 Benjamin Richter <br AT waldteufel-online.net>
-##
-## 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/>.
-##
-
-"""
-Python class to show a "fuzzy clock".
-Homepage of the original: http://home.gna.org/fuzzyclock/
-Project Page of the original: http://gna.org/projects/fuzzyclock
-
-The class is based on a port from PHP code by
-Henrique Recidive <henrique at recidive.com> which was
-in turn based on the Fuzzy Clock Applet of Frerich Raabe (KDE).
-So most of the credit goes to this guys, thanks :-)
-"""
-
-
-class FuzzyClock:
- HOUR_NAMES = [ _('twelve'), _('one'), _('two'), _('three'), _('four'),
- _('five'), _('six'), _('seven'), _('eight'), _('nine'), _('ten'),
- _('eleven') ]
-
- #Strings to use for the output. %(0)s will be replaced with the preceding hour
- #(e.g. "x PAST %(0)s"), %(1)s with the coming hour (e.g. "x TO %(0)s"). '''
- FUZZY_TIME = [ _("%(0)s o'clock"), _('five past %(0)s'), _('ten past %(0)s'),
- _('quarter past %(0)s'), _('twenty past %(0)s'), _('twenty five past %(0)s'),
- _('half past %(0)s'), _('twenty five to %(1)s'), _('twenty to %(1)s'),
- _('quarter to %(1)s'), _('ten to %(1)s'), _('five to %(1)s'), _("%(1)s o'clock") ]
-
- FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'),
- _('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'),
- _('Late evening'), _('Night') ]
-
- FUZZY_WEEK = [ _('Start of week'), _('Middle of week'), _('Middle of week'),
- _('Middle of week'), _('End of week'), _('Weekend!'), _('Weekend!') ]
-
- def fuzzy_time(self, fuzzyness, now):
- if fuzzyness == 1 or fuzzyness == 2:
- if fuzzyness == 1:
- sector = int(round(now.tm_min / 5.0))
- else:
- sector = int(round(now.tm_min / 15.0)) * 3
-
- return self.FUZZY_TIME[sector] % {
- '0': self.HOUR_NAMES[now.tm_hour % 12],
- '1': self.HOUR_NAMES[(now.tm_hour + 1) % 12]}
-
- elif fuzzyness == 3:
- return self.FUZZY_DAYTIME[int(round(now.tm_hour / 3.0))]
-
- else:
- return self.FUZZY_WEEK[now.tm_wday]
diff --git a/src/common/gajim.py b/src/common/gajim.py
deleted file mode 100644
index ef6b816a2..000000000
--- a/src/common/gajim.py
+++ /dev/null
@@ -1,486 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/gajim.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import os
-import logging
-import locale
-import gi
-import uuid
-from distutils.version import LooseVersion as V
-
-from common import config
-import nbxmpp
-from common import ged as ged_module
-
-interface = None # The actual interface (the gtk one for the moment)
-thread_interface = None # Interface to run a thread and then a callback
-config = config.Config()
-version = config.get('version')
-connections = {} # 'account name': 'account (connection.Connection) instance'
-ipython_window = None
-app = None # Gtk.Application
-
-ged = ged_module.GlobalEventsDispatcher() # Global Events Dispatcher
-nec = None # Network Events Controller
-plugin_manager = None # Plugins Manager
-
-log = logging.getLogger('gajim')
-
-logger = None
-
-from common import configpaths
-gajimpaths = configpaths.gajimpaths
-
-VCARD_PATH = gajimpaths['VCARD']
-AVATAR_PATH = gajimpaths['AVATAR']
-MY_EMOTS_PATH = gajimpaths['MY_EMOTS']
-MY_ICONSETS_PATH = gajimpaths['MY_ICONSETS']
-MY_MOOD_ICONSETS_PATH = gajimpaths['MY_MOOD_ICONSETS']
-MY_ACTIVITY_ICONSETS_PATH = gajimpaths['MY_ACTIVITY_ICONSETS']
-MY_CACERTS = gajimpaths['MY_CACERTS']
-MY_PEER_CERTS_PATH = gajimpaths['MY_PEER_CERTS']
-TMP = gajimpaths['TMP']
-DATA_DIR = gajimpaths['DATA']
-ICONS_DIR = gajimpaths['ICONS']
-HOME_DIR = gajimpaths['HOME']
-PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'],
- gajimpaths['PLUGINS_USER']]
-PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR']
-MY_CERT_DIR = gajimpaths['MY_CERT']
-
-try:
- LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
-except (ValueError, locale.Error):
- # unknown locale, use en is better than fail
- LANG = None
-if LANG is None:
- LANG = 'en'
-else:
- LANG = LANG[:2] # en, fr, el etc..
-
-os_info = None # used to cache os information
-
-from common.contacts import LegacyContactsAPI
-from common.events import Events
-
-gmail_domains = ['gmail.com', 'googlemail.com']
-
-transport_type = {} # list the type of transport
-
-last_message_time = {} # list of time of the latest incomming message
- # {acct1: {jid1: time1, jid2: time2}, }
-encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..}
-
-contacts = LegacyContactsAPI()
-gc_connected = {} # tell if we are connected to the room or not
- # {acct: {room_jid: True}}
-gc_passwords = {} # list of the pass required to enter a room
- # {room_jid: password}
-automatic_rooms = {} # list of rooms that must be automaticaly configured
- # and for which we have a list of invities
- #{account: {room_jid: {'invities': []}}}
-new_room_nick = None # if it's != None, use this nick instead of asking for
- # a new nickname when there is a conflict.
-
-groups = {} # list of groups
-newly_added = {} # list of contacts that has just signed in
-to_be_removed = {} # list of contacts that has just signed out
-
-events = Events()
-
-notification = None
-
-nicks = {} # list of our nick names in each account
-# should we block 'contact signed in' notifications for this account?
-# this is only for the first 30 seconds after we change our show
-# to something else than offline
-# can also contain account/transport_jid to block notifications for contacts
-# from this transport
-block_signed_in_notifications = {}
-con_types = {} # type of each connection (ssl, tls, tcp, ...)
-
-sleeper_state = {} # whether we pass auto away / xa or not
-#'off': don't use sleeper for this account
-#'online': online and use sleeper
-#'autoaway': autoaway and use sleeper
-#'autoxa': autoxa and use sleeper
-status_before_autoaway = {}
-
-# jid of transport contacts for which we need to ask avatar when transport will
-# be online
-transport_avatar = {} # {transport_jid: [jid_list]}
-
-# Is Gnome configured to activate on single click ?
-single_click = False
-SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible', 'error']
-
-# zeroconf account name
-ZEROCONF_ACC_NAME = 'Local'
-
-# These will be set in gajim.gui_interface.
-idlequeue = None
-socks5queue = None
-
-HAVE_ZEROCONF = True
-try:
- __import__('avahi')
-except ImportError:
- try:
- __import__('pybonjour')
- except Exception: # Linux raises ImportError, Windows raises WindowsError
- HAVE_ZEROCONF = False
-
-HAVE_PYCRYPTO = True
-try:
- __import__('Crypto')
-except ImportError:
- HAVE_PYCRYPTO = False
-
-HAVE_GPG = True
-GPG_BINARY = 'gpg'
-try:
- import gnupg
- '''
- We need https://pypi.python.org/pypi/python-gnupg
- but https://pypi.python.org/pypi/gnupg shares the same package name.
- It cannot be used as a drop-in replacement.
- We test with a version check if python-gnupg is installed as it is
- on a much lower version number than gnupg
- Also we need at least python-gnupg 0.3.8
- '''
- v_gnupg = gnupg.__version__
- if V(v_gnupg) < V('0.3.8') or V(v_gnupg) > V('1.0.0'):
- log.info('Gajim needs python-gnupg >= 0.3.8')
- HAVE_GPG = False
-except ImportError:
- HAVE_GPG = False
-else:
- import os
- import subprocess
- def test_gpg(binary='gpg'):
- if os.name == 'nt':
- gpg_cmd = binary + ' -h >nul 2>&1'
- else:
- gpg_cmd = binary + ' -h >/dev/null 2>&1'
- if subprocess.call(gpg_cmd, shell=True):
- return False
- return True
- if test_gpg(binary='gpg2'):
- GPG_BINARY = 'gpg2'
- if not test_gpg(binary='gpg'):
- HAVE_GPG = False
-
-HAVE_PYOPENSSL = True
-try:
- import OpenSSL.SSL
- import OpenSSL.crypto
- ver = OpenSSL.__version__
- ver_l = [int(i) for i in ver.split('.')]
- if ver_l < [0, 12]:
- raise ImportError
-except Exception:
- HAVE_PYOPENSSL = False
-
-HAVE_FARSTREAM = True
-try:
- if os.name == 'nt':
- os.environ['FS_PLUGIN_PATH'] = 'gtk\\lib\\farstream-0.1'
- os.environ['GST_PLUGIN_PATH'] = 'gtk\\lib\\gstreamer-0.10'
- gi.require_version('Farstream', '0.2')
- from gi.repository import Farstream
- gi.require_version('Gst', '1.0')
- from gi.repository import Gst
- from gi.repository import GLib
- try:
- Gst.init(None)
- conference = Gst.ElementFactory.make('fsrtpconference', None)
- session = conference.new_session(Farstream.MediaType.AUDIO)
- del session
- del conference
- except GLib.GError:
- HAVE_FARSTREAM = False
-
-except (ImportError, ValueError):
- HAVE_FARSTREAM = False
-
-HAVE_UPNP_IGD = True
-try:
- gi.require_version('GUPnPIgd', '1.0')
- from gi.repository import GUPnPIgd
- gupnp_igd = GUPnPIgd.SimpleIgd()
-except ValueError:
- HAVE_UPNP_IGD = False
-
-HAVE_PYCURL = True
-try:
- __import__('pycurl')
-except ImportError:
- HAVE_PYCURL = False
-
-
-gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
-gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
- nbxmpp.NS_MUC, nbxmpp.NS_MUC_USER, nbxmpp.NS_MUC_ADMIN, nbxmpp.NS_MUC_OWNER,
- nbxmpp.NS_MUC_CONFIG, nbxmpp.NS_COMMANDS, nbxmpp.NS_DISCO_INFO, 'ipv6',
- 'jabber:iq:gateway', nbxmpp.NS_LAST, nbxmpp.NS_PRIVACY, nbxmpp.NS_PRIVATE,
- nbxmpp.NS_REGISTER, nbxmpp.NS_VERSION, nbxmpp.NS_DATA, nbxmpp.NS_ENCRYPTED,
- 'msglog', 'sslc2s', 'stringprep', nbxmpp.NS_PING, nbxmpp.NS_TIME_REVISED,
- nbxmpp.NS_SSN, nbxmpp.NS_MOOD, nbxmpp.NS_ACTIVITY, nbxmpp.NS_NICK,
- nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES_2,
- nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256,
- nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CONFERENCE, nbxmpp.NS_CORRECT]
-
-# Optional features gajim supports per account
-gajim_optional_features = {}
-
-# Capabilities hash per account
-caps_hash = {}
-
-def get_an_id():
- return str(uuid.uuid4())
-
-def get_nick_from_jid(jid):
- pos = jid.find('@')
- return jid[:pos]
-
-def get_server_from_jid(jid):
- pos = jid.find('@') + 1 # after @
- return jid[pos:]
-
-def get_name_and_server_from_jid(jid):
- name = get_nick_from_jid(jid)
- server = get_server_from_jid(jid)
- return name, server
-
-def get_room_and_nick_from_fjid(jid):
- # fake jid is the jid for a contact in a room
- # gaim@conference.jabber.no/nick/nick-continued
- # return ('gaim@conference.jabber.no', 'nick/nick-continued')
- l = jid.split('/', 1)
- if len(l) == 1: # No nick
- l.append('')
- return l
-
-def get_real_jid_from_fjid(account, fjid):
- """
- Return real jid or returns None, if we don't know the real jid
- """
- room_jid, nick = get_room_and_nick_from_fjid(fjid)
- if not nick: # It's not a fake_jid, it is a real jid
- return fjid # we return the real jid
- real_jid = fjid
- if interface.msg_win_mgr.get_gc_control(room_jid, account):
- # It's a pm, so if we have real jid it's in contact.jid
- gc_contact = contacts.get_gc_contact(account, room_jid, nick)
- if not gc_contact:
- return
- # gc_contact.jid is None when it's not a real jid (we don't know real jid)
- real_jid = gc_contact.jid
- return real_jid
-
-def get_room_from_fjid(jid):
- return get_room_and_nick_from_fjid(jid)[0]
-
-def get_contact_name_from_jid(account, jid):
- c = contacts.get_first_contact_from_jid(account, jid)
- return c.name
-
-def get_jid_without_resource(jid):
- return jid.split('/')[0]
-
-def construct_fjid(room_jid, nick):
- # fake jid is the jid for a contact in a room
- # gaim@conference.jabber.org/nick
- return room_jid + '/' + nick
-
-def get_resource_from_jid(jid):
- jids = jid.split('/', 1)
- if len(jids) > 1:
- return jids[1] # abc@doremi.org/res/res-continued
- else:
- return ''
-
-def get_number_of_accounts():
- """
- Return the number of ALL accounts
- """
- return len(connections.keys())
-
-def get_number_of_connected_accounts(accounts_list = None):
- """
- Returns the number of CONNECTED accounts. Uou can optionally pass an
- accounts_list and if you do those will be checked, else all will be checked
- """
- connected_accounts = 0
- if accounts_list is None:
- accounts = connections.keys()
- else:
- accounts = accounts_list
- for account in accounts:
- if account_is_connected(account):
- connected_accounts = connected_accounts + 1
- return connected_accounts
-
-def account_is_connected(account):
- if account not in connections:
- return False
- if connections[account].connected > 1: # 0 is offline, 1 is connecting
- return True
- else:
- return False
-
-def account_is_disconnected(account):
- return not account_is_connected(account)
-
-def zeroconf_is_connected():
- return account_is_connected(ZEROCONF_ACC_NAME) and \
- config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf')
-
-def get_number_of_securely_connected_accounts():
- """
- Return the number of the accounts that are SSL/TLS connected
- """
- num_of_secured = 0
- for account in connections.keys():
- if account_is_securely_connected(account):
- num_of_secured += 1
- return num_of_secured
-
-def account_is_securely_connected(account):
- if account_is_connected(account) and \
- account in con_types and con_types[account] in ('tls', 'ssl'):
- return True
- else:
- return False
-
-def get_transport_name_from_jid(jid, use_config_setting = True):
- """
- Returns 'aim', 'gg', 'irc' etc
-
- If JID is not from transport returns None.
- """
- #FIXME: jid can be None! one TB I saw had this problem:
- # in the code block # it is a groupchat presence in handle_event_notify
- # jid was None. Yann why?
- if not jid or (use_config_setting and not config.get('use_transports_iconsets')):
- return
-
- host = get_server_from_jid(jid)
- if host in transport_type:
- return transport_type[host]
-
- # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports)
- host_splitted = host.split('.')
- if len(host_splitted) != 0:
- # now we support both 'icq.' and 'icq' but not icqsucks.org
- host = host_splitted[0]
-
- if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo',
- 'mrim', 'facebook'):
- return host
- elif host == 'gg':
- return 'gadu-gadu'
- elif host == 'jit':
- return 'icq'
- elif host == 'facebook':
- return 'facebook'
- else:
- return None
-
-def jid_is_transport(jid):
- # if not '@' or '@' starts the jid then it is transport
- if jid.find('@') <= 0:
- return True
- return False
-
-def get_jid_from_account(account_name, full=False):
- """
- Return the jid we use in the given account
- """
- name = config.get_per('accounts', account_name, 'name')
- hostname = config.get_per('accounts', account_name, 'hostname')
- jid = name + '@' + hostname
- if full:
- resource = connections[account_name].server_resource
- jid += '/' + resource
- return jid
-
-def get_our_jids():
- """
- Returns a list of the jids we use in our accounts
- """
- our_jids = list()
- for account in contacts.get_accounts():
- our_jids.append(get_jid_from_account(account))
- return our_jids
-
-def get_hostname_from_account(account_name, use_srv = False):
- """
- Returns hostname (if custom hostname is used, that is returned)
- """
- if use_srv and connections[account_name].connected_hostname:
- return connections[account_name].connected_hostname
- if config.get_per('accounts', account_name, 'use_custom_host'):
- return config.get_per('accounts', account_name, 'custom_host')
- return config.get_per('accounts', account_name, 'hostname')
-
-def get_notification_image_prefix(jid):
- """
- Returns the prefix for the notification images
- """
- transport_name = get_transport_name_from_jid(jid)
- if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'):
- prefix = transport_name
- else:
- prefix = 'jabber'
- return prefix
-
-def get_name_from_jid(account, jid):
- """
- Return from JID's shown name and if no contact returns jids
- """
- contact = contacts.get_first_contact_from_jid(account, jid)
- if contact:
- actor = contact.get_shown_name()
- else:
- actor = jid
- return actor
-
-def get_priority(account, show):
- """
- Return the priority an account must have
- """
- if not show:
- show = 'online'
-
- if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \
- config.get_per('accounts', account, 'adjust_priority_with_status'):
- return config.get_per('accounts', account, 'autopriority_' + show)
- return config.get_per('accounts', account, 'priority')
diff --git a/src/common/ged.py b/src/common/ged.py
deleted file mode 100644
index 1f387f016..000000000
--- a/src/common/ged.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## 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/>.
-##
-
-'''
-Global Events Dispatcher module.
-
-:author: Mateusz Biliński <mateusz@bilinski.it>
-:since: 8th August 2008
-:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
-:copyright: Copyright (2011) Yann Leboulanger <asterix@lagaule.org>
-:license: GPL
-'''
-
-import traceback
-
-from nbxmpp import NodeProcessed
-import logging
-log = logging.getLogger('gajim.c.ged')
-
-PRECORE = 10
-CORE = 20
-POSTCORE = 30
-PREGUI = 40
-PREGUI1 = 50
-GUI1 = 60
-POSTGUI1 = 70
-PREGUI2 = 80
-GUI2 = 90
-POSTGUI2 = 100
-POSTGUI = 110
-
-OUT_PREGUI = 10
-OUT_PREGUI1 = 20
-OUT_GUI1 = 30
-OUT_POSTGUI1 = 40
-OUT_PREGUI2 = 50
-OUT_GUI2 = 60
-OUT_POSTGUI2 = 70
-OUT_POSTGUI = 80
-OUT_PRECORE = 90
-OUT_CORE = 100
-OUT_POSTCORE = 110
-
-class GlobalEventsDispatcher(object):
-
- def __init__(self):
- self.handlers = {}
-
- def register_event_handler(self, event_name, priority, handler):
- if event_name in self.handlers:
- handlers_list = self.handlers[event_name]
- i = 0
- for i, h in enumerate(handlers_list):
- if priority < h[0]:
- break
- else:
- # no event with smaller prio found, put it at the end
- i += 1
-
- handlers_list.insert(i, (priority, handler))
- else:
- self.handlers[event_name] = [(priority, handler)]
-
- def remove_event_handler(self, event_name, priority, handler):
- if event_name in self.handlers:
- try:
- self.handlers[event_name].remove((priority, handler))
- except ValueError as error:
- log.warning('''Function (%s) with priority "%s" never registered
- as handler of event "%s". Couldn\'t remove. Error: %s'''
- %(handler, priority, event_name, error))
-
- def raise_event(self, event_name, *args, **kwargs):
- log.debug('%s Args: %s'%(event_name, str(args)))
- if event_name in self.handlers:
- node_processed = False
- for priority, handler in self.handlers[event_name]:
- try:
- if handler(*args, **kwargs):
- return True
- except NodeProcessed:
- node_processed = True
- except Exception:
- log.error('Error while running an even handler: %s' % \
- handler)
- traceback.print_exc()
- if node_processed:
- raise NodeProcessed
diff --git a/src/common/gpg.py b/src/common/gpg.py
deleted file mode 100644
index dc7f695cb..000000000
--- a/src/common/gpg.py
+++ /dev/null
@@ -1,150 +0,0 @@
-## src/common/gpg.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import os
-import logging
-from common import gajim
-from common.gajim import HAVE_GPG, GPG_BINARY
-
-if HAVE_GPG:
- import gnupg
- gnupg.logger = logging.getLogger('gajim.c.gnupg')
-
- class GnuPG(gnupg.GPG):
- def __init__(self):
- use_agent = gajim.config.get('use_gpg_agent')
- gnupg.GPG.__init__(self, gpgbinary=GPG_BINARY, use_agent=use_agent)
- encoding = gajim.config.get('pgp_encoding')
- if encoding:
- self.encoding = encoding
- self.decode_errors = 'replace'
- self.passphrase = None
- self.always_trust = [] # list of keyID to always trust
-
- def encrypt(self, str_, recipients, always_trust=False):
- trust = always_trust
- if not trust:
- # check if we trust all keys
- trust = True
- for key in recipients:
- if key not in self.always_trust:
- trust = False
- if not trust:
- # check that we'll be able to encrypt
- result = super(GnuPG, self).list_keys(keys=recipients)
- for key in result:
- if key['trust'] not in ('f', 'u'):
- if key['keyid'][-8:] not in self.always_trust:
- return '', 'NOT_TRUSTED ' + key['keyid'][-8:]
- else:
- trust = True
- result = super(GnuPG, self).encrypt(str_.encode('utf8'), recipients,
- always_trust=trust, passphrase=self.passphrase)
-
- if result.ok:
- error = ''
- else:
- error = result.status
-
- return self._stripHeaderFooter(str(result)), error
-
- def decrypt(self, str_, keyID):
- data = self._addHeaderFooter(str_, 'MESSAGE')
- result = super(GnuPG, self).decrypt(data.encode('utf8'),
- passphrase=self.passphrase)
-
- return str(result)
-
- def sign(self, str_, keyID):
- result = super(GnuPG, self).sign(str_.encode('utf8'), keyid=keyID, detach=True,
- passphrase=self.passphrase)
-
- if result.fingerprint:
- return self._stripHeaderFooter(str(result))
- if hasattr(result, 'status') and result.status == 'key expired':
- return 'KEYEXPIRED'
- return 'BAD_PASSPHRASE'
-
- def verify(self, str_, sign):
- if str_ is None:
- return ''
- # Hash algorithm is not transfered in the signed presence stanza so try
- # all algorithms. Text name for hash algorithms from RFC 4880 - section 9.4
- hash_algorithms = ['SHA512', 'SHA384', 'SHA256', 'SHA224', 'SHA1', 'RIPEMD160']
- for algo in hash_algorithms:
- data = os.linesep.join(
- ['-----BEGIN PGP SIGNED MESSAGE-----',
- 'Hash: ' + algo,
- '',
- str_,
- self._addHeaderFooter(sign, 'SIGNATURE')]
- )
- result = super(GnuPG, self).verify(data.encode('utf8'))
- if result.valid:
- return result.key_id
-
- return ''
-
- def get_key(self, keyID):
- return super(GnuPG, self).list_keys(keys=[keyID])
-
- def get_keys(self, secret=False):
- keys = {}
- result = super(GnuPG, self).list_keys(secret=secret)
- for key in result:
- # Take first not empty uid
- keys[key['keyid'][8:]] = [uid for uid in key['uids'] if uid][0]
- return keys
-
- def get_secret_keys(self):
- return self.get_keys(True)
-
- def _stripHeaderFooter(self, data):
- """
- Remove header and footer from data
- """
- if not data: return ''
- lines = data.splitlines()
- while lines[0] != '':
- lines.remove(lines[0])
- while lines[0] == '':
- lines.remove(lines[0])
- i = 0
- for line in lines:
- if line:
- if line[0] == '-': break
- i = i+1
- line = '\n'.join(lines[0:i])
- return line
-
- def _addHeaderFooter(self, data, type_):
- """
- Add header and footer from data
- """
- out = "-----BEGIN PGP %s-----" % type_ + os.linesep
- out = out + "Version: PGP" + os.linesep
- out = out + os.linesep
- out = out + data + os.linesep
- out = out + "-----END PGP %s-----" % type_ + os.linesep
- return out
diff --git a/src/common/helpers.py b/src/common/helpers.py
deleted file mode 100644
index 17a1fb9d0..000000000
--- a/src/common/helpers.py
+++ /dev/null
@@ -1,1531 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/helpers.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Alex Mauer <hawke AT hawkesnest.net>
-## Copyright (C) 2006-2007 Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## James Newton <redshodan AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import sys
-import re
-import os
-import subprocess
-import urllib
-import webbrowser
-import errno
-import select
-import base64
-import hashlib
-import shlex
-from common import caps_cache
-import socket
-import time
-import datetime
-
-from encodings.punycode import punycode_encode
-from string import Template
-
-from common.i18n import Q_
-from common.i18n import ngettext
-
-if os.name == 'nt':
- try:
- HAS_WINSOUND = True
- import winsound # windows-only built-in module for playing wav
- except ImportError:
- HAS_WINSOUND = False
- print('Gajim is not able to playback sound because'
- 'pywin32 is missing', file=sys.stderr)
-
-try:
- import wave # posix-only fallback wav playback
- import ossaudiodev as oss
-except Exception:
- pass
-
-import logging
-log = logging.getLogger('gajim.c.helpers')
-
-special_groups = (_('Transports'), _('Not in Roster'), _('Observers'), _('Groupchats'))
-
-class InvalidFormat(Exception):
- pass
-
-def decompose_jid(jidstring):
- user = None
- server = None
- resource = None
-
- # Search for delimiters
- user_sep = jidstring.find('@')
- res_sep = jidstring.find('/')
-
- if user_sep == -1:
- if res_sep == -1:
- # host
- server = jidstring
- else:
- # host/resource
- server = jidstring[0:res_sep]
- resource = jidstring[res_sep + 1:]
- else:
- if res_sep == -1:
- # user@host
- user = jidstring[0:user_sep]
- server = jidstring[user_sep + 1:]
- else:
- if user_sep < res_sep:
- # user@host/resource
- user = jidstring[0:user_sep]
- server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)]
- resource = jidstring[res_sep + 1:]
- else:
- # server/resource (with an @ in resource)
- server = jidstring[0:res_sep]
- resource = jidstring[res_sep + 1:]
- return user, server, resource
-
-def parse_jid(jidstring):
- """
- Perform stringprep on all JID fragments from a string and return the full
- jid
- """
- # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
-
- return prep(*decompose_jid(jidstring))
-
-def idn_to_ascii(host):
- """
- Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible
- encoding)
- """
- from encodings import idna
- labels = idna.dots.split(host)
- converted_labels = []
- for label in labels:
- if label:
- converted_labels.append(idna.ToASCII(label).decode('utf-8'))
- else:
- converted_labels.append('')
- return ".".join(converted_labels)
-
-def ascii_to_idn(host):
- """
- Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain
- Names)
- """
- from encodings import idna
- labels = idna.dots.split(host)
- converted_labels = []
- for label in labels:
- converted_labels.append(idna.ToUnicode(label))
- return ".".join(converted_labels)
-
-def puny_encode_url(url):
- _url = url
- if '//' not in _url:
- _url = '//' + _url
- try:
- o = urllib.parse.urlparse(_url)
- p_loc = idn_to_ascii(o.netloc)
- except Exception:
- log.debug('urlparse failed: %s', url)
- return False
- return url.replace(o.netloc, p_loc)
-
-def parse_resource(resource):
- """
- Perform stringprep on resource and return it
- """
- if resource:
- try:
- from nbxmpp.stringprepare import resourceprep
- return resourceprep.prepare(resource)
- except UnicodeError:
- raise InvalidFormat('Invalid character in resource.')
-
-def prep(user, server, resource):
- """
- Perform stringprep on all JID fragments and return the full jid
- """
- # This function comes from
- #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
-
- ip_address = False
-
- try:
- socket.inet_aton(server)
- ip_address = True
- except socket.error:
- pass
-
- if not ip_address and hasattr(socket, 'inet_pton'):
- try:
- socket.inet_pton(socket.AF_INET6, server.strip('[]'))
- server = '[%s]' % server.strip('[]')
- ip_address = True
- except (socket.error, ValueError):
- pass
-
- if not ip_address:
- if server is not None:
- if len(server) < 1 or len(server) > 1023:
- raise InvalidFormat(_('Server must be between 1 and 1023 chars'))
- try:
- from nbxmpp.stringprepare import nameprep
- server = nameprep.prepare(server)
- except UnicodeError:
- raise InvalidFormat(_('Invalid character in hostname.'))
- else:
- raise InvalidFormat(_('Server address required.'))
-
- if user is not None:
- if len(user) < 1 or len(user) > 1023:
- raise InvalidFormat(_('Username must be between 1 and 1023 chars'))
- try:
- from nbxmpp.stringprepare import nodeprep
- user = nodeprep.prepare(user)
- except UnicodeError:
- raise InvalidFormat(_('Invalid character in username.'))
- else:
- user = None
-
- if resource is not None:
- if len(resource) < 1 or len(resource) > 1023:
- raise InvalidFormat(_('Resource must be between 1 and 1023 chars'))
- try:
- from nbxmpp.stringprepare import resourceprep
- resource = resourceprep.prepare(resource)
- except UnicodeError:
- raise InvalidFormat(_('Invalid character in resource.'))
- else:
- resource = None
-
- if user:
- if resource:
- return '%s@%s/%s' % (user, server, resource)
- else:
- return '%s@%s' % (user, server)
- else:
- if resource:
- return '%s/%s' % (server, resource)
- else:
- return server
-
-def windowsify(s):
- if os.name == 'nt':
- return s.capitalize()
- return s
-
-def temp_failure_retry(func, *args, **kwargs):
- while True:
- try:
- return func(*args, **kwargs)
- except (os.error, IOError, select.error) as ex:
- if ex.errno == errno.EINTR:
- continue
- else:
- raise
-
-def get_uf_show(show, use_mnemonic = False):
- """
- Return a userfriendly string for dnd/xa/chat and make all strings
- translatable
-
- If use_mnemonic is True, it adds _ so GUI should call with True for
- accessibility issues
- """
- if show == 'dnd':
- if use_mnemonic:
- uf_show = _('_Busy')
- else:
- uf_show = _('Busy')
- elif show == 'xa':
- if use_mnemonic:
- uf_show = _('_Not Available')
- else:
- uf_show = _('Not Available')
- elif show == 'chat':
- if use_mnemonic:
- uf_show = _('_Free for Chat')
- else:
- uf_show = _('Free for Chat')
- elif show == 'online':
- if use_mnemonic:
- uf_show = Q_('?user status:_Available')
- else:
- uf_show = Q_('?user status:Available')
- elif show == 'connecting':
- uf_show = _('Connecting')
- elif show == 'away':
- if use_mnemonic:
- uf_show = _('A_way')
- else:
- uf_show = _('Away')
- elif show == 'offline':
- if use_mnemonic:
- uf_show = _('_Offline')
- else:
- uf_show = _('Offline')
- elif show == 'invisible':
- if use_mnemonic:
- uf_show = _('_Invisible')
- else:
- uf_show = _('Invisible')
- elif show == 'not in roster':
- uf_show = _('Not in Roster')
- elif show == 'requested':
- uf_show = Q_('?contact has status:Unknown')
- else:
- uf_show = Q_('?contact has status:Has errors')
- return uf_show
-
-def get_uf_sub(sub):
- if sub == 'none':
- uf_sub = Q_('?Subscription we already have:None')
- elif sub == 'to':
- uf_sub = _('To')
- elif sub == 'from':
- uf_sub = _('From')
- elif sub == 'both':
- uf_sub = _('Both')
- else:
- uf_sub = sub
-
- return uf_sub
-
-def get_uf_ask(ask):
- if ask is None:
- uf_ask = Q_('?Ask (for Subscription):None')
- elif ask == 'subscribe':
- uf_ask = _('Subscribe')
- else:
- uf_ask = ask
-
- return uf_ask
-
-def get_uf_role(role, plural = False):
- ''' plural determines if you get Moderators or Moderator'''
- if role == 'none':
- role_name = Q_('?Group Chat Contact Role:None')
- elif role == 'moderator':
- if plural:
- role_name = _('Moderators')
- else:
- role_name = _('Moderator')
- elif role == 'participant':
- if plural:
- role_name = _('Participants')
- else:
- role_name = _('Participant')
- elif role == 'visitor':
- if plural:
- role_name = _('Visitors')
- else:
- role_name = _('Visitor')
- return role_name
-
-def get_uf_affiliation(affiliation):
- '''Get a nice and translated affilition for muc'''
- if affiliation == 'none':
- affiliation_name = Q_('?Group Chat Contact Affiliation:None')
- elif affiliation == 'owner':
- affiliation_name = _('Owner')
- elif affiliation == 'admin':
- affiliation_name = _('Administrator')
- elif affiliation == 'member':
- affiliation_name = _('Member')
- else: # Argl ! An unknown affiliation !
- affiliation_name = affiliation.capitalize()
- return affiliation_name
-
-def get_sorted_keys(adict):
- keys = sorted(adict.keys())
- return keys
-
-def to_one_line(msg):
- msg = msg.replace('\\', '\\\\')
- msg = msg.replace('\n', '\\n')
- # s1 = 'test\ntest\\ntest'
- # s11 = s1.replace('\\', '\\\\')
- # s12 = s11.replace('\n', '\\n')
- # s12
- # 'test\\ntest\\\\ntest'
- return msg
-
-def from_one_line(msg):
- # (?<!\\) is a lookbehind assertion which asks anything but '\'
- # to match the regexp that follows it
-
- # So here match '\\n' but not if you have a '\' before that
- expr = re.compile(r'(?<!\\)\\n')
- msg = expr.sub('\n', msg)
- msg = msg.replace('\\\\', '\\')
- # s12 = 'test\\ntest\\\\ntest'
- # s13 = re.sub('\n', s12)
- # s14 s13.replace('\\\\', '\\')
- # s14
- # 'test\ntest\\ntest'
- return msg
-
-def get_uf_chatstate(chatstate):
- """
- Remove chatstate jargon and returns user friendly messages
- """
- if chatstate == 'active':
- return _('is paying attention to the conversation')
- elif chatstate == 'inactive':
- return _('is doing something else')
- elif chatstate == 'composing':
- return _('is composing a message…')
- elif chatstate == 'paused':
- #paused means he or she was composing but has stopped for a while
- return _('paused composing a message')
- elif chatstate == 'gone':
- return _('has closed the chat window or tab')
- return ''
-
-def is_in_path(command, return_abs_path=False):
- """
- Return True if 'command' is found in one of the directories in the user's
- path. If 'return_abs_path' is True, return the absolute path of the first
- found command instead. Return False otherwise and on errors
- """
- for directory in os.getenv('PATH').split(os.pathsep):
- try:
- if command in os.listdir(directory):
- if return_abs_path:
- return os.path.join(directory, command)
- else:
- return True
- except OSError:
- # If the user has non directories in his path
- pass
- return False
-
-def exec_command(command, use_shell=False, posix=True):
- """
- execute a command. if use_shell is True, we run the command as is it was
- typed in a console. So it may be dangerous if you are not sure about what
- is executed.
- """
- if use_shell:
- subprocess.Popen('%s &' % command, shell=True).wait()
- else:
- args = shlex.split(command, posix=posix)
- p = subprocess.Popen(args)
- gajim.thread_interface(p.wait)
-
-def build_command(executable, parameter):
- # we add to the parameter (can hold path with spaces)
- # "" so we have good parsing from shell
- parameter = parameter.replace('"', '\\"') # but first escape "
- command = '%s "%s"' % (executable, parameter)
- return command
-
-def get_file_path_from_dnd_dropped_uri(uri):
- path = urllib.parse.unquote(uri.decode('utf-8')) # escape special chars
- path = path.strip('\r\n\x00') # remove \r\n and NULL
- # get the path to file
- if re.match('^file:///[a-zA-Z]:/', path): # windows
- path = path[8:] # 8 is len('file:///')
- elif path.startswith('file://'): # nautilus, rox
- path = path[7:] # 7 is len('file://')
- elif path.startswith('file:'): # xffm
- path = path[5:] # 5 is len('file:')
- return path
-
-def from_xs_boolean_to_python_boolean(value):
- # this is xs:boolean so 'true', 'false', '1', '0'
- # convert those to True/False (python booleans)
- if value in ('1', 'true'):
- val = True
- else: # '0', 'false' or anything else
- val = False
-
- return val
-
-def get_xmpp_show(show):
- if show in ('online', 'offline'):
- return None
- return show
-
-def get_output_of_command(command):
- try:
- child_stdin, child_stdout = os.popen2(command)
- except ValueError:
- return None
-
- output = child_stdout.readlines()
- child_stdout.close()
- child_stdin.close()
-
- return output
-
-def sanitize_filename(filename):
- """
- Make sure the filename we will write does contain only acceptable and latin
- characters, and is not too long (in that case hash it)
- """
- # 48 is the limit
- if len(filename) > 48:
- hash = hashlib.md5(filename.encode('utf-8'))
- filename = base64.b64encode(hash.digest()).decode('utf-8')
-
- # make it latin chars only
- filename = punycode_encode(filename).decode('utf-8')
- filename = filename.replace('/', '_')
- if os.name == 'nt':
- filename = filename.replace('?', '_').replace(':', '_')\
- .replace('\\', '_').replace('"', "'").replace('|', '_')\
- .replace('*', '_').replace('<', '_').replace('>', '_')
-
- return filename
-
-def reduce_chars_newlines(text, max_chars = 0, max_lines = 0):
- """
- Cut the chars after 'max_chars' on each line and show only the first
- 'max_lines'
-
- If any of the params is not present (None or 0) the action on it is not
- performed
- """
- def _cut_if_long(string):
- if len(string) > max_chars:
- string = string[:max_chars - 3] + '…'
- return string
-
- if max_lines == 0:
- lines = text.split('\n')
- else:
- lines = text.split('\n', max_lines)[:max_lines]
- if max_chars > 0:
- if lines:
- lines = [_cut_if_long(e) for e in lines]
- if lines:
- reduced_text = '\n'.join(lines)
- if reduced_text != text:
- reduced_text += '…'
- else:
- reduced_text = ''
- return reduced_text
-
-def get_account_status(account):
- status = reduce_chars_newlines(account['status_line'], 100, 1)
- return status
-
-def get_avatar_path(prefix):
- """
- Return the filename of the avatar, distinguishes between user- and contact-
- provided one. Return None if no avatar was found at all. prefix is the path
- to the requested avatar just before the ".png" or ".jpeg"
- """
- # First, scan for a local, user-set avatar
- for type_ in ('jpeg', 'png'):
- file_ = prefix + '_local.' + type_
- if os.path.exists(file_):
- return file_
- # If none available, scan for a contact-provided avatar
- for type_ in ('jpeg', 'png'):
- file_ = prefix + '.' + type_
- if os.path.exists(file_):
- return file_
- return None
-
-def datetime_tuple(timestamp):
- """
- Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S
-
- Because of various datetime formats are used the following exceptions
- are handled:
- - Optional milliseconds appened to the string are removed
- - Optional Z (that means UTC) appened to the string are removed
- - XEP-082 datetime strings have all '-' cahrs removed to meet
- the above format.
- """
- date, tim = timestamp.split('T', 1)
- date = date.replace('-', '')
- tim = tim.replace('z', '')
- tim = tim.replace('Z', '')
- zone = None
- if '+' in tim:
- sign = -1
- tim, zone = tim.split('+', 1)
- if '-' in tim:
- sign = 1
- tim, zone = tim.split('-', 1)
- tim = tim.split('.')[0]
- tim = time.strptime(date + 'T' + tim, '%Y%m%dT%H:%M:%S')
- if zone:
- zone = zone.replace(':', '')
- tim = datetime.datetime.fromtimestamp(time.mktime(tim))
- if len(zone) > 2:
- zone = time.strptime(zone, '%H%M')
- else:
- zone = time.strptime(zone, '%H')
- zone = datetime.timedelta(hours=zone.tm_hour, minutes=zone.tm_min)
- tim += zone * sign
- tim = tim.timetuple()
- return tim
-
-from common import gajim
-if gajim.HAVE_PYCURL:
- import pycurl
- from io import StringIO
-
-def convert_bytes(string):
- suffix = ''
- # IEC standard says KiB = 1024 bytes KB = 1000 bytes
- # but do we use the standard?
- use_kib_mib = gajim.config.get('use_kib_mib')
- align = 1024.
- bytes_ = float(string)
- if bytes_ >= align:
- bytes_ = round(bytes_/align, 1)
- if bytes_ >= align:
- bytes_ = round(bytes_/align, 1)
- if bytes_ >= align:
- bytes_ = round(bytes_/align, 1)
- if use_kib_mib:
- #GiB means gibibyte
- suffix = _('%s GiB')
- else:
- #GB means gigabyte
- suffix = _('%s GB')
- else:
- if use_kib_mib:
- #MiB means mibibyte
- suffix = _('%s MiB')
- else:
- #MB means megabyte
- suffix = _('%s MB')
- else:
- if use_kib_mib:
- #KiB means kibibyte
- suffix = _('%s KiB')
- else:
- #KB means kilo bytes
- suffix = _('%s KB')
- else:
- #B means bytes
- suffix = _('%s B')
- return suffix % str(bytes_)
-
-def get_contact_dict_for_account(account):
- """
- Create a dict of jid, nick -> contact with all contacts of account.
-
- Can be used for completion lists
- """
- contacts_dict = {}
- for jid in gajim.contacts.get_jid_list(account):
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- contacts_dict[jid] = contact
- name = contact.name
- if name in contacts_dict:
- contact1 = contacts_dict[name]
- del contacts_dict[name]
- contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1
- contacts_dict['%s (%s)' % (name, jid)] = contact
- elif contact.name:
- if contact.name == gajim.get_nick_from_jid(jid):
- del contacts_dict[jid]
- contacts_dict[name] = contact
- return contacts_dict
-
-def launch_browser_mailer(kind, uri):
- # kind = 'url' or 'mail'
- if kind == 'url' and uri.startswith('file://'):
- launch_file_manager(uri)
- return
- if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'):
- uri = 'mailto:' + uri
-
- if kind == 'url' and uri.startswith('www.'):
- uri = 'http://' + uri
-
- if not gajim.config.get('autodetect_browser_mailer'):
- if kind == 'url':
- command = gajim.config.get('custombrowser')
- elif kind in ('mail', 'sth_at_sth'):
- command = gajim.config.get('custommailapp')
- if command == '': # if no app is configured
- return
-
- command = build_command(command, uri)
- try:
- exec_command(command)
- except Exception:
- pass
-
- else:
- webbrowser.open(uri)
-
-
-def launch_file_manager(path_to_open):
- if not path_to_open.startswith('file://'):
- uri = 'file://' + path_to_open
- if os.name == 'nt':
- try:
- os.startfile(path_to_open) # if pywin32 is installed we open
- except Exception:
- pass
- else:
- if not gajim.config.get('autodetect_browser_mailer'):
- command = gajim.config.get('custom_file_manager')
- if command == '': # if no app is configured
- return
- else:
- command = 'xdg-open'
- command = build_command(command, path_to_open)
- try:
- exec_command(command)
- except Exception:
- pass
-
-def play_sound(event):
- if not gajim.config.get('sounds_on'):
- return
- path_to_soundfile = gajim.config.get_per('soundevents', event, 'path')
- play_sound_file(path_to_soundfile)
-
-def check_soundfile_path(file_, dirs=(gajim.gajimpaths.data_root,
-gajim.DATA_DIR)):
- """
- Check if the sound file exists
-
- :param file_: the file to check, absolute or relative to 'dirs' path
- :param dirs: list of knows paths to fallback if the file doesn't exists
- (eg: ~/.gajim/sounds/, DATADIR/sounds...).
- :return the path to file or None if it doesn't exists.
- """
- if not file_:
- return None
- elif os.path.exists(file_):
- return file_
-
- for d in dirs:
- d = os.path.join(d, 'sounds', file_)
- if os.path.exists(d):
- return d
- return None
-
-def strip_soundfile_path(file_, dirs=(gajim.gajimpaths.data_root,
-gajim.DATA_DIR), abs=True):
- """
- Remove knowns paths from a sound file
-
- Filechooser returns absolute path. If path is a known fallback path, we remove it.
- So config have no hardcoded path to DATA_DIR and text in textfield is shorther.
- param: file_: the filename to strip.
- param: dirs: list of knowns paths from which the filename should be stripped.
- param: abs: force absolute path on dirs
- """
- if not file_:
- return None
-
- name = os.path.basename(file_)
- for d in dirs:
- d = os.path.join(d, 'sounds', name)
- if abs:
- d = os.path.abspath(d)
- if file_ == d:
- return name
- return file_
-
-def play_sound_file(path_to_soundfile):
- if path_to_soundfile == 'beep':
- exec_command('beep')
- return
- path_to_soundfile = check_soundfile_path(path_to_soundfile)
- if path_to_soundfile is None:
- return
- elif sys.platform == 'win32' and HAS_WINSOUND:
- try:
- winsound.PlaySound(path_to_soundfile,
- winsound.SND_FILENAME|winsound.SND_ASYNC)
- except Exception:
- log.exception('Sound Playback Error')
- elif sys.platform == 'linux':
- if gajim.config.get('soundplayer') == '':
- def _oss_play():
- sndfile = wave.open(path_to_soundfile, 'rb')
- (nc, sw, fr, nf, comptype, compname) = sndfile.getparams()
- dev = oss.open('/dev/dsp', 'w')
- dev.setparameters(sw * 8, nc, fr)
- dev.write(sndfile.readframes(nf))
- sndfile.close()
- dev.close()
- gajim.thread_interface(_oss_play)
- return
- player = gajim.config.get('soundplayer')
- command = build_command(player, path_to_soundfile)
- exec_command(command)
-
-def get_global_show():
- maxi = 0
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- connected = gajim.connections[account].connected
- if connected > maxi:
- maxi = connected
- return gajim.SHOW_LIST[maxi]
-
-def get_global_status():
- maxi = 0
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- connected = gajim.connections[account].connected
- if connected > maxi:
- maxi = connected
- status = gajim.connections[account].status
- return status
-
-
-def statuses_unified():
- """
- Test if all statuses are the same
- """
- reference = None
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- if reference is None:
- reference = gajim.connections[account].connected
- elif reference != gajim.connections[account].connected:
- return False
- return True
-
-def get_icon_name_to_show(contact, account = None):
- """
- Get the icon name to show in online, away, requested, etc
- """
- if account and gajim.events.get_nb_roster_events(account, contact.jid):
- return 'event'
- if account and gajim.events.get_nb_roster_events(account,
- contact.get_full_jid()):
- return 'event'
- if account and account in gajim.interface.minimized_controls and \
- contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\
- minimized_controls[account][contact.jid].get_nb_unread_pm() > 0:
- return 'event'
- if account and contact.jid in gajim.gc_connected[account]:
- if gajim.gc_connected[account][contact.jid]:
- return 'muc_active'
- else:
- return 'muc_inactive'
- if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
- return contact.show
- if contact.sub in ('both', 'to'):
- return contact.show
- if contact.ask == 'subscribe':
- return 'requested'
- transport = gajim.get_transport_name_from_jid(contact.jid)
- if transport:
- return contact.show
- if contact.show in gajim.SHOW_LIST:
- return contact.show
- return 'not in roster'
-
-def get_full_jid_from_iq(iq_obj):
- """
- Return the full jid (with resource) from an iq
- """
- return parse_jid(str(iq_obj.getFrom()))
-
-def get_jid_from_iq(iq_obj):
- """
- Return the jid (without resource) from an iq
- """
- jid = get_full_jid_from_iq(iq_obj)
- return gajim.get_jid_without_resource(jid)
-
-def get_auth_sha(sid, initiator, target):
- """
- Return sha of sid + initiator + target used for proxy auth
- """
- return hashlib.sha1(("%s%s%s" % (sid, initiator, target)).encode('utf-8')).\
- hexdigest()
-
-def remove_invalid_xml_chars(string):
- if string:
- string = re.sub(gajim.interface.invalid_XML_chars_re, '', string)
- return string
-
-distro_info = {
- 'Arch Linux': '/etc/arch-release',
- 'Aurox Linux': '/etc/aurox-release',
- 'Conectiva Linux': '/etc/conectiva-release',
- 'CRUX': '/usr/bin/crux',
- 'Debian GNU/Linux': '/etc/debian_version',
- 'Fedora Linux': '/etc/fedora-release',
- 'Gentoo Linux': '/etc/gentoo-release',
- 'Linux from Scratch': '/etc/lfs-release',
- 'Mandrake Linux': '/etc/mandrake-release',
- 'Slackware Linux': '/etc/slackware-version',
- 'Solaris/Sparc': '/etc/release',
- 'Source Mage': '/etc/sourcemage_version',
- 'SUSE Linux': '/etc/SuSE-release',
- 'Sun JDS': '/etc/sun-release',
- 'PLD Linux': '/etc/pld-release',
- 'Yellow Dog Linux': '/etc/yellowdog-release',
- 'AgiliaLinux': '/etc/agilialinux-version',
- # many distros use the /etc/redhat-release for compatibility
- # so Redhat is the last
- 'Redhat Linux': '/etc/redhat-release'
-}
-
-def get_random_string_16():
- """
- Create random string of length 16
- """
- rng = list(range(65, 90))
- rng.extend(range(48, 57))
- char_sequence = [chr(e) for e in rng]
- from random import sample
- return ''.join(sample(char_sequence, 16))
-
-def get_os_info():
- if gajim.os_info:
- return gajim.os_info
- if os.name == 'nt':
- # platform.release() seems to return the name of the windows
- ver = sys.getwindowsversion()
- ver_format = ver[3], ver[0], ver[1]
- win_version = {
- (1, 4, 0): '95',
- (1, 4, 10): '98',
- (1, 4, 90): 'ME',
- (2, 4, 0): 'NT',
- (2, 5, 0): '2000',
- (2, 5, 1): 'XP',
- (2, 5, 2): '2003',
- (2, 6, 0): 'Vista',
- (2, 6, 1): '7',
- }
- if ver_format in win_version:
- os_info = 'Windows' + ' ' + win_version[ver_format]
- else:
- os_info = 'Windows'
- gajim.os_info = os_info
- return os_info
- elif os.name == 'posix':
- executable = 'lsb_release'
- params = ' --description --codename --release --short'
- full_path_to_executable = is_in_path(executable, return_abs_path = True)
- if full_path_to_executable:
- command = executable + params
- p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, close_fds=True)
- p.wait()
- output = temp_failure_retry(p.stdout.readline).strip()
- # some distros put n/a in places, so remove those
- output = output.decode('utf-8').replace('n/a', '').replace('N/A', '')
- gajim.os_info = output
- p.stdout.close()
- p.stdin.close()
- return output
-
- # lsb_release executable not available, so parse files
- for distro_name in distro_info:
- path_to_file = distro_info[distro_name]
- if os.path.exists(path_to_file):
- if os.access(path_to_file, os.X_OK):
- # the file is executable (f.e. CRUX)
- # yes, then run it and get the first line of output.
- text = get_output_of_command(path_to_file)[0]
- else:
- fd = open(path_to_file)
- text = fd.readline().strip() # get only first line
- fd.close()
- if path_to_file.endswith('version'):
- # sourcemage_version and slackware-version files
- # have all the info we need (name and version of distro)
- if not os.path.basename(path_to_file).startswith(
- 'sourcemage') or not\
- os.path.basename(path_to_file).startswith('slackware'):
- text = distro_name + ' ' + text
- elif path_to_file.endswith('aurox-release') or \
- path_to_file.endswith('arch-release'):
- # file doesn't have version
- text = distro_name
- elif path_to_file.endswith('lfs-release'):
- # file just has version
- text = distro_name + ' ' + text
- os_info = text.replace('\n', '')
- gajim.os_info = os_info
- return os_info
-
- # our last chance, ask uname and strip it
- uname_output = get_output_of_command('uname -sr')
- if uname_output is not None:
- os_info = uname_output[0] # only first line
- gajim.os_info = os_info
- return os_info
- os_info = 'N/A'
- gajim.os_info = os_info
- return os_info
-
-
-def allow_showing_notification(account, type_='notify_on_new_message',
-is_first_message=True):
- """
- Is it allowed to show nofication?
-
- Check OUR status and if we allow notifications for that status type is the
- option that need to be True e.g.: notify_on_signing is_first_message: set it
- to false when it's not the first message
- """
- if type_ and (not gajim.config.get(type_) or not is_first_message):
- return False
- if gajim.config.get('autopopupaway'): # always show notification
- return True
- if gajim.connections[account].connected in (2, 3): # we're online or chat
- return True
- return False
-
-def allow_popup_window(account):
- """
- Is it allowed to popup windows?
- """
- autopopup = gajim.config.get('autopopup')
- autopopupaway = gajim.config.get('autopopupaway')
- if autopopup and (autopopupaway or \
- gajim.connections[account].connected in (2, 3)): # we're online or chat
- return True
- return False
-
-def allow_sound_notification(account, sound_event):
- if gajim.config.get('sounddnd') or gajim.connections[account].connected != \
- gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents',
- sound_event, 'enabled'):
- return True
- return False
-
-def get_chat_control(account, contact):
- full_jid_with_resource = contact.jid
- if contact.resource:
- full_jid_with_resource += '/' + contact.resource
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- account, contact.jid)
-
- # Look for a chat control that has the given resource, or default to
- # one without resource
- ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
- account)
-
- if ctrl:
- return ctrl
- elif highest_contact and highest_contact.resource and \
- contact.resource != highest_contact.resource:
- return None
- else:
- # unknown contact or offline message
- return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
-
-def get_notification_icon_tooltip_dict():
- """
- Return a dict of the form {acct: {'show': show, 'message': message,
- 'event_lines': [list of text lines to show in tooltip]}
- """
- # How many events must there be before they're shown summarized, not per-user
- max_ungrouped_events = 10
-
- accounts = get_accounts_info()
-
- # Gather events. (With accounts, when there are more.)
- for account in accounts:
- account_name = account['name']
- account['event_lines'] = []
- # Gather events per-account
- pending_events = gajim.events.get_events(account = account_name)
- messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0
- for jid in pending_events:
- for event in pending_events[jid]:
- if event.type_.count('file') > 0:
- # This is a non-messagee event.
- messages[jid] = non_messages.get(jid, 0) + 1
- total_non_messages = total_non_messages + 1
- else:
- # This is a message.
- messages[jid] = messages.get(jid, 0) + 1
- total_messages = total_messages + 1
- # Display unread messages numbers, if any
- if total_messages > 0:
- if total_messages > max_ungrouped_events:
- text = ngettext(
- '%d message pending',
- '%d messages pending',
- total_messages, total_messages, total_messages)
- account['event_lines'].append(text)
- else:
- for jid in messages.keys():
- text = ngettext(
- '%d message pending',
- '%d messages pending',
- messages[jid], messages[jid], messages[jid])
- contact = gajim.contacts.get_first_contact_from_jid(
- account['name'], jid)
- text += ' '
- if jid in gajim.gc_connected[account['name']]:
- text += _('from room %s') % (jid)
- elif contact:
- name = contact.get_shown_name()
- text += _('from user %s') % (name)
- else:
- text += _('from %s') % (jid)
- account['event_lines'].append(text)
-
- # Display unseen events numbers, if any
- if total_non_messages > 0:
- if total_non_messages > max_ungrouped_events:
- text = ngettext(
- '%d event pending',
- '%d events pending',
- total_non_messages, total_non_messages,total_non_messages)
- account['event_lines'].append(text)
- else:
- for jid in non_messages.keys():
- text = ngettext('%d event pending', '%d events pending',
- non_messages[jid], non_messages[jid], non_messages[jid])
- text += ' ' + _('from user %s') % (jid)
- account[account]['event_lines'].append(text)
-
- return accounts
-
-def get_notification_icon_tooltip_text():
- text = None
- # How many events must there be before they're shown summarized, not per-user
- # max_ungrouped_events = 10
- # Character which should be used to indent in the tooltip.
- indent_with = ' '
-
- accounts = get_notification_icon_tooltip_dict()
-
- if len(accounts) == 0:
- # No configured account
- return _('Gajim')
-
- # at least one account present
-
- # Is there more that one account?
- if len(accounts) == 1:
- show_more_accounts = False
- else:
- show_more_accounts = True
-
- # If there is only one account, its status is shown on the first line.
- if show_more_accounts:
- text = _('Gajim')
- else:
- text = _('Gajim - %s') % (get_account_status(accounts[0]))
-
- # Gather and display events. (With accounts, when there are more.)
- for account in accounts:
- account_name = account['name']
- # Set account status, if not set above
- if (show_more_accounts):
- message = '\n' + indent_with + ' %s - %s'
- text += message % (account_name, get_account_status(account))
- # Account list shown, messages need to be indented more
- indent_how = 2
- else:
- # If no account list is shown, messages could have default indenting.
- indent_how = 1
- for line in account['event_lines']:
- text += '\n' + indent_with * indent_how + ' '
- text += line
- return text
-
-def get_accounts_info():
- """
- Helper for notification icon tooltip
- """
- accounts = []
- accounts_list = sorted(gajim.contacts.get_accounts())
- for account in accounts_list:
- status_idx = gajim.connections[account].connected
- # uncomment the following to hide offline accounts
- # if status_idx == 0: continue
- status = gajim.SHOW_LIST[status_idx]
- message = gajim.connections[account].status
- single_line = get_uf_show(status)
- if message is None:
- message = ''
- else:
- message = message.strip()
- if message != '':
- single_line += ': ' + message
- accounts.append({'name': account, 'status_line': single_line,
- 'show': status, 'message': message})
- return accounts
-
-
-def get_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)):
- return os.path.join(gajim.DATA_DIR, 'iconsets', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)):
- return os.path.join(gajim.MY_ICONSETS_PATH, iconset)
-
-def get_mood_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)):
- return os.path.join(gajim.DATA_DIR, 'moods', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)):
- return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)
-
-def get_activity_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)):
- return os.path.join(gajim.DATA_DIR, 'activities', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH,
- iconset)):
- return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset)
-
-def get_transport_path(transport):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports',
- transport)):
- return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport)
- elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports',
- transport)):
- return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport)
- # No transport folder found, use default jabber one
- return get_iconset_path(gajim.config.get('iconset'))
-
-def prepare_and_validate_gpg_keyID(account, jid, keyID):
- """
- Return an eight char long keyID that can be used with for GPG encryption
- with this contact
-
- If the given keyID is None, return UNKNOWN; if the key does not match the
- assigned key XXXXXXXXMISMATCH is returned. If the key is trusted and not yet
- assigned, assign it.
- """
- if gajim.connections[account].USE_GPG:
- if keyID and len(keyID) == 16:
- keyID = keyID[8:]
-
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
-
- if jid in attached_keys and keyID:
- attachedkeyID = attached_keys[attached_keys.index(jid) + 1]
- if attachedkeyID != keyID:
- # Get signing subkeys for the attached key
- subkeys = []
- for key in gajim.connections[account].gpg.list_keys():
- if key['keyid'][8:] == attachedkeyID:
- subkeys = [subkey[0][8:] for subkey in key['subkeys'] \
- if subkey[1] == 's']
- break
-
- if keyID not in subkeys:
- # Mismatch! Another gpg key was expected
- keyID += 'MISMATCH'
- elif jid in attached_keys:
- # An unsigned presence, just use the assigned key
- keyID = attached_keys[attached_keys.index(jid) + 1]
- elif keyID:
- full_key = gajim.connections[account].ask_gpg_keys(keyID=keyID)
- # Assign the corresponding key, if we have it in our keyring
- if full_key:
- for u in gajim.contacts.get_contacts(account, jid):
- u.keyID = keyID
- keys_str = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys')
- keys_str += jid + ' ' + keyID + ' '
- gajim.config.set_per('accounts', account, 'attached_gpg_keys',
- keys_str)
- elif keyID is None:
- keyID = 'UNKNOWN'
- return keyID
-
-def update_optional_features(account = None):
- import nbxmpp
- if account:
- accounts = [account]
- else:
- accounts = [a for a in gajim.connections]
- for a in accounts:
- gajim.gajim_optional_features[a] = []
- if gajim.config.get_per('accounts', a, 'subscribe_mood'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_MOOD + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_activity'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_ACTIVITY + \
- '+notify')
- if gajim.config.get_per('accounts', a, 'publish_tune'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_TUNE)
- if gajim.config.get_per('accounts', a, 'publish_location'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_LOCATION)
- if gajim.config.get_per('accounts', a, 'subscribe_tune'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_TUNE + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_nick'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_NICK + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_location'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_LOCATION + \
- '+notify')
- if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
- gajim.gajim_optional_features[a].append(nbxmpp.NS_CHATSTATES)
- if not gajim.config.get('ignore_incoming_xhtml'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_XHTML_IM)
- if gajim.HAVE_PYCRYPTO \
- and gajim.config.get_per('accounts', a, 'enable_esessions'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_ESESSION)
- if gajim.config.get_per('accounts', a, 'answer_receipts'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_RECEIPTS)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE)
- if gajim.HAVE_FARSTREAM:
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_RTP)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_RTP_AUDIO)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_RTP_VIDEO)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_ICE_UDP)
- gajim.gajim_optional_features[a].append(
- nbxmpp.NS_JINGLE_FILE_TRANSFER_5)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_XTLS)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_BYTESTREAM)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_IBB)
- gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity],
- gajim.gajim_common_features + gajim.gajim_optional_features[a])
- # re-send presence with new hash
- connected = gajim.connections[a].connected
- if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
- gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
- gajim.connections[a].status)
-
-def jid_is_blocked(account, jid):
- return ((jid in gajim.connections[account].blocked_contacts) or \
- gajim.connections[account].blocked_all)
-
-def group_is_blocked(account, group):
- return ((group in gajim.connections[account].blocked_groups) or \
- gajim.connections[account].blocked_all)
-
-def get_subscription_request_msg(account=None):
- s = gajim.config.get_per('accounts', account, 'subscription_request_msg')
- if s:
- return s
- s = _('I would like to add you to my contact list.')
- if account:
- s = _('Hello, I am $name.') + ' ' + s
- our_jid = gajim.get_jid_from_account(account)
- vcard = gajim.connections[account].get_cached_vcard(our_jid)
- name = ''
- if vcard:
- if 'N' in vcard:
- if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']:
- name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY']
- if not name and 'FN' in vcard:
- name = vcard['FN']
- nick = gajim.nicks[account]
- if name and nick:
- name += ' (%s)' % nick
- elif nick:
- name = nick
- s = Template(s).safe_substitute({'name': name})
- return s
-
-def replace_dataform_media(form, stanza):
- import nbxmpp
- found = False
- for field in form.getTags('field'):
- for media in field.getTags('media'):
- for uri in media.getTags('uri'):
- uri_data = uri.getData()
- if uri_data.startswith('cid:'):
- uri_data = uri_data[4:]
- for data in stanza.getTags('data', namespace=nbxmpp.NS_BOB):
- if data.getAttr('cid') == uri_data:
- uri.setData(data.getData())
- found = True
- return found
-
-def get_proxy_info(account):
- p = gajim.config.get_per('accounts', account, 'proxy')
- if not p:
- if gajim.config.get_per('accounts', account, 'use_env_http_proxy'):
- try:
- try:
- env_http_proxy = os.environ['HTTP_PROXY']
- except Exception:
- env_http_proxy = os.environ['http_proxy']
- env_http_proxy = env_http_proxy.strip('"')
- # Dispose of the http:// prefix
- env_http_proxy = env_http_proxy.split('://')[-1]
- env_http_proxy = env_http_proxy.split('@')
-
- if len(env_http_proxy) == 2:
- login = env_http_proxy[0].split(':')
- addr = env_http_proxy[1].split(':')
- else:
- login = ['', '']
- addr = env_http_proxy[0].split(':')
-
- proxy = {'host': addr[0], 'type' : 'http', 'user':login[0]}
-
- if len(addr) == 2:
- proxy['port'] = addr[1]
- else:
- proxy['port'] = 3128
-
- if len(login) == 2:
- proxy['pass'] = login[1]
- proxy['useauth'] = True
- else:
- proxy['pass'] = ''
- return proxy
-
- except Exception:
- proxy = None
- p = gajim.config.get('global_proxy')
- if p and p in gajim.config.get_per('proxies'):
- proxy = {}
- proxyptr = gajim.config.get_per('proxies', p)
- if not proxyptr:
- return proxy
- for key in proxyptr.keys():
- proxy[key] = proxyptr[key]
- return proxy
-
-def _get_img_direct(attrs):
- """
- Download an image. This function should be launched in a separated thread.
- """
- mem = b''
- alt = ''
- max_size = 2*1024*1024
- if 'max_size' in attrs:
- max_size = attrs['max_size']
- # Wait maximum 10s for connection
- socket.setdefaulttimeout(10)
- try:
- req = urllib.request.Request(attrs['src'])
- req.add_header('User-Agent', 'Gajim ' + gajim.version)
- f = urllib.request.urlopen(req)
- except Exception as ex:
- log.debug('Error loading image %s ' % attrs['src'] + str(ex))
- pixbuf = None
- alt = attrs.get('alt', 'Broken image')
- else:
- # Wait 2s between each byte
- try:
- f.fp._sock.fp._sock.settimeout(2)
- except Exception:
- pass
- # On a slow internet connection with ~1000kbps you need ~10 seconds for 1 MB
- deadline = time.time() + (10 * (max_size / 1048576))
- while True:
- if time.time() > deadline:
- log.debug('Timeout loading image %s ' % attrs['src'])
- mem = ''
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Timeout loading image')
- break
- try:
- temp = f.read(100)
- except socket.timeout as ex:
- log.debug('Timeout loading image %s ' % attrs['src'] + str(ex))
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Timeout loading image')
- break
- if temp:
- mem += temp
- else:
- break
- if len(mem) > max_size:
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Image is too big')
- break
- f.close()
- return (mem, alt)
-
-def _get_img_proxy(attrs, proxy):
- """
- Download an image through a proxy. This function should be launched in a
- separated thread.
- """
- if not gajim.HAVE_PYCURL:
- return '', _('PyCURL is not installed')
- mem, alt, max_size = '', '', 2*1024*1024
- if 'max_size' in attrs:
- max_size = attrs['max_size']
- try:
- b = StringIO()
- c = pycurl.Curl()
- c.setopt(pycurl.URL, attrs['src'].encode('utf-8'))
- c.setopt(pycurl.FOLLOWLOCATION, 1)
- # Wait maximum 10s for connection
- c.setopt(pycurl.CONNECTTIMEOUT, 10)
- # On a slow internet connection with ~1000kbps you need ~10 seconds for 1 MB
- c.setopt(pycurl.TIMEOUT, 10 * (max_size / 1048576))
- c.setopt(pycurl.MAXFILESIZE, max_size)
- c.setopt(pycurl.WRITEFUNCTION, b.write)
- c.setopt(pycurl.USERAGENT, 'Gajim ' + gajim.version)
- # set proxy
- c.setopt(pycurl.PROXY, proxy['host'].encode('utf-8'))
- c.setopt(pycurl.PROXYPORT, proxy['port'])
- if proxy['useauth']:
- c.setopt(pycurl.PROXYUSERPWD, proxy['user'].encode('utf-8')\
- + ':' + proxy['pass'].encode('utf-8'))
- c.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
- if proxy['type'] == 'http':
- c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
- elif proxy['type'] == 'socks5':
- c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
- x = c.perform()
- c.close()
- t = b.getvalue()
- return (t, attrs.get('alt', ''))
- except pycurl.error as ex:
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- if ex.errno == pycurl.E_FILESIZE_EXCEEDED:
- alt += _('Image is too big')
- elif ex.errno == pycurl.E_OPERATION_TIMEOUTED:
- alt += _('Timeout loading image')
- else:
- alt += _('Error loading image')
- except Exception as ex:
- log.debug('Error loading image %s ' % attrs['src'] + str(ex))
- pixbuf = None
- alt = attrs.get('alt', 'Broken image')
- return ('', alt)
-
-def download_image(account, attrs):
- proxy = get_proxy_info(account)
- if proxy and proxy['type'] in ('http', 'socks5'):
- return _get_img_proxy(attrs, proxy)
- return _get_img_direct(attrs)
diff --git a/src/common/i18n.py b/src/common/i18n.py
deleted file mode 100644
index 01ba2be89..000000000
--- a/src/common/i18n.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/i18n.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2004 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2009 Benjamin Richter <br AT waldteufel-online.net>
-##
-## 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/>.
-##
-
-import locale
-import gettext
-import os
-from common import defs
-import unicodedata
-
-# May be changed after GTK is imported
-direction_mark = '\u200E'
-
-def paragraph_direction_mark(text):
- """
- Determine paragraph writing direction according to
- http://www.unicode.org/reports/tr9/#The_Paragraph_Level
-
- Returns either Unicode LTR mark or RTL mark.
- """
- for char in text:
- bidi = unicodedata.bidirectional(char)
- if bidi == 'L':
- return '\u200E'
- elif bidi == 'AL' or bidi == 'R':
- return '\u200F'
-
- return '\u200E'
-
-APP = 'gajim'
-DIR = defs.localedir
-
-# set '' so each part of the locale that should be modified is set
-# according to the environment variables
-locale.setlocale(locale.LC_ALL, '')
-
-## For windows: set, if needed, a value in LANG environmental variable ##
-if os.name == 'nt':
- lang = os.getenv('LANG')
- if lang is None:
- default_lang = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
- if default_lang:
- lang = default_lang
-
- if lang:
- os.environ['LANG'] = lang
-
-gettext.install(APP, DIR)
-if gettext._translations:
- _translation = list(gettext._translations.values())[0]
-else:
- _translation = gettext.NullTranslations()
-
-def Q_(text):
- """
- Translate the given text, optionally qualified with a special
- construction, which will help translators to disambiguate between
- same terms, but in different contexts.
-
- When translated text is returned - this rudimentary construction
- will be stripped off, if it's present.
-
- Here is the construction to use:
- Q_("?vcard:Unknown")
-
- Everything between ? and : - is the qualifier to convey the context
- to the translators. Everything after : - is the text itself.
- """
- text = _(text)
- if text.startswith('?'):
- qualifier, text = text.split(':', 1)
- return text
-
-def ngettext(s_sing, s_plural, n, replace_sing = None, replace_plural = None):
- """
- Use as:
- i18n.ngettext('leave room %s', 'leave rooms %s', len(rooms), 'a', 'a, b, c')
-
- In other words this is a hack to ngettext() to support %s %d etc..
- """
- text = _translation.ngettext(s_sing, s_plural, n)
- if n == 1 and replace_sing is not None:
- text = text % replace_sing
- elif n > 1 and replace_plural is not None:
- text = text % replace_plural
- return text
diff --git a/src/common/idle.py b/src/common/idle.py
deleted file mode 100644
index 9e475c47d..000000000
--- a/src/common/idle.py
+++ /dev/null
@@ -1,99 +0,0 @@
-## src/common/idle.py
-##
-## (C) 2008 Thorsten P. 'dGhvcnN0ZW5wIEFUIHltYWlsIGNvbQ==\n'.decode("base64")
-##
-## 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/>.
-
-import ctypes
-import ctypes.util
-
-class XScreenSaverInfo(ctypes.Structure):
- _fields_ = [
- ('window', ctypes.c_ulong),
- ('state', ctypes.c_int),
- ('kind', ctypes.c_int),
- ('til_or_since', ctypes.c_ulong),
- ('idle', ctypes.c_ulong),
- ('eventMask', ctypes.c_ulong)
- ]
-XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo)
-
-display_p = ctypes.c_void_p
-xid = ctypes.c_ulong
-c_int_p = ctypes.POINTER(ctypes.c_int)
-
-try:
- libX11path = ctypes.util.find_library('X11')
- if libX11path == None:
- raise OSError('libX11 could not be found.')
- libX11 = ctypes.cdll.LoadLibrary(libX11path)
- libX11.XOpenDisplay.restype = display_p
- libX11.XOpenDisplay.argtypes = ctypes.c_char_p,
- libX11.XDefaultRootWindow.restype = xid
- libX11.XDefaultRootWindow.argtypes = display_p,
-
- libXsspath = ctypes.util.find_library('Xss')
- if libXsspath == None:
- raise OSError('libXss could not be found.')
- libXss = ctypes.cdll.LoadLibrary(libXsspath)
- libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p
- libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p
- libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p)
-
- dpy_p = libX11.XOpenDisplay(None)
- if dpy_p == None:
- raise OSError('Could not open X Display.')
-
- _event_basep = ctypes.c_int()
- _error_basep = ctypes.c_int()
- if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep),
- ctypes.byref(_error_basep)) == 0:
- raise OSError('XScreenSaver Extension not available on display.')
-
- xss_info_p = libXss.XScreenSaverAllocInfo()
- if xss_info_p == None:
- raise OSError('XScreenSaverAllocInfo: Out of Memory.')
-
- rootwindow = libX11.XDefaultRootWindow(dpy_p)
- xss_available = True
-except OSError:
- # Logging?
- xss_available = False
-
-def getIdleSec():
- global xss_available
- """
- Return the idle time in seconds
- """
- if not xss_available:
- return 0
- if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0:
- return 0
- else:
- return int(xss_info_p.contents.idle) / 1000
-
-def close():
- global xss_available
- if xss_available:
- libX11.XFree(xss_info_p)
- libX11.XCloseDisplay(dpy_p)
- xss_available = False
-
-if __name__ == '__main__':
- import time
- time.sleep(2.1)
- print(getIdleSec())
- close()
- print(getIdleSec())
diff --git a/src/common/jingle.py b/src/common/jingle.py
deleted file mode 100644
index 6903679b9..000000000
--- a/src/common/jingle.py
+++ /dev/null
@@ -1,233 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-"""
-Handles the jingle signalling protocol
-"""
-
-#TODO:
-# * things in XEP 0176, including:
-# - http://xmpp.org/extensions/xep-0176.html#protocol-restarts
-# - http://xmpp.org/extensions/xep-0176.html#fallback
-# * XEP 0177 (raw udp)
-
-# * UI:
-# - make state and codec informations available to the user
-# - video integration
-# * config:
-# - codecs
-
-import logging
-
-import nbxmpp
-from common import helpers
-from common import gajim
-
-from common.jingle_session import JingleSession, JingleStates
-from common.jingle_ft import JingleFileTransfer
-from common.jingle_transport import JingleTransportSocks5, JingleTransportIBB
-if gajim.HAVE_FARSTREAM:
- from common.jingle_rtp import JingleAudio, JingleVideo
-
-logger = logging.getLogger('gajim.c.jingle')
-
-
-class ConnectionJingle(object):
- """
- This object depends on that it is a part of Connection class.
- """
-
- def __init__(self):
- # dictionary: sessionid => JingleSession object
- self._sessions = {}
-
- # dictionary: (jid, iq stanza id) => JingleSession object,
- # one time callbacks
- self.__iq_responses = {}
- self.files = []
-
- def delete_jingle_session(self, sid):
- """
- Remove a jingle session from a jingle stanza dispatcher
- """
- if sid in self._sessions:
- #FIXME: Move this elsewhere?
- for content in list(self._sessions[sid].contents.values()):
- content.destroy()
- self._sessions[sid].callbacks = []
- del self._sessions[sid]
-
- def _JingleCB(self, con, stanza):
- """
- The jingle stanza dispatcher
-
- Route jingle stanza to proper JingleSession object, or create one if it
- is a new session.
-
- TODO: Also check if the stanza isn't an error stanza, if so route it
- adequatelly.
- """
- # get data
- try:
- jid = helpers.get_full_jid_from_iq(stanza)
- except helpers.InvalidFormat:
- logger.warning('Invalid JID: %s, ignoring it', stanza.getFrom())
- return
- id_ = stanza.getID()
- if (jid, id_) in self.__iq_responses.keys():
- self.__iq_responses[(jid, id_)].on_stanza(stanza)
- del self.__iq_responses[(jid, id_)]
- raise nbxmpp.NodeProcessed
- jingle = stanza.getTag('jingle')
- # a jingle element is not necessary in iq-result stanza
- # don't check for that
- if jingle:
- sid = jingle.getAttr('sid')
- else:
- sid = None
- for sesn in self._sessions.values():
- if id_ in sesn.iq_ids:
- sesn.on_stanza(stanza)
- return
- # do we need to create a new jingle object
- if sid not in self._sessions:
- #TODO: tie-breaking and other things...
- newjingle = JingleSession(con=self, weinitiate=False, jid=jid,
- iq_id=id_, sid=sid)
- self._sessions[sid] = newjingle
- # we already have such session in dispatcher...
- self._sessions[sid].collect_iq_id(id_)
- self._sessions[sid].on_stanza(stanza)
- # Delete invalid/unneeded sessions
- if sid in self._sessions and \
- self._sessions[sid].state == JingleStates.ENDED:
- self.delete_jingle_session(sid)
- raise nbxmpp.NodeProcessed
-
- def start_audio(self, jid):
- if self.get_jingle_session(jid, media='audio'):
- return self.get_jingle_session(jid, media='audio').sid
- jingle = self.get_jingle_session(jid, media='video')
- if jingle:
- jingle.add_content('voice', JingleAudio(jingle))
- else:
- jingle = JingleSession(self, weinitiate=True, jid=jid)
- self._sessions[jingle.sid] = jingle
- jingle.add_content('voice', JingleAudio(jingle))
- jingle.start_session()
- return jingle.sid
-
- def start_video(self, jid, in_xid, out_xid):
- if self.get_jingle_session(jid, media='video'):
- return self.get_jingle_session(jid, media='video').sid
- jingle = self.get_jingle_session(jid, media='audio')
- if jingle:
- jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
- out_xid=out_xid))
- else:
- jingle = JingleSession(self, weinitiate=True, jid=jid)
- self._sessions[jingle.sid] = jingle
- jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
- out_xid=out_xid))
- jingle.start_session()
- return jingle.sid
-
- def start_file_transfer(self, jid, file_props, request=False):
- logger.info("start file transfer with file: %s", file_props)
- contact = gajim.contacts.get_contact_with_highest_priority(self.name,
- gajim.get_jid_without_resource(jid))
- if gajim.contacts.is_gc_contact(self.name, jid):
- gcc = jid.split('/')
- if len(gcc) == 2:
- contact = gajim.contacts.get_gc_contact(self.name, gcc[0], gcc[1])
- if contact is None:
- return
- use_security = contact.supports(nbxmpp.NS_JINGLE_XTLS)
- jingle = JingleSession(self, weinitiate=True, jid=jid, werequest=request)
- # this is a file transfer
- jingle.session_type_ft = True
- self._sessions[jingle.sid] = jingle
- file_props.sid = jingle.sid
- if contact.supports(nbxmpp.NS_JINGLE_BYTESTREAM):
- transport = JingleTransportSocks5()
- elif contact.supports(nbxmpp.NS_JINGLE_IBB):
- transport = JingleTransportIBB()
- c = JingleFileTransfer(jingle, transport=transport,
- file_props=file_props,
- use_security=use_security)
- file_props.algo = self.__hash_support(contact)
- jingle.add_content('file' + helpers.get_random_string_16(), c)
- jingle.start_session()
- return c.transport.sid
-
- def __hash_support(self, contact):
- if contact.supports(nbxmpp.NS_HASHES_2):
- if contact.supports(nbxmpp.NS_HASHES_BLAKE2B_512):
- return 'blake2b-512'
- elif contact.supports(nbxmpp.NS_HASHES_BLAKE2B_256):
- return 'blake2b-256'
- elif contact.supports(nbxmpp.NS_HASHES_SHA3_512):
- return 'sha3-512'
- elif contact.supports(nbxmpp.NS_HASHES_SHA3_256):
- return 'sha3-256'
- elif contact.supports(nbxmpp.NS_HASHES_SHA512):
- return 'sha-512'
- elif contact.supports(nbxmpp.NS_HASHES_SHA256):
- return 'sha-256'
- return None
-
- def iter_jingle_sessions(self, jid, sid=None, media=None):
- if sid:
- return (session for session in self._sessions.values() if \
- session.sid == sid)
- sessions = (session for session in self._sessions.values() if \
- session.peerjid == jid)
- if media:
- if media not in ('audio', 'video', 'file'):
- return tuple()
- else:
- return (session for session in sessions if \
- session.get_content(media))
- else:
- return sessions
-
- def set_file_info(self, file_):
- # Saves information about the files we have transfered in case they need
- # to be requested again.
- self.files.append(file_)
-
- def get_file_info(self, peerjid, hash_=None, name=None, account=None):
- if hash_:
- for f in self.files: # DEBUG
- #if f['hash'] == '1294809248109223':
- if f['hash'] == hash_ and f['peerjid'] == peerjid:
- return f
- elif name:
- for f in self.files:
- if f['name'] == name and f['peerjid'] == peerjid:
- return f
-
- def get_jingle_session(self, jid, sid=None, media=None):
- if sid:
- if sid in self._sessions:
- return self._sessions[sid]
- else:
- return None
- elif media:
- if media not in ('audio', 'video', 'file'):
- return None
- for session in self._sessions.values():
- if session.peerjid == jid and session.get_content(media):
- return session
- return None
diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py
deleted file mode 100644
index 4108f8ebc..000000000
--- a/src/common/jingle_content.py
+++ /dev/null
@@ -1,240 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-"""
-Handles Jingle contents (XEP 0166)
-"""
-
-import os
-from common import gajim
-import nbxmpp
-from .jingle_xtls import SELF_SIGNED_CERTIFICATE
-from .jingle_xtls import load_cert_file
-
-contents = {}
-
-def get_jingle_content(node):
- namespace = node.getNamespace()
- if namespace in contents:
- return contents[namespace](node)
-
-
-class JingleContentSetupException(Exception):
- """
- Exception that should be raised when a content fails to setup.
- """
-
-
-class JingleContent:
- """
- An abstraction of content in Jingle sessions
- """
-
- def __init__(self, session, transport):
- self.session = session
- self.transport = transport
- # will be filled by JingleSession.add_content()
- # don't uncomment these lines, we will catch more buggy code then
- # (a JingleContent not added to session shouldn't send anything)
- self.creator = None
- self.name = None
- self.accepted = False
- self.sent = False
- self.negotiated = False
-
- self.media = None
-
- self.senders = 'both' #FIXME
- self.allow_sending = True # Used for stream direction, attribute 'senders'
-
- # These were found by the Politie
- self.file_props = None
- self.use_security = None
-
- self.callbacks = {
- # these are called when *we* get stanzas
- 'content-accept': [self.__on_transport_info,
- self.__on_content_accept],
- 'content-add': [self.__on_transport_info],
- 'content-modify': [],
- 'content-reject': [],
- 'content-remove': [],
- 'description-info': [],
- 'security-info': [],
- 'session-accept': [self.__on_transport_info,
- self.__on_content_accept],
- 'session-info': [],
- 'session-initiate': [self.__on_transport_info],
- 'session-terminate': [],
- 'transport-info': [self.__on_transport_info],
- 'transport-replace': [self.__on_transport_replace],
- 'transport-accept': [],
- 'transport-reject': [],
- 'iq-result': [],
- 'iq-error': [],
- # these are called when *we* sent these stanzas
- 'content-accept-sent': [self.__fill_jingle_stanza,
- self.__on_content_accept],
- 'content-add-sent': [self.__fill_jingle_stanza],
- 'session-initiate-sent': [self.__fill_jingle_stanza],
- 'session-accept-sent': [self.__fill_jingle_stanza,
- self.__on_content_accept],
- 'session-terminate-sent': [],
- }
-
- def is_ready(self):
- return self.accepted and not self.sent
-
- def __on_content_accept(self, stanza, content, error, action):
- self.on_negotiated()
-
- def on_negotiated(self):
- if self.accepted:
- self.negotiated = True
- self.session.content_negotiated(self.media)
-
- def add_remote_candidates(self, candidates):
- """
- Add a list of candidates to the list of remote candidates
- """
- self.transport.remote_candidates = candidates
-
- def on_stanza(self, stanza, content, error, action):
- """
- Called when something related to our content was sent by peer
- """
- if action in self.callbacks:
- for callback in self.callbacks[action]:
- callback(stanza, content, error, action)
-
- def __on_transport_replace(self, stanza, content, error, action):
- content.addChild(node=self.transport.make_transport())
-
- def __on_transport_info(self, stanza, content, error, action):
- """
- Got a new transport candidate
- """
- candidates = self.transport.parse_transport_stanza(
- content.getTag('transport'))
- if candidates:
- self.add_remote_candidates(candidates)
-
- def __content(self, payload=None):
- """
- Build a XML content-wrapper for our data
- """
- if payload is None:
- payload = []
- return nbxmpp.Node('content',
- attrs={'name': self.name, 'creator': self.creator},
- payload=payload)
-
- def send_candidate(self, candidate):
- """
- Send a transport candidate for a previously defined transport.
- """
- content = self.__content()
- content.addChild(node=self.transport.make_transport([candidate]))
- self.session.send_transport_info(content)
-
- def send_error_candidate(self):
- """
- Sends a candidate-error when we can't connect to a candidate.
- """
- content = self.__content()
- tp = self.transport.make_transport(add_candidates=False)
- tp.addChild(name='candidate-error')
- content.addChild(node=tp)
- self.session.send_transport_info(content)
-
-
- def send_description_info(self):
- content = self.__content()
- self._fill_content(content)
- self.session.send_description_info(content)
-
- def __fill_jingle_stanza(self, stanza, content, error, action):
- """
- Add our things to session-initiate stanza
- """
- self._fill_content(content)
- self.sent = True
- content.addChild(node=self.transport.make_transport())
-
- def _fill_content(self, content):
- description_node = nbxmpp.simplexml.Node(
- tag=nbxmpp.NS_JINGLE_FILE_TRANSFER_5 + ' description')
- file_tag = description_node.setTag('file')
- if self.file_props.name:
- node = nbxmpp.simplexml.Node(tag='name')
- node.addData(self.file_props.name)
- file_tag.addChild(node=node)
- if self.file_props.date:
- node = nbxmpp.simplexml.Node(tag='date')
- node.addData(self.file_props.date)
- file_tag.addChild(node=node)
- if self.file_props.size:
- node = nbxmpp.simplexml.Node(tag='size')
- node.addData(self.file_props.size)
- file_tag.addChild(node=node)
- if self.file_props.type_ == 'r':
- if self.file_props.hash_:
- file_tag.addChild('hash', attrs={'algo': self.file_props.algo},
- namespace=nbxmpp.NS_HASHES_2,
- payload=self.file_props.hash_)
- else:
- # if the file is less than 10 mb, then it is small
- # lets calculate it right away
- if self.file_props.size < 10000000 and not self.file_props.hash_:
- hash_data = self._compute_hash()
- if hash_data:
- file_tag.addChild(node=hash_data)
- pjid = gajim.get_jid_without_resource(self.session.peerjid)
- file_info = {'name' : self.file_props.name,
- 'file-name' : self.file_props.file_name,
- 'hash' : self.file_props.hash_,
- 'size' : self.file_props.size,
- 'date' : self.file_props.date,
- 'peerjid' : pjid
- }
- self.session.connection.set_file_info(file_info)
- desc = file_tag.setTag('desc')
- if self.file_props.desc:
- desc.setData(self.file_props.desc)
- if self.use_security:
- security = nbxmpp.simplexml.Node(
- tag=nbxmpp.NS_JINGLE_XTLS + ' security')
- certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE)\
- + '.cert'
- cert = load_cert_file(certpath)
- if cert:
- try:
- digest_algo = (cert.get_signature_algorithm()
- .decode('utf-8').split('With')[0])
- except AttributeError:
- # Old py-OpenSSL is missing get_signature_algorithm
- digest_algo = "sha256"
- security.addChild('fingerprint').addData(cert.digest(
- digest_algo).decode('utf-8'))
- for m in ('x509', ): # supported authentication methods
- method = nbxmpp.simplexml.Node(tag='method')
- method.setAttr('name', m)
- security.addChild(node=method)
- content.addChild(node=security)
- content.addChild(node=description_node)
-
- def destroy(self):
- self.callbacks = None
- del self.session.contents[(self.creator, self.name)]
diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py
deleted file mode 100644
index 3f7eae168..000000000
--- a/src/common/jingle_ft.py
+++ /dev/null
@@ -1,413 +0,0 @@
-# -*- coding:utf-8 -*-
-## 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/>.
-##
-
-
-"""
-Handles Jingle File Transfer (XEP 0234)
-"""
-
-import hashlib
-import logging
-import os
-import threading
-from enum import IntEnum, unique
-import nbxmpp
-from common import gajim
-from common import configpaths
-from common import jingle_xtls
-from common.jingle_content import contents, JingleContent
-from common.jingle_transport import JingleTransportSocks5, TransportType
-from common import helpers
-from common.connection_handlers_events import FileRequestReceivedEvent
-from common.jingle_ftstates import (
- StateInitialized, StateCandSent, StateCandReceived, StateTransfering,
- StateCandSentAndRecv, StateTransportReplace)
-
-log = logging.getLogger('gajim.c.jingle_ft')
-
-
-@unique
-class State(IntEnum):
- NOT_STARTED = 0
- INITIALIZED = 1
- # We send the candidates and we are waiting for a reply
- CAND_SENT = 2
- # We received the candidates and we are waiting to reply
- CAND_RECEIVED = 3
- # We have sent and received the candidates
- # This also includes any candidate-error received or sent
- CAND_SENT_AND_RECEIVED = 4
- TRANSPORT_REPLACE = 5
- # We are transfering the file
- TRANSFERING = 6
-
-
-class JingleFileTransfer(JingleContent):
-
- def __init__(self, session, transport=None, file_props=None,
- use_security=False):
- JingleContent.__init__(self, session, transport)
- log.info("transport value: %s", transport)
- # events we might be interested in
- self.callbacks['session-initiate'] += [self.__on_session_initiate]
- self.callbacks['session-initiate-sent'] += [
- self.__on_session_initiate_sent]
- self.callbacks['content-add'] += [self.__on_session_initiate]
- self.callbacks['session-accept'] += [self.__on_session_accept]
- self.callbacks['session-terminate'] += [self.__on_session_terminate]
- self.callbacks['session-info'] += [self.__on_session_info]
- self.callbacks['transport-accept'] += [self.__on_transport_accept]
- self.callbacks['transport-replace'] += [self.__on_transport_replace]
- self.callbacks['session-accept-sent'] += [self.__transport_setup]
- # fallback transport method
- self.callbacks['transport-reject'] += [self.__on_transport_reject]
- self.callbacks['transport-info'] += [self.__on_transport_info]
- self.callbacks['iq-result'] += [self.__on_iq_result]
- self.use_security = use_security
- self.x509_fingerprint = None
- self.file_props = file_props
- self.weinitiate = self.session.weinitiate
- self.werequest = self.session.werequest
- if self.file_props is not None:
- if self.session.werequest:
- self.file_props.sender = self.session.peerjid
- self.file_props.receiver = self.session.ourjid
- else:
- self.file_props.sender = self.session.ourjid
- self.file_props.receiver = self.session.peerjid
- self.file_props.session_type = 'jingle'
- self.file_props.sid = session.sid
- self.file_props.transfered_size = []
- self.file_props.transport_sid = self.transport.sid
- log.info("FT request: %s", file_props)
- if transport is None:
- self.transport = JingleTransportSocks5()
- self.transport.set_connection(session.connection)
- self.transport.set_file_props(self.file_props)
- self.transport.set_our_jid(session.ourjid)
- log.info('ourjid: %s', session.ourjid)
- self.session = session
- self.media = 'file'
- self.nominated_cand = {}
- if gajim.contacts.is_gc_contact(session.connection.name,
- session.peerjid):
- roomjid = session.peerjid.split('/')[0]
- dstaddr = hashlib.sha1(('%s%s%s' % (self.file_props.sid,
- session.ourjid, roomjid))
- .encode('utf-8')).hexdigest()
- self.file_props.dstaddr = dstaddr
- self.state = State.NOT_STARTED
- self.states = {
- State.INITIALIZED : StateInitialized(self),
- State.CAND_SENT : StateCandSent(self),
- State.CAND_RECEIVED : StateCandReceived(self),
- State.TRANSFERING : StateTransfering(self),
- State.TRANSPORT_REPLACE : StateTransportReplace(self),
- State.CAND_SENT_AND_RECEIVED : StateCandSentAndRecv(self)
- }
-
- if jingle_xtls.PYOPENSSL_PRESENT:
- cert_name = os.path.join(configpaths.gajimpaths['MY_CERT'],
- jingle_xtls.SELF_SIGNED_CERTIFICATE)
- if not (os.path.exists(cert_name + '.cert')
- and os.path.exists(cert_name + '.pkey')):
- jingle_xtls.make_certs(cert_name, 'gajim')
-
- def __state_changed(self, nextstate, args=None):
- # Executes the next state action and sets the next state
- current_state = self.state
- st = self.states[nextstate]
- st.action(args)
- # state can have been changed during the action. Don't go back.
- if self.state == current_state:
- self.state = nextstate
-
- def __on_session_initiate(self, stanza, content, error, action):
- log.debug("Jingle FT request received")
- gajim.nec.push_incoming_event(FileRequestReceivedEvent(None,
- conn=self.session.connection,
- stanza=stanza,
- jingle_content=content,
- FT_content=self))
- if self.session.request:
- # accept the request
- self.session.approve_content(self.media, self.name)
- self.session.accept_session()
-
- def __on_session_initiate_sent(self, stanza, content, error, action):
- pass
-
- def __send_hash(self):
- # Send hash in a session info
- checksum = nbxmpp.Node(tag='checksum',
- payload=[nbxmpp.Node(tag='file',
- payload=[self._compute_hash()])])
- checksum.setNamespace(nbxmpp.NS_JINGLE_FILE_TRANSFER_5)
- self.session.__session_info(checksum)
- pjid = gajim.get_jid_without_resource(self.session.peerjid)
- file_info = {'name' : self.file_props.name,
- 'file-name' : self.file_props.file_name,
- 'hash' : self.file_props.hash_,
- 'size' : self.file_props.size,
- 'date' : self.file_props.date,
- 'peerjid' : pjid
- }
- self.session.connection.set_file_info(file_info)
-
- def _compute_hash(self):
- # Caculates the hash and returns a xep-300 hash stanza
- if self.file_props.algo is None:
- return
- try:
- file_ = open(self.file_props.file_name, 'rb')
- except IOError:
- # can't open file
- return
- h = nbxmpp.Hashes2()
- hash_ = h.calculateHash(self.file_props.algo, file_)
- file_.close()
- # DEBUG
- #hash_ = '1294809248109223'
- if not hash_:
- # Hash alogrithm not supported
- return
- self.file_props.hash_ = hash_
- h.addHash(hash_, self.file_props.algo)
- return h
-
- def on_cert_received(self):
- self.session.approve_session()
- self.session.approve_content('file', name=self.name)
-
- def __on_session_accept(self, stanza, content, error, action):
- log.info("__on_session_accept")
- con = self.session.connection
- security = content.getTag('security')
- if not security: # responder can not verify our fingerprint
- self.use_security = False
- else:
- fingerprint = security.getTag('fingerprint')
- if fingerprint:
- fingerprint = fingerprint.getData()
- self.x509_fingerprint = fingerprint
- if not jingle_xtls.check_cert(gajim.get_jid_without_resource(
- self.session.responder), fingerprint):
- id_ = jingle_xtls.send_cert_request(con,
- self.session.responder)
- jingle_xtls.key_exchange_pend(id_,
- self.continue_session_accept,
- [stanza])
- raise nbxmpp.NodeProcessed
- self.continue_session_accept(stanza)
-
- def continue_session_accept(self, stanza):
- con = self.session.connection
- if self.state == State.TRANSPORT_REPLACE:
- # If we are requesting we don't have the file
- if self.session.werequest:
- raise nbxmpp.NodeProcessed
- # We send the file
- self.__state_changed(State.TRANSFERING)
- raise nbxmpp.NodeProcessed
- self.file_props.streamhosts = self.transport.remote_candidates
- # Calculate file hash in a new thread
- # if we haven't sent the hash already.
- if self.file_props.hash_ is None and self.file_props.algo and \
- not self.werequest:
- self.hash_thread = threading.Thread(target=self.__send_hash)
- self.hash_thread.start()
- for host in self.file_props.streamhosts:
- host['initiator'] = self.session.initiator
- host['target'] = self.session.responder
- host['sid'] = self.file_props.sid
- fingerprint = None
- if self.use_security:
- fingerprint = 'client'
- if self.transport.type_ == TransportType.SOCKS5:
- gajim.socks5queue.connect_to_hosts(self.session.connection.name,
- self.file_props.sid,
- self.on_connect,
- self._on_connect_error,
- fingerprint=fingerprint,
- receiving=False)
- raise nbxmpp.NodeProcessed
- self.__state_changed(State.TRANSFERING)
- raise nbxmpp.NodeProcessed
-
- def __on_session_terminate(self, stanza, content, error, action):
- log.info("__on_session_terminate")
-
- def __on_session_info(self, stanza, content, error, action):
- pass
-
- def __on_transport_accept(self, stanza, content, error, action):
- log.info("__on_transport_accept")
-
- def __on_transport_replace(self, stanza, content, error, action):
- log.info("__on_transport_replace")
-
- def __on_transport_reject(self, stanza, content, error, action):
- log.info("__on_transport_reject")
-
- def __on_transport_info(self, stanza, content, error, action):
- log.info("__on_transport_info")
- cand_error = content.getTag('transport').getTag('candidate-error')
- cand_used = content.getTag('transport').getTag('candidate-used')
- if (cand_error or cand_used) and \
- self.state >= State.CAND_SENT_AND_RECEIVED:
- raise nbxmpp.NodeProcessed
- if cand_error:
- if not gajim.socks5queue.listener.connections:
- gajim.socks5queue.listener.disconnect()
- self.nominated_cand['peer-cand'] = False
- if self.state == State.CAND_SENT:
- if not self.nominated_cand['our-cand'] and \
- not self.nominated_cand['peer-cand']:
- if not self.weinitiate:
- return
- self.__state_changed(State.TRANSPORT_REPLACE)
- else:
- response = stanza.buildReply('result')
- response.delChild(response.getQuery())
- self.session.connection.connection.send(response)
- self.__state_changed(State.TRANSFERING)
- raise nbxmpp.NodeProcessed
- else:
- args = {'cand_error' : True}
- self.__state_changed(State.CAND_RECEIVED, args)
- return
- if cand_used:
- streamhost_cid = cand_used.getAttr('cid')
- streamhost_used = None
- for cand in self.transport.candidates:
- if cand['candidate_id'] == streamhost_cid:
- streamhost_used = cand
- break
- if streamhost_used is None or streamhost_used['type'] == 'proxy':
- if gajim.socks5queue.listener and \
- not gajim.socks5queue.listener.connections:
- gajim.socks5queue.listener.disconnect()
- if content.getTag('transport').getTag('activated'):
- self.state = State.TRANSFERING
- jid = gajim.get_jid_without_resource(self.session.ourjid)
- gajim.socks5queue.send_file(self.file_props,
- self.session.connection.name, 'client')
- return
- args = {'content': content,
- 'sendCand': False}
- if self.state == State.CAND_SENT:
- self.__state_changed(State.CAND_SENT_AND_RECEIVED, args)
- self.__state_changed(State.TRANSFERING)
- raise nbxmpp.NodeProcessed
- else:
- self.__state_changed(State.CAND_RECEIVED, args)
-
- def __on_iq_result(self, stanza, content, error, action):
- log.info("__on_iq_result")
-
- if self.state == State.NOT_STARTED:
- self.__state_changed(State.INITIALIZED)
- elif self.state == State.CAND_SENT_AND_RECEIVED:
- if not self.nominated_cand['our-cand'] and \
- not self.nominated_cand['peer-cand']:
- if not self.weinitiate:
- return
- self.__state_changed(State.TRANSPORT_REPLACE)
- return
- # initiate transfer
- self.__state_changed(State.TRANSFERING)
-
- def __transport_setup(self, stanza=None, content=None, error=None,
- action=None):
- # Sets up a few transport specific things for the file transfer
- if self.transport.type_ == TransportType.IBB:
- # No action required, just set the state to transfering
- self.state = State.TRANSFERING
- else:
- self._listen_host()
-
- def on_connect(self, streamhost):
- """
- send candidate-used stanza
- """
- log.info('send_candidate_used')
- if streamhost is None:
- return
- args = {'streamhost' : streamhost,
- 'sendCand' : True}
- self.nominated_cand['our-cand'] = streamhost
- self.__send_candidate(args)
-
- def _on_connect_error(self, sid):
- log.info('connect error, sid=' + sid)
- args = {'candError' : True,
- 'sendCand' : True}
- self.__send_candidate(args)
-
- def __send_candidate(self, args):
- if self.state == State.CAND_RECEIVED:
- self.__state_changed(State.CAND_SENT_AND_RECEIVED, args)
- else:
- self.__state_changed(State.CAND_SENT, args)
-
- def _store_socks5_sid(self, sid, hash_id):
- # callback from socsk5queue.start_listener
- self.file_props.hash_ = hash_id
-
- def _listen_host(self):
- receiver = self.file_props.receiver
- sender = self.file_props.sender
- sha_str = helpers.get_auth_sha(self.file_props.sid, sender,
- receiver)
- self.file_props.sha_str = sha_str
- port = gajim.config.get('file_transfers_port')
- fingerprint = None
- if self.use_security:
- fingerprint = 'server'
- listener = gajim.socks5queue.start_listener(port, sha_str,
- self._store_socks5_sid,
- self.file_props,
- fingerprint=fingerprint,
- typ='sender' if self.weinitiate else 'receiver')
- if not listener:
- # send error message, notify the user
- return
-
- def is_our_candidate_used(self):
- '''
- If this method returns true then the candidate we nominated will be
- used, if false, the candidate nominated by peer will be used
- '''
-
- if not self.nominated_cand['peer-cand']:
- return True
- if not self.nominated_cand['our-cand']:
- return False
- peer_pr = int(self.nominated_cand['peer-cand']['priority'])
- our_pr = int(self.nominated_cand['our-cand']['priority'])
- if peer_pr != our_pr:
- return our_pr > peer_pr
- return self.weinitiate
-
- def start_ibb_transfer(self):
- if self.file_props.type_ == 's':
- self.__state_changed(State.TRANSFERING)
-
-
-def get_content(desc):
- return JingleFileTransfer
-
-contents[nbxmpp.NS_JINGLE_FILE_TRANSFER_5] = get_content
diff --git a/src/common/jingle_ftstates.py b/src/common/jingle_ftstates.py
deleted file mode 100644
index 5c305f78a..000000000
--- a/src/common/jingle_ftstates.py
+++ /dev/null
@@ -1,229 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-import nbxmpp
-from common import gajim
-from common.jingle_transport import TransportType
-from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
-
-import logging
-log = logging.getLogger('gajim.c.jingle_ftstates')
-
-
-class JingleFileTransferStates:
- '''
- This class implements the state machine design pattern
- '''
-
- def __init__(self, jingleft):
- self.jft = jingleft
-
- def action(self, args=None):
- '''
- This method MUST be overriden by a subclass
- '''
- raise NotImplementedError('This is an abstract method!')
-
-
-class StateInitialized(JingleFileTransferStates):
- '''
- This state initializes the file transfer
- '''
-
- def action(self, args=None):
- if self.jft.weinitiate:
- # update connection's fileprops
- self.jft._listen_host()
- # Listen on configured port for file transfer
- else:
- fingerprint = None
- if self.jft.use_security:
- fingerprint = 'client'
- # Connect to the candidate host, on success call on_connect method
- gajim.socks5queue.connect_to_hosts(self.jft.session.connection.name,
- self.jft.file_props.sid,
- self.jft.on_connect,
- self.jft._on_connect_error,
- fingerprint=fingerprint)
-
-
-class StateCandSent(JingleFileTransferStates):
- '''
- This state sends our nominated candidate
- '''
-
- def _send_candidate(self, args):
- if 'candError' in args:
- self.jft.nominated_cand['our-cand'] = False
- self.jft.send_error_candidate()
- return
- # Send candidate used
- streamhost = args['streamhost']
- self.jft.nominated_cand['our-cand'] = streamhost
- content = nbxmpp.Node('content')
- content.setAttr('creator', 'initiator')
- content.setAttr('name', self.jft.name)
- transport = nbxmpp.Node('transport')
- transport.setNamespace(nbxmpp.NS_JINGLE_BYTESTREAM)
- transport.setAttr('sid', self.jft.transport.sid)
- candidateused = nbxmpp.Node('candidate-used')
- candidateused.setAttr('cid', streamhost['cid'])
- transport.addChild(node=candidateused)
- content.addChild(node=transport)
- self.jft.session.send_transport_info(content)
-
- def action(self, args=None):
- self._send_candidate(args)
-
-
-class StateCandReceived(JingleFileTransferStates):
- '''
- This state happens when we receive a candidate.
- It takes the arguments: canError if we receive a candidate-error
- '''
-
- def _recv_candidate(self, args):
- if 'candError' in args:
- return
- content = args['content']
- streamhost_cid = content.getTag('transport').getTag('candidate-used').\
- getAttr('cid')
- streamhost_used = None
- for cand in self.jft.transport.candidates:
- if cand['candidate_id'] == streamhost_cid:
- streamhost_used = cand
- break
- if streamhost_used is None:
- log.info("unknow streamhost")
- return
- # We save the candidate nominated by peer
- self.jft.nominated_cand['peer-cand'] = streamhost_used
-
- def action(self, args=None):
- self._recv_candidate(args)
-
-
-class StateCandSentAndRecv(StateCandSent, StateCandReceived):
- '''
- This state happens when we have received and sent the candidates.
- It takes the boolean argument: sendCand in order to decide whether
- we should execute the action of when we receive or send a candidate.
- '''
-
- def action(self, args=None):
- if args['sendCand']:
- self._send_candidate(args)
- else:
- self._recv_candidate(args)
-
-
-class StateTransportReplace(JingleFileTransferStates):
- '''
- This state initiates transport replace
- '''
-
- def action(self, args=None):
- self.jft.session.transport_replace()
-
-
-class StateTransfering(JingleFileTransferStates):
- '''
- This state will start the transfer depeding on the type of transport
- we have.
- '''
-
- def _start_ibb_transfer(self, con):
- self.jft.file_props.transport_sid = self.jft.transport.sid
- fp = open(self.jft.file_props.file_name, 'rb')
- con.OpenStream(self.jft.file_props.sid, self.jft.session.peerjid, fp,
- blocksize=4096)
-
- def _start_sock5_transfer(self):
- # It tells wether we start the transfer as client or server
- mode = None
- if self.jft.is_our_candidate_used():
- mode = 'client'
- streamhost_used = self.jft.nominated_cand['our-cand']
- gajim.socks5queue.remove_server(self.jft.file_props.sid)
- else:
- mode = 'server'
- streamhost_used = self.jft.nominated_cand['peer-cand']
- gajim.socks5queue.remove_client(self.jft.file_props.sid)
-# our_cand = self.jft.nominated_cand['our-cand']
-# gajim.socks5queue.remove_receiver(our_cand['idx'])
- if streamhost_used['type'] == 'proxy':
- self.jft.file_props.is_a_proxy = True
- if self.jft.file_props.type_ == 's' and self.jft.weinitiate:
- self.jft.file_props.proxy_sender = streamhost_used['initiator']
- self.jft.file_props.proxy_receiver = streamhost_used['target']
- else:
- self.jft.file_props.proxy_sender = streamhost_used['target']
- self.jft.file_props.proxy_receiver = streamhost_used[
- 'initiator']
- if self.jft.file_props.type_ == 's':
- s = gajim.socks5queue.senders
- for sender in s:
- if s[sender].host == streamhost_used['host'] and \
- s[sender].connected:
- return
- elif self.jft.file_props.type_ == 'r':
- r = gajim.socks5queue.readers
- for reader in r:
- if r[reader].host == streamhost_used['host'] and \
- r[reader].connected:
- return
- else:
- raise TypeError
- self.jft.file_props.streamhost_used = True
- streamhost_used['sid'] = self.jft.file_props.sid
- self.jft.file_props.streamhosts = []
- self.jft.file_props.streamhosts.append(streamhost_used)
- self.jft.file_props.proxyhosts = []
- self.jft.file_props.proxyhosts.append(streamhost_used)
- if self.jft.file_props.type_ == 's':
- gajim.socks5queue.idx += 1
- idx = gajim.socks5queue.idx
- sockobj = Socks5SenderClient(gajim.idlequeue, idx,
- gajim.socks5queue, _sock=None,
- host=str(streamhost_used['host']),
- port=int(streamhost_used['port']),
- fingerprint=None, connected=False,
- file_props=self.jft.file_props)
- else:
- sockobj = Socks5ReceiverClient(gajim.idlequeue, streamhost_used,
- sid=self.jft.file_props.sid,
- file_props=self.jft.file_props,
- fingerprint=None)
- sockobj.proxy = True
- sockobj.streamhost = streamhost_used
- gajim.socks5queue.add_sockobj(self.jft.session.connection.name,
- sockobj)
- streamhost_used['idx'] = sockobj.queue_idx
- # If we offered the nominated candidate used, we activate
- # the proxy
- if not self.jft.is_our_candidate_used():
- gajim.socks5queue.on_success[self.jft.file_props.sid] = \
- self.jft.transport._on_proxy_auth_ok
- # TODO: add on failure
- else:
- jid = gajim.get_jid_without_resource(self.jft.session.ourjid)
- gajim.socks5queue.send_file(self.jft.file_props,
- self.jft.session.connection.name, mode)
-
- def action(self, args=None):
- if self.jft.transport.type_ == TransportType.IBB:
- self._start_ibb_transfer(self.jft.session.connection)
- elif self.jft.transport.type_ == TransportType.SOCKS5:
- self._start_sock5_transfer()
diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py
deleted file mode 100644
index 12fc9a7e8..000000000
--- a/src/common/jingle_rtp.py
+++ /dev/null
@@ -1,477 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-"""
-Handles Jingle RTP sessions (XEP 0167)
-"""
-
-import logging
-import socket
-import nbxmpp
-import gi
-from gi.repository import Farstream
-
-gi.require_version('Gst', '1.0')
-from gi.repository import Gst
-from gi.repository import GLib
-
-from common import gajim
-
-from common.jingle_transport import JingleTransportICEUDP
-from common.jingle_content import contents, JingleContent, JingleContentSetupException
-from common.connection_handlers_events import InformationEvent
-from common.jingle_session import FailedApplication
-
-from collections import deque
-
-log = logging.getLogger('gajim.c.jingle_rtp')
-
-
-class JingleRTPContent(JingleContent):
- def __init__(self, session, media, transport=None):
- if transport is None:
- transport = JingleTransportICEUDP(None)
- JingleContent.__init__(self, session, transport)
- self.media = media
- self._dtmf_running = False
- self.farstream_media = {
- 'audio': Farstream.MediaType.AUDIO,
- 'video': Farstream.MediaType.VIDEO}[media]
-
- self.pipeline = None
- self.src_bin = None
- self.stream_failed_once = False
-
- self.candidates_ready = False # True when local candidates are prepared
-
- # TODO
- self.conference = None
- self.funnel = None
- self.p2psession = None
- self.p2pstream = None
-
- self.callbacks['session-initiate'] += [self.__on_remote_codecs]
- self.callbacks['content-add'] += [self.__on_remote_codecs]
- self.callbacks['description-info'] += [self.__on_remote_codecs]
- self.callbacks['content-accept'] += [self.__on_remote_codecs]
- self.callbacks['session-accept'] += [self.__on_remote_codecs]
- self.callbacks['session-terminate'] += [self.__stop]
- self.callbacks['session-terminate-sent'] += [self.__stop]
-
- def setup_stream(self, on_src_pad_added):
- # pipeline and bus
- self.pipeline = Gst.Pipeline()
- bus = self.pipeline.get_bus()
- bus.add_signal_watch()
- bus.connect('message', self._on_gst_message)
-
- # conference
- self.conference = Gst.ElementFactory.make('fsrtpconference', None)
- self.pipeline.add(self.conference)
- self.funnel = None
-
- self.p2psession = self.conference.new_session(self.farstream_media)
-
- participant = self.conference.new_participant()
- # FIXME: Consider a workaround, here...
- # pidgin and telepathy-gabble don't follow the XEP, and it won't work
- # due to bad controlling-mode
-
- params = {'controlling-mode': self.session.weinitiate, 'debug': False}
- if gajim.config.get('use_stun_server'):
- stun_server = gajim.config.get('stun_server')
- if not stun_server and self.session.connection._stun_servers:
- stun_server = self.session.connection._stun_servers[0]['host']
- if stun_server:
- try:
- ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
- socket.SOCK_STREAM)[0][4][0]
- except socket.gaierror as e:
- log.warning('Lookup of stun ip failed: %s', str(e))
- else:
- params['stun-ip'] = ip
-
- self.p2pstream = self.p2psession.new_stream(participant,
- Farstream.StreamDirection.BOTH)
- self.p2pstream.connect('src-pad-added', on_src_pad_added)
- self.p2pstream.set_transmitter_ht('nice', params)
-
- def is_ready(self):
- return JingleContent.is_ready(self) and self.candidates_ready
-
- def make_bin_from_config(self, config_key, pipeline, text):
- pipeline = pipeline % gajim.config.get(config_key)
- try:
- gst_bin = Gst.parse_bin_from_description(pipeline, True)
- return gst_bin
- except GLib.GError as e:
- gajim.nec.push_incoming_event(
- InformationEvent(
- None, conn=self.session.connection, level='error',
- pri_txt=_('%s configuration error') % text.capitalize(),
- sec_txt=_('Couldn’t setup %s. Check your configuration.\n\n'
- 'Pipeline was:\n%s\n\nError was:\n%s') % (text, pipeline, str(e))))
- raise JingleContentSetupException
-
- def add_remote_candidates(self, candidates):
- JingleContent.add_remote_candidates(self, candidates)
- # FIXME: connectivity should not be etablished yet
- # Instead, it should be etablished after session-accept!
- if self.sent:
- self.p2pstream.add_remote_candidates(candidates)
-
- def batch_dtmf(self, events):
- """
- Send several DTMF tones
- """
- if self._dtmf_running:
- raise Exception("There is a DTMF batch already running")
- events = deque(events)
- self._dtmf_running = True
- self._start_dtmf(events.popleft())
- GLib.timeout_add(500, self._next_dtmf, events)
-
- def _next_dtmf(self, events):
- self._stop_dtmf()
- if events:
- self._start_dtmf(events.popleft())
- GLib.timeout_add(500, self._next_dtmf, events)
- else:
- self._dtmf_running = False
-
- def _start_dtmf(self, event):
- if event in ('*', '#'):
- event = {'*': Farstream.DTMFEvent.STAR,
- '#': Farstream.DTMFEvent.POUND}[event]
- else:
- event = int(event)
- self.p2psession.start_telephony_event(event, 2)
-
- def _stop_dtmf(self):
- self.p2psession.stop_telephony_event()
-
- def _fill_content(self, content):
- content.addChild(nbxmpp.NS_JINGLE_RTP + ' description',
- attrs={'media': self.media},
- payload=list(self.iter_codecs()))
-
- def _setup_funnel(self):
- self.funnel = Gst.ElementFactory.make('funnel', None)
- self.pipeline.add(self.funnel)
- self.funnel.link(self.sink)
- self.sink.set_state(Gst.State.PLAYING)
- self.funnel.set_state(Gst.State.PLAYING)
-
- def _on_src_pad_added(self, stream, pad, codec):
- if not self.funnel:
- self._setup_funnel()
- pad.link(self.funnel.get_request_pad('sink_%u'))
-
- def _on_gst_message(self, bus, message):
- if message.type == Gst.MessageType.ELEMENT:
- name = message.get_structure().get_name()
- log.debug('gst element message: %s: %s', name, message)
- if name == 'farstream-new-active-candidate-pair':
- pass
- elif name == 'farstream-recv-codecs-changed':
- pass
- elif name == 'farstream-codecs-changed':
- if self.sent and self.p2psession.props.codecs_without_config:
- self.send_description_info()
- if self.transport.remote_candidates:
- # those lines MUST be done after we get info on our
- # codecs
- self.p2pstream.add_remote_candidates(
- self.transport.remote_candidates)
- self.transport.remote_candidates = []
- self.p2pstream.set_property('direction',
- Farstream.StreamDirection.BOTH)
-
- elif name == 'farstream-local-candidates-prepared':
- self.candidates_ready = True
- if self.is_ready():
- self.session.on_session_state_changed(self)
- elif name == 'farstream-new-local-candidate':
- candidate = self.p2pstream.parse_new_local_candidate(message)[1]
- self.transport.candidates.append(candidate)
- if self.sent:
- # FIXME: Is this case even possible?
- self.send_candidate(candidate)
- elif name == 'farstream-component-state-changed':
- state = message.get_structure().get_value('state')
- if state == Farstream.StreamState.FAILED:
- reason = nbxmpp.Node('reason')
- reason.setTag('failed-transport')
- self.session.remove_content(self.creator, self.name, reason)
- elif name == 'farstream-error':
- log.error('Farstream error #%d!\nMessage: %s',
- message.get_structure().get_value('error-no'),
- message.get_structure().get_value('error-msg'))
- elif message.type == Gst.MessageType.ERROR:
- # TODO: Fix it to fallback to videotestsrc anytime an error occur,
- # or raise an error, Jingle way
- # or maybe one-sided stream?
- if not self.stream_failed_once:
- gajim.nec.push_incoming_event(
- InformationEvent(
- None, conn=self.session.connection, level='error',
- pri_txt=_('GStreamer error'),
- sec_txt=_('Error: %s\nDebug: %s' %
- (message.get_structure().get_value('gerror'),
- message.get_structure().get_value('debug')))))
-
- sink_pad = self.p2psession.get_property('sink-pad')
-
- # Remove old source
- self.src_bin.get_static_pad('src').unlink(sink_pad)
- self.src_bin.set_state(Gst.State.NULL)
- self.pipeline.remove(self.src_bin)
-
- if not self.stream_failed_once:
- # Add fallback source
- self.src_bin = self.get_fallback_src()
- self.pipeline.add(self.src_bin)
- self.src_bin.link(sink_pad)
- self.stream_failed_once = True
- else:
- reason = nbxmpp.Node('reason')
- reason.setTag('failed-application')
- self.session.remove_content(self.creator, self.name, reason)
-
- # Start playing again
- self.pipeline.set_state(Gst.State.PLAYING)
-
- @staticmethod
- def get_fallback_src():
- return Gst.ElementFactory.make('fakesrc', None)
-
- def on_negotiated(self):
- if self.accepted:
- if self.p2psession.get_property('codecs'):
- # those lines MUST be done after we get info on our codecs
- if self.transport.remote_candidates:
- self.p2pstream.add_remote_candidates(
- self.transport.remote_candidates)
- self.transport.remote_candidates = []
- # TODO: Farstream.StreamDirection.BOTH only if senders='both'
-# self.p2pstream.set_property('direction',
-# Farstream.StreamDirection.BOTH)
- JingleContent.on_negotiated(self)
-
- def __on_remote_codecs(self, stanza, content, error, action):
- """
- Get peer codecs from what we get from peer
- """
-
- codecs = []
- for codec in content.getTag('description').iterTags('payload-type'):
- if not codec['id'] or not codec['name'] or not codec['clockrate']:
- # ignore invalid payload-types
- continue
- c = Farstream.Codec.new(int(codec['id']), codec['name'],
- self.farstream_media, int(codec['clockrate']))
- if 'channels' in codec:
- c.channels = int(codec['channels'])
- else:
- c.channels = 1
- for p in codec.iterTags('parameter'):
- c.add_optional_parameter(p['name'], str(p['value']))
- codecs.append(c)
-
- if codecs:
- try:
- self.p2pstream.set_remote_codecs(codecs)
- except GLib.Error:
- raise FailedApplication
-
- def iter_codecs(self):
- codecs = self.p2psession.props.codecs_without_config
- for codec in codecs:
- attrs = {
- 'name': codec.encoding_name,
- 'id': codec.id,
- 'channels': codec.channels
- }
- if codec.clock_rate:
- attrs['clockrate'] = codec.clock_rate
- if codec.optional_params:
- payload = [nbxmpp.Node('parameter',
- {'name': p.name, 'value': p.value})
- for p in codec.optional_params]
- else:
- payload = []
- yield nbxmpp.Node('payload-type', attrs, payload)
-
- def __stop(self, *things):
- self.pipeline.set_state(Gst.State.NULL)
-
- def __del__(self):
- self.__stop()
-
- def destroy(self):
- JingleContent.destroy(self)
- self.p2pstream.disconnect_by_func(self._on_src_pad_added)
- self.pipeline.get_bus().disconnect_by_func(self._on_gst_message)
-
-
-class JingleAudio(JingleRTPContent):
- """
- Jingle VoIP sessions consist of audio content transported over an ICE UDP
- protocol
- """
-
- def __init__(self, session, transport=None):
- JingleRTPContent.__init__(self, session, 'audio', transport)
- self.setup_stream()
-
- def set_mic_volume(self, vol):
- """
- vol must be between 0 ans 1
- """
- self.mic_volume.set_property('volume', vol)
-
- def set_out_volume(self, vol):
- """
- vol must be between 0 ans 1
- """
- self.out_volume.set_property('volume', vol)
-
- def setup_stream(self):
- JingleRTPContent.setup_stream(self, self._on_src_pad_added)
-
- # Configure SPEEX
- # Workaround for psi (not needed since rev
- # 147aedcea39b43402fe64c533d1866a25449888a):
- # place 16kHz before 8kHz, as buggy psi versions will take in
- # account only the first codec
-
- codecs = [
- Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
- Farstream.MediaType.AUDIO, 16000),
- Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
- Farstream.MediaType.AUDIO, 8000)]
- self.p2psession.set_codec_preferences(codecs)
-
- # the local parts
- # TODO: Add queues?
- self.src_bin = self.make_bin_from_config('audio_input_device',
- '%s ! audioconvert',
- _("audio input"))
-
- self.sink = self.make_bin_from_config('audio_output_device',
- 'audioconvert ! volume name=gajim_out_vol ! %s',
- _("audio output"))
-
- self.mic_volume = self.src_bin.get_by_name('gajim_vol')
- self.out_volume = self.sink.get_by_name('gajim_out_vol')
-
- # link gst elements
- self.pipeline.add(self.sink)
- self.pipeline.add(self.src_bin)
-
- self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
- 'sink-pad'))
-
- # The following is needed for farstream to process ICE requests:
- self.pipeline.set_state(Gst.State.PLAYING)
-
-
-class JingleVideo(JingleRTPContent):
- def __init__(self, session, transport=None, in_xid=0, out_xid=0):
- JingleRTPContent.__init__(self, session, 'video', transport)
- self.in_xid = in_xid
- self.out_xid = out_xid
- self.out_xid_set = False
- self.setup_stream()
-
- def setup_stream(self):
- # TODO: Everything is not working properly:
- # sometimes, one window won't show up,
- # sometimes it'll freeze...
- JingleRTPContent.setup_stream(self, self._on_src_pad_added)
- bus = self.pipeline.get_bus()
- bus.enable_sync_message_emission()
- bus.connect('sync-message::element', self._on_sync_message)
-
- # the local parts
- if gajim.config.get('video_framerate'):
- framerate = 'videorate ! video/x-raw,framerate=%s ! ' % \
- gajim.config.get('video_framerate')
- else:
- framerate = ''
- try:
- w, h = gajim.config.get('video_size').split('x')
- except:
- w = h = None
- if w and h:
- video_size = 'video/x-raw,width=%s,height=%s ! ' % (w, h)
- else:
- video_size = ''
- if gajim.config.get('video_see_self'):
- tee = '! tee name=t ! queue ! videoscale ! ' + \
- 'video/x-raw,width=160,height=120 ! videoconvert ! ' + \
- '%s t. ! queue ' % gajim.config.get(
- 'video_output_device')
- else:
- tee = ''
-
- self.src_bin = self.make_bin_from_config('video_input_device',
- '%%s %s! %svideoscale ! %svideoconvert' %
- (tee, framerate, video_size),
- _("video input"))
-
- self.pipeline.add(self.src_bin)
- self.pipeline.set_state(Gst.State.PLAYING)
-
- self.sink = self.make_bin_from_config('video_output_device',
- 'videoscale ! videoconvert ! %s',
- _("video output"))
-
- self.pipeline.add(self.sink)
-
- self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
- 'sink-pad'))
-
- # The following is needed for farstream to process ICE requests:
- self.pipeline.set_state(Gst.State.PLAYING)
-
- def _on_sync_message(self, bus, message):
- if message.get_structure() is None:
- return False
- if message.get_structure().get_name() == 'prepare-window-handle':
- message.src.set_property('force-aspect-ratio', True)
- imagesink = message.src
- if gajim.config.get('video_see_self') and not self.out_xid_set:
- imagesink.set_window_handle(self.out_xid)
- self.out_xid_set = True
- else:
- imagesink.set_window_handle(self.in_xid)
-
- def get_fallback_src(self):
- # TODO: Use avatar?
- pipeline = 'videotestsrc is-live=true ! video/x-raw,framerate=10/1 ! videoconvert'
- return Gst.parse_bin_from_description(pipeline, True)
-
- def destroy(self):
- JingleRTPContent.destroy(self)
- self.pipeline.get_bus().disconnect_by_func(self._on_sync_message)
-
-def get_content(desc):
- if desc['media'] == 'audio':
- return JingleAudio
- elif desc['media'] == 'video':
- return JingleVideo
-
-contents[nbxmpp.NS_JINGLE_RTP] = get_content
diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py
deleted file mode 100644
index 371baf56c..000000000
--- a/src/common/jingle_session.py
+++ /dev/null
@@ -1,833 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-"""
-Handles Jingle sessions (XEP 0166)
-"""
-
-#TODO:
-# * 'senders' attribute of 'content' element
-# * security preconditions
-# * actions:
-# - content-modify
-# - session-info
-# - security-info
-# - transport-accept, transport-reject
-# - Tie-breaking
-# * timeout
-
-import logging
-from enum import Enum, unique
-import nbxmpp
-from common import gajim
-from common.jingle_transport import get_jingle_transport, JingleTransportIBB
-from common.jingle_content import get_jingle_content, JingleContentSetupException, JingleContent
-from common.jingle_ft import State
-from common.connection_handlers_events import (
- FilesProp, JingleRequestReceivedEvent, JingleDisconnectedReceivedEvent,
- JingleTransferCancelledEvent, JingleConnectedReceivedEvent,
- JingleErrorReceivedEvent)
-
-log = logging.getLogger("gajim.c.jingle_session")
-
-# FIXME: Move it to JingleSession.States?
-@unique
-class JingleStates(Enum):
- """
- States in which jingle session may exist
- """
- ENDED = 0
- PENDING = 1
- ACTIVE = 2
-
-class OutOfOrder(Exception):
- """
- Exception that should be raised when an action is received when in the wrong
- state
- """
-
-class TieBreak(Exception):
- """
- Exception that should be raised in case of a tie, when we overrule the other
- action
- """
-
-class FailedApplication(Exception):
- """
- Exception that should be raised in case responder supports none of the
- payload-types offered by the initiator
- """
-
-class JingleSession:
- """
- This represents one jingle session, that is, one or more content types
- negotiated between an initiator and a responder.
- """
-
- def __init__(self, con, weinitiate, jid, iq_id=None, sid=None,
- werequest=False):
- """
- con -- connection object,
- weinitiate -- boolean, are we the initiator?
- jid - jid of the other entity
- """
- self.contents = {} # negotiated contents
- self.connection = con # connection to use
- # our full jid
- self.ourjid = gajim.get_jid_from_account(self.connection.name)
- if con.server_resource:
- self.ourjid = self.ourjid + '/' + con.server_resource
- self.peerjid = jid # jid we connect to
- # jid we use as the initiator
- self.initiator = self.ourjid if weinitiate else self.peerjid
- # jid we use as the responder
- self.responder = self.peerjid if weinitiate else self.ourjid
- # are we an initiator?
- self.weinitiate = weinitiate
- # Are we requesting or offering a file?
- self.werequest = werequest
- self.request = False
- # what state is session in? (one from JingleStates)
- self.state = JingleStates.ENDED
- if not sid:
- sid = con.connection.getAnID()
- self.sid = sid # sessionid
- # iq stanza id, used to determine which sessions to summon callback
- # later on when iq-result stanza arrives
- if iq_id is not None:
- self.iq_ids = [iq_id]
- else:
- self.iq_ids = []
- self.accepted = True # is this session accepted by user
- # Tells whether this session is a file transfer or not
- self.session_type_ft = False
- # callbacks to call on proper contents
- # use .prepend() to add new callbacks, especially when you're going
- # to send error instead of ack
- self.callbacks = {
- 'content-accept': [self.__ack, self.__on_content_accept,
- self.__broadcast],
- 'content-add': [self.__ack,
- self.__on_content_add, self.__broadcast
- ], #TODO
- 'content-modify': [self.__ack], #TODO
- 'content-reject': [self.__ack, self.__on_content_remove],
- 'content-remove': [self.__ack, self.__on_content_remove],
- 'description-info': [self.__ack, self.__broadcast], #TODO
- 'security-info': [self.__ack], #TODO
- 'session-accept': [self.__ack, self.__on_session_accept,
- self.__on_content_accept,
- self.__broadcast],
- 'session-info': [self.__ack, self.__broadcast,
- self.__on_session_info],
- 'session-initiate': [self.__ack, self.__on_session_initiate,
- self.__broadcast],
- 'session-terminate': [self.__ack, self.__on_session_terminate,
- self.__broadcast_all],
- 'transport-info': [self.__ack, self.__broadcast],
- 'transport-replace': [self.__ack, self.__broadcast,
- self.__on_transport_replace], #TODO
- 'transport-accept': [self.__ack], #TODO
- 'transport-reject': [self.__ack], #TODO
- 'iq-result': [self.__broadcast],
- 'iq-error': [self.__on_error],
- }
-
- def collect_iq_id(self, iq_id):
- if iq_id is not None:
- self.iq_ids.append(iq_id)
-
- def approve_session(self):
- """
- Called when user accepts session in UI (when we aren't the initiator)
- """
- self.accept_session()
-
- def decline_session(self):
- """
- Called when user declines session in UI (when we aren't the initiator)
- """
- reason = nbxmpp.Node('reason')
- reason.addChild('decline')
- self._session_terminate(reason)
-
- def cancel_session(self):
- """
- Called when user declines session in UI (when we aren't the initiator)
- """
- reason = nbxmpp.Node('reason')
- reason.addChild('cancel')
- self._session_terminate(reason)
-
- def approve_content(self, media, name=None):
- content = self.get_content(media, name)
- if content:
- content.accepted = True
- self.on_session_state_changed(content)
-
- def reject_content(self, media, name=None):
- content = self.get_content(media, name)
- if content:
- if self.state == JingleStates.ACTIVE:
- self.__content_reject(content)
- content.destroy()
- self.on_session_state_changed()
-
- def end_session(self):
- """
- Called when user stops or cancel session in UI
- """
- reason = nbxmpp.Node('reason')
- if self.state == JingleStates.ACTIVE:
- reason.addChild('success')
- else:
- reason.addChild('cancel')
- self._session_terminate(reason)
-
- def get_content(self, media=None, name=None):
- if media is None:
- return
- for content in self.contents.values():
- if content.media == media:
- if name is None or content.name == name:
- return content
-
- def add_content(self, name, content, creator='we'):
- """
- Add new content to session. If the session is active, this will send
- proper stanza to update session
-
- Creator must be one of ('we', 'peer', 'initiator', 'responder')
- """
- assert creator in ('we', 'peer', 'initiator', 'responder')
- if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \
- not self.weinitiate):
- creator = 'initiator'
- elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \
- not self.weinitiate):
- creator = 'responder'
- content.creator = creator
- content.name = name
- self.contents[(creator, name)] = content
- if (creator == 'initiator') == self.weinitiate:
- # The content is from us, accept it
- content.accepted = True
-
- def remove_content(self, creator, name, reason=None):
- """
- Remove the content `name` created by `creator`
- by sending content-remove, or by sending session-terminate if
- there is no content left.
- """
- if (creator, name) in self.contents:
- content = self.contents[(creator, name)]
- self.__content_remove(content, reason)
- self.contents[(creator, name)].destroy()
- if not self.contents:
- self.end_session()
-
- def modify_content(self, creator, name, transport=None):
- '''
- Currently used for transport replacement
- '''
- content = self.contents[(creator, name)]
- transport.set_sid(content.transport.sid)
- transport.set_file_props(content.transport.file_props)
- content.transport = transport
- # The content will have to be resend now that it is modified
- content.sent = False
- content.accepted = True
-
- def on_session_state_changed(self, content=None):
- if self.state == JingleStates.ENDED:
- # Session not yet started, only one action possible: session-initiate
- if self.is_ready() and self.weinitiate:
- self.__session_initiate()
- elif self.state == JingleStates.PENDING:
- # We can either send a session-accept or a content-add
- if self.is_ready() and not self.weinitiate:
- self.__session_accept()
- elif content and (content.creator == 'initiator') == self.weinitiate:
- self.__content_add(content)
- elif content and self.weinitiate:
- self.__content_accept(content)
- elif self.state == JingleStates.ACTIVE:
- # We can either send a content-add or a content-accept. However, if
- # we are sending a file we can only use session_initiate.
- if not content:
- return
- we_created_content = (content.creator == 'initiator') \
- == self.weinitiate
- if we_created_content and content.media == 'file':
- self.__session_initiate()
- if we_created_content:
- # We initiated this content. It's a pending content-add.
- self.__content_add(content)
- else:
- # The other side created this content, we accept it.
- self.__content_accept(content)
-
- def is_ready(self):
- """
- Return True when all codecs and candidates are ready (for all contents)
- """
- return (any((content.is_ready() for content in self.contents.values()))
- and self.accepted)
-
- def accept_session(self):
- """
- Mark the session as accepted
- """
- self.accepted = True
- self.on_session_state_changed()
-
- def start_session(self):
- """
- Mark the session as ready to be started
- """
- self.accepted = True
- self.on_session_state_changed()
-
- def send_session_info(self):
- pass
-
- def send_content_accept(self, content):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('content-accept')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
-
- def send_transport_info(self, content):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('transport-info')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
- self.collect_iq_id(stanza.getID())
-
- def send_description_info(self, content):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('description-info')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
-
- def on_stanza(self, stanza):
- """
- A callback for ConnectionJingle. It gets stanza, then tries to send it to
- all internally registered callbacks. First one to raise
- nbxmpp.NodeProcessed breaks function
- """
- jingle = stanza.getTag('jingle')
- error = stanza.getTag('error')
- if error:
- # it's an iq-error stanza
- action = 'iq-error'
- elif jingle:
- # it's a jingle action
- action = jingle.getAttr('action')
- if action not in self.callbacks:
- self.__send_error(stanza, 'bad-request')
- return
- # FIXME: If we aren't initiated and it's not a session-initiate...
- if action not in ['session-initiate', 'session-terminate'] \
- and self.state == JingleStates.ENDED:
- self.__send_error(stanza, 'item-not-found', 'unknown-session')
- return
- else:
- # it's an iq-result (ack) stanza
- action = 'iq-result'
- callables = self.callbacks[action]
- try:
- for call in callables:
- call(stanza=stanza, jingle=jingle, error=error, action=action)
- except nbxmpp.NodeProcessed:
- pass
- except TieBreak:
- self.__send_error(stanza, 'conflict', 'tiebreak')
- except OutOfOrder:
- # FIXME
- self.__send_error(stanza, 'unexpected-request', 'out-of-order')
- except FailedApplication:
- reason = nbxmpp.Node('reason')
- reason.addChild('failed-application')
- self._session_terminate(reason)
-
- def __ack(self, stanza, jingle, error, action):
- """
- Default callback for action stanzas -- simple ack and stop processing
- """
- response = stanza.buildReply('result')
- response.delChild(response.getQuery())
- self.connection.connection.send(response)
-
- def __on_error(self, stanza, jingle, error, action):
- # FIXME
- text = error.getTagData('text')
- error_name = None
- for child in error.getChildren():
- if child.getNamespace() == nbxmpp.NS_JINGLE_ERRORS:
- error_name = child.getName()
- break
- elif child.getNamespace() == nbxmpp.NS_STANZAS:
- error_name = child.getName()
- self.__dispatch_error(error_name, text, error.getAttr('type'))
-
- def transport_replace(self):
- transport = JingleTransportIBB()
- # For debug only, delete this and replace for a function
- # that will identify contents by its sid
- for creator, name in self.contents:
- self.modify_content(creator, name, transport)
- cont = self.contents[(creator, name)]
- cont.transport = transport
- stanza, jingle = self.__make_jingle('transport-replace')
- self.__append_contents(jingle)
- self.__broadcast(stanza, jingle, None, 'transport-replace')
- self.connection.connection.send(stanza)
- self.state = JingleStates.PENDING
-
- def __on_transport_replace(self, stanza, jingle, error, action):
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']
- if (creator, name) in self.contents:
- transport_ns = content.getTag('transport').getNamespace()
- if transport_ns == nbxmpp.NS_JINGLE_ICE_UDP:
- # FIXME: We don't manage anything else than ICE-UDP now...
- # What was the previous transport?!?
- # Anyway, content's transport is not modifiable yet
- pass
- elif transport_ns == nbxmpp.NS_JINGLE_IBB:
- transport = JingleTransportIBB()
- self.modify_content(creator, name, transport)
- self.state = JingleStates.PENDING
- self.contents[(creator, name)].state = State.TRANSPORT_REPLACE
- self.__ack(stanza, jingle, error, action)
- self.__session_accept()
- self.contents[(creator, name)].start_IBB_transfer()
- else:
- stanza, jingle = self.__make_jingle('transport-reject')
- content = jingle.setTag('content', attrs={'creator': creator,
- 'name': name})
- content.setTag('transport', namespace=transport_ns)
- self.connection.connection.send(stanza)
- raise nbxmpp.NodeProcessed
- else:
- # FIXME: This ressource is unknown to us, what should we do?
- # For now, reject the transport
- stanza, jingle = self.__make_jingle('transport-reject')
- content = jingle.setTag('content', attrs={'creator': creator,
- 'name': name})
- content.setTag('transport', namespace=transport_ns)
- self.connection.connection.send(stanza)
- raise nbxmpp.NodeProcessed
-
- def __on_session_info(self, stanza, jingle, error, action):
- # TODO: active, (un)hold, (un)mute
- payload = jingle.getPayload()
- if payload[0].getName() == 'ringing':
- # ignore ringing
- raise nbxmpp.NodeProcessed
- if self.state != JingleStates.ACTIVE:
- raise OutOfOrder
- for child in payload:
- if child.getName() == 'checksum':
- hash_ = child.getTag('file').getTag(name='hash',
- namespace=nbxmpp.NS_HASHES_2)
- algo = hash_.getAttr('algo')
- if algo in nbxmpp.Hashes2.supported:
- file_props = FilesProp.getFileProp(self.connection.name,
- self.sid)
- file_props.algo = algo
- file_props.hash_ = hash_.getData()
- raise nbxmpp.NodeProcessed
- self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info',
- type_='modify')
- raise nbxmpp.NodeProcessed
-
- def __on_content_remove(self, stanza, jingle, error, action):
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']
- if (creator, name) in self.contents:
- content = self.contents[(creator, name)]
- # TODO: this will fail if content is not an RTP content
- gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(
- None, conn=self.connection, jingle_session=self,
- media=content.media, reason='removed'))
- content.destroy()
- if not self.contents:
- reason = nbxmpp.Node('reason')
- reason.setTag('success')
- self._session_terminate(reason)
-
- def __on_session_accept(self, stanza, jingle, error, action):
- # FIXME
- if self.state != JingleStates.PENDING:
- raise OutOfOrder
- self.state = JingleStates.ACTIVE
-
- @staticmethod
- def __on_content_accept(stanza, jingle, error, action):
- """
- Called when we get content-accept stanza or equivalent one (like
- session-accept)
- """
- # check which contents are accepted
- for content in jingle.iterTags('content'):
- creator = content['creator']
- # TODO
- name = content['name']
-
- def __on_content_add(self, stanza, jingle, error, action):
- if self.state == JingleStates.ENDED:
- raise OutOfOrder
- parse_result = self.__parse_contents(jingle)
- contents = parse_result[0]
- rejected_contents = parse_result[1]
- for name, creator in rejected_contents:
- # TODO
- content = JingleContent()
- self.add_content(name, content, creator)
- self.__content_reject(content)
- self.contents[(content.creator, content.name)].destroy()
- gajim.nec.push_incoming_event(JingleRequestReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- contents=contents))
-
- def __on_session_initiate(self, stanza, jingle, error, action):
- """
- We got a jingle session request from other entity, therefore we are the
- receiver... Unpack the data, inform the user
- """
- if self.state != JingleStates.ENDED:
- raise OutOfOrder
- self.initiator = jingle['initiator']
- self.responder = self.ourjid
- self.peerjid = self.initiator
- self.accepted = False # user did not accept this session yet
- # TODO: If the initiator is unknown to the receiver (e.g., via presence
- # subscription) and the receiver has a policy of not communicating via
- # Jingle with unknown entities, it SHOULD return a <service-unavailable/>
- # error.
- # Lets check what kind of jingle session does the peer want
- contents, contents_rejected, reason_txt = self.__parse_contents(jingle)
- # If we are not receivin a file
- # Check if there's already a session with this user:
- if contents[0][0] != 'file':
- for session in self.connection.iter_jingle_sessions(self.peerjid):
- if session is not self:
- reason = nbxmpp.Node('reason')
- alternative_session = reason.setTag('alternative-session')
- alternative_session.setTagData('sid', session.sid)
- self.__ack(stanza, jingle, error, action)
- self._session_terminate(reason)
- raise nbxmpp.NodeProcessed
- else:
- # Stop if we don't have the requested file or the peer is not
- # allowed to request the file
- request = \
- jingle.getTag('content').getTag('description').getTag('request')
- if request:
- self.request = True
- hash_tag = request.getTag('file').getTag('hash')
- hash_data = hash_tag.getData() if hash_tag else None
- n = request.getTag('file').getTag('name')
- n = n.getData() if n else None
- pjid = gajim.get_jid_without_resource(self.peerjid)
- file_info = self.connection.get_file_info(pjid, hash_data, n,
- self.connection.name)
- if not file_info:
- log.warning('The peer %s is requesting a ' \
- 'file that we dont have or ' \
- 'it is not allowed to request', pjid)
- self.decline_session()
- raise nbxmpp.NodeProcessed
- # If there's no content we understand...
- if not contents:
- # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
- reason = nbxmpp.Node('reason')
- reason.setTag(reason_txt)
- self.__ack(stanza, jingle, error, action)
- self._session_terminate(reason)
- raise nbxmpp.NodeProcessed
- self.state = JingleStates.PENDING
- # Send event about starting a session
- gajim.nec.push_incoming_event(JingleRequestReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- contents=contents))
-
- def __broadcast(self, stanza, jingle, error, action):
- """
- Broadcast the stanza contents to proper content handlers
- """
- #if jingle is None: # it is a iq-result stanza
- # for cn in self.contents.values():
- # cn.on_stanza(stanza, None, error, action)
- # return
- # special case: iq-result stanza does not come with a jingle element
- if action == 'iq-result':
- for cn in self.contents.values():
- cn.on_stanza(stanza, None, error, action)
- return
- for content in jingle.iterTags('content'):
- name = content['name']
- creator = content['creator']
- if (creator, name) not in self.contents:
- text = 'Content %s (created by %s) does not exist' % (name, creator)
- self.__send_error(stanza, 'bad-request', text=text, type_='_modify')
- raise nbxmpp.NodeProcessed
- else:
- cn = self.contents[(creator, name)]
- cn.on_stanza(stanza, content, error, action)
-
- def __on_session_terminate(self, stanza, jingle, error, action):
- self.connection.delete_jingle_session(self.sid)
- reason, text = self.__reason_from_stanza(jingle)
- if reason not in ('success', 'cancel', 'decline'):
- self.__dispatch_error(reason, text)
- if text:
- text = '%s (%s)' % (reason, text)
- else:
- # TODO
- text = reason
- if reason == 'cancel' and self.session_type_ft:
- gajim.nec.push_incoming_event(JingleTransferCancelledEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=None,
- reason=text))
-
- def __broadcast_all(self, stanza, jingle, error, action):
- """
- Broadcast the stanza to all content handlers
- """
- for content in self.contents.values():
- content.on_stanza(stanza, None, error, action)
-
- def __parse_contents(self, jingle):
- # TODO: Needs some reworking
- contents = []
- contents_rejected = []
- reasons = set()
- for element in jingle.iterTags('content'):
- transport = get_jingle_transport(element.getTag('transport'))
- if transport:
- transport.ourjid = self.ourjid
- content_type = get_jingle_content(element.getTag('description'))
- if content_type:
- try:
- if transport:
- content = content_type(self, transport)
- self.add_content(element['name'],
- content, 'peer')
- contents.append((content.media,))
- else:
- reasons.add('unsupported-transports')
- contents_rejected.append((element['name'], 'peer'))
- except JingleContentSetupException:
- reasons.add('failed-application')
- else:
- contents_rejected.append((element['name'], 'peer'))
- reasons.add('unsupported-applications')
- failure_reason = None
- # Store the first reason of failure
- for reason in ('failed-application', 'unsupported-transports',
- 'unsupported-applications'):
- if reason in reasons:
- failure_reason = reason
- break
- return (contents, contents_rejected, failure_reason)
-
- def __dispatch_error(self, error=None, text=None, type_=None):
- if text:
- text = '%s (%s)' % (error, text)
- if type_ != 'modify':
- gajim.nec.push_incoming_event(JingleErrorReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- reason=text or error))
-
- @staticmethod
- def __reason_from_stanza(stanza):
- # TODO: Move to GUI?
- reason = 'success'
- reasons = [
- 'success', 'busy', 'cancel', 'connectivity-error', 'decline',
- 'expired', 'failed-application', 'failed-transport',
- 'general-error', 'gone', 'incompatible-parameters', 'media-error',
- 'security-error', 'timeout', 'unsupported-applications',
- 'unsupported-transports'
- ]
- tag = stanza.getTag('reason')
- text = ''
- if tag:
- text = tag.getTagData('text')
- for r in reasons:
- if tag.getTag(r):
- reason = r
- break
- return (reason, text)
-
- def __make_jingle(self, action, reason=None):
- stanza = nbxmpp.Iq(typ='set', to=nbxmpp.JID(self.peerjid),
- frm=self.ourjid)
- attrs = {
- 'action': action,
- 'sid': self.sid,
- 'initiator' : self.initiator
- }
- jingle = stanza.addChild('jingle', attrs=attrs,
- namespace=nbxmpp.NS_JINGLE)
- if reason is not None:
- jingle.addChild(node=reason)
- return stanza, jingle
-
- def __send_error(self, stanza, error, jingle_error=None, text=None, type_=None):
- err_stanza = nbxmpp.Error(stanza, '%s %s' % (nbxmpp.NS_STANZAS, error))
- err = err_stanza.getTag('error')
- if type_:
- err.setAttr('type', type_)
- if jingle_error:
- err.setTag(jingle_error, namespace=nbxmpp.NS_JINGLE_ERRORS)
- if text:
- err.setTagData('text', text)
- self.connection.connection.send(err_stanza)
- self.__dispatch_error(jingle_error or error, text, type_)
-
- @staticmethod
- def __append_content(jingle, content):
- """
- Append <content/> element to <jingle/> element, with (full=True) or
- without (full=False) <content/> children
- """
- jingle.addChild('content',
- attrs={'name': content.name,
- 'creator': content.creator})
-
- def __append_contents(self, jingle):
- """
- Append all <content/> elements to <jingle/>
- """
- # TODO: integrate with __appendContent?
- # TODO: parameters 'name', 'content'?
- for content in self.contents.values():
- if content.is_ready():
- self.__append_content(jingle, content)
-
- def __session_initiate(self):
- assert self.state == JingleStates.ENDED
- stanza, jingle = self.__make_jingle('session-initiate')
- self.__append_contents(jingle)
- self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
- self.connection.connection.send(stanza)
- self.collect_iq_id(stanza.getID())
- self.state = JingleStates.PENDING
-
- def __session_accept(self):
- assert self.state == JingleStates.PENDING
- stanza, jingle = self.__make_jingle('session-accept')
- self.__append_contents(jingle)
- self.__broadcast(stanza, jingle, None, 'session-accept-sent')
- self.connection.connection.send(stanza)
- self.collect_iq_id(stanza.getID())
- self.state = JingleStates.ACTIVE
-
- def __session_info(self, payload=None):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('session-info')
- if payload:
- jingle.addChild(node=payload)
- self.connection.connection.send(stanza)
-
- def _JingleFileTransfer__session_info(self, payload):
- # For some strange reason when I call
- # self.session.__session_info(payload) from the jingleFileTransfer object
- # within a thread, this method gets called instead. Even though, it
- # isn't being called explicitly.
- self.__session_info(payload)
-
- def _session_terminate(self, reason=None):
- stanza, jingle = self.__make_jingle('session-terminate', reason=reason)
- self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent')
- if self.connection.connection and self.connection.connected >= 2:
- self.connection.connection.send(stanza)
- # TODO: Move to GUI?
- reason, text = self.__reason_from_stanza(jingle)
- if reason not in ('success', 'cancel', 'decline'):
- self.__dispatch_error(reason, text)
- if text:
- text = '%s (%s)' % (reason, text)
- else:
- text = reason
- self.connection.delete_jingle_session(self.sid)
- gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=None,
- reason=text))
-
- def __content_add(self, content):
- # TODO: test
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('content-add')
- self.__append_content(jingle, content)
- self.__broadcast(stanza, jingle, None, 'content-add-sent')
- id_ = self.connection.connection.send(stanza)
- self.collect_iq_id(id_)
-
- def __content_accept(self, content):
- # TODO: test
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('content-accept')
- self.__append_content(jingle, content)
- self.__broadcast(stanza, jingle, None, 'content-accept-sent')
- id_ = self.connection.connection.send(stanza)
- self.collect_iq_id(id_)
-
- def __content_reject(self, content):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('content-reject')
- self.__append_content(jingle, content)
- self.connection.connection.send(stanza)
- # TODO: this will fail if content is not an RTP content
- gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=content.media,
- reason='rejected'))
-
- def __content_modify(self):
- assert self.state != JingleStates.ENDED
-
- def __content_remove(self, content, reason=None):
- assert self.state != JingleStates.ENDED
- if self.connection.connection and self.connection.connected > 1:
- stanza, jingle = self.__make_jingle('content-remove', reason=reason)
- self.__append_content(jingle, content)
- self.connection.connection.send(stanza)
- # TODO: this will fail if content is not an RTP content
- gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=content.media,
- reason='removed'))
-
- def content_negotiated(self, media):
- gajim.nec.push_incoming_event(JingleConnectedReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=media))
diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py
deleted file mode 100644
index 0632dff69..000000000
--- a/src/common/jingle_transport.py
+++ /dev/null
@@ -1,449 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-"""
-Handles Jingle Transports (currently only ICE-UDP)
-"""
-
-import logging
-import socket
-from enum import IntEnum, unique
-import nbxmpp
-from common import gajim
-
-log = logging.getLogger('gajim.c.jingle_transport')
-
-
-transports = {}
-
-def get_jingle_transport(node):
- namespace = node.getNamespace()
- if namespace in transports:
- return transports[namespace](node)
-
-
-@unique
-class TransportType(IntEnum):
- """
- Possible types of a JingleTransport
- """
- ICEUDP = 1
- SOCKS5 = 2
- IBB = 3
-
-
-class JingleTransport:
- """
- An abstraction of a transport in Jingle sessions
- """
-
- __slots__ = ['type_', 'candidates', 'remote_candidates', 'connection',
- 'file_props', 'ourjid', 'sid']
-
- def __init__(self, type_):
- self.type_ = type_
- self.candidates = []
- self.remote_candidates = []
-
- self.connection = None
- self.file_props = None
- self.ourjid = None
- self.sid = None
-
- def _iter_candidates(self):
- for candidate in self.candidates:
- yield self.make_candidate(candidate)
-
- def make_candidate(self, candidate):
- """
- Build a candidate stanza for the given candidate
- """
- pass
-
- def make_transport(self, candidates=None):
- """
- Build a transport stanza with the given candidates (or self.candidates if
- candidates is None)
- """
- if not candidates:
- candidates = list(self._iter_candidates())
- else:
- candidates = (self.make_candidate(candidate) for candidate in candidates)
- transport = nbxmpp.Node('transport', payload=candidates)
- return transport
-
- def parse_transport_stanza(self, transport):
- """
- Return the list of transport candidates from a transport stanza
- """
- return []
-
- def set_connection(self, conn):
- self.connection = conn
- if not self.sid:
- self.sid = self.connection.connection.getAnID()
-
- def set_file_props(self, file_props):
- self.file_props = file_props
-
- def set_our_jid(self, jid):
- self.ourjid = jid
-
- def set_sid(self, sid):
- self.sid = sid
-
-class JingleTransportSocks5(JingleTransport):
- """
- Socks5 transport in jingle scenario
- Note: Don't forget to call set_file_props after initialization
- """
- def __init__(self, node=None):
- JingleTransport.__init__(self, TransportType.SOCKS5)
- self.connection = None
- self.remote_candidates = []
- self.sid = None
- if node and node.getAttr('sid'):
- self.sid = node.getAttr('sid')
-
-
- def make_candidate(self, candidate):
- log.info('candidate dict, %s', candidate)
- attrs = {
- 'cid': candidate['candidate_id'],
- 'host': candidate['host'],
- 'jid': candidate['jid'],
- 'port': candidate['port'],
- 'priority': candidate['priority'],
- 'type': candidate['type']
- }
-
- return nbxmpp.Node('candidate', attrs=attrs)
-
- def make_transport(self, candidates=None, add_candidates=True):
- if add_candidates:
- self._add_local_ips_as_candidates()
- self._add_additional_candidates()
- self._add_proxy_candidates()
- transport = JingleTransport.make_transport(self, candidates)
- else:
- transport = nbxmpp.Node('transport')
- transport.setNamespace(nbxmpp.NS_JINGLE_BYTESTREAM)
- transport.setAttr('sid', self.sid)
- if self.file_props.dstaddr:
- transport.setAttr('dstaddr', self.file_props.dstaddr)
- return transport
-
- def parse_transport_stanza(self, transport):
- candidates = []
- for candidate in transport.iterTags('candidate'):
- typ = 'direct' # default value
- if candidate.has_attr('type'):
- typ = candidate['type']
- cand = {
- 'state': 0,
- 'target': self.ourjid,
- 'host': candidate['host'],
- 'port': int(candidate['port']),
- 'cid': candidate['cid'],
- 'type': typ,
- 'priority': candidate['priority']
- }
- candidates.append(cand)
-
- # we need this when we construct file_props on session-initiation
- if candidates:
- self.remote_candidates = candidates
- return candidates
-
-
- def _add_candidates(self, candidates):
- for cand in candidates:
- in_remote = False
- for cand2 in self.remote_candidates:
- if cand['host'] == cand2['host'] and \
- cand['port'] == cand2['port']:
- in_remote = True
- break
- if not in_remote:
- self.candidates.append(cand)
-
- def _add_local_ips_as_candidates(self):
- if not gajim.config.get_per('accounts', self.connection.name,
- 'ft_send_local_ips'):
- return
- if not self.connection:
- return
- port = int(gajim.config.get('file_transfers_port'))
- #type preference of connection type. XEP-0260 section 2.2
- type_preference = 126
- priority = (2**16) * type_preference
-
- hosts = set()
- local_ip_cand = []
-
- candidate = {
- 'host': self.connection.peerhost[0],
- 'candidate_id': self.connection.connection.getAnID(),
- 'port': port,
- 'type': 'direct',
- 'jid': self.ourjid,
- 'priority': priority
- }
- hosts.add(self.connection.peerhost[0])
- local_ip_cand.append(candidate)
-
- try:
- for addrinfo in socket.getaddrinfo(socket.gethostname(), None):
- addr = addrinfo[4][0]
- if not addr in hosts and not addr.startswith('127.') and \
- addr != '::1':
- candidate = {
- 'host': addr,
- 'candidate_id': self.connection.connection.getAnID(),
- 'port': port,
- 'type': 'direct',
- 'jid': self.ourjid,
- 'priority': priority,
- 'initiator': self.file_props.sender,
- 'target': self.file_props.receiver
- }
- hosts.add(addr)
- local_ip_cand.append(candidate)
- except socket.gaierror:
- pass # ignore address-related errors for getaddrinfo
-
- self._add_candidates(local_ip_cand)
-
- def _add_additional_candidates(self):
- if not self.connection:
- return
- type_preference = 126
- priority = (2**16) * type_preference
- additional_ip_cand = []
- port = int(gajim.config.get('file_transfers_port'))
- ft_add_hosts = gajim.config.get('ft_add_hosts_to_send')
-
- if ft_add_hosts:
- hosts = [e.strip() for e in ft_add_hosts.split(',')]
- for host in hosts:
- candidate = {
- 'host': host,
- 'candidate_id': self.connection.connection.getAnID(),
- 'port': port,
- 'type': 'direct',
- 'jid': self.ourjid,
- 'priority': priority,
- 'initiator': self.file_props.sender,
- 'target': self.file_props.receiver
- }
- additional_ip_cand.append(candidate)
-
- self._add_candidates(additional_ip_cand)
-
- def _add_proxy_candidates(self):
- if not self.connection:
- return
- type_preference = 10
- priority = (2**16) * type_preference
- proxy_cand = []
- socks5conn = self.connection
- proxyhosts = socks5conn._get_file_transfer_proxies_from_config(self.file_props)
-
- if proxyhosts:
- self.file_props.proxyhosts = proxyhosts
-
- for proxyhost in proxyhosts:
- candidate = {
- 'host': proxyhost['host'],
- 'candidate_id': self.connection.connection.getAnID(),
- 'port': int(proxyhost['port']),
- 'type': 'proxy',
- 'jid': proxyhost['jid'],
- 'priority': priority,
- 'initiator': self.file_props.sender,
- 'target': self.file_props.receiver
- }
- proxy_cand.append(candidate)
-
- self._add_candidates(proxy_cand)
-
- def get_content(self):
- sesn = self.connection.get_jingle_session(self.ourjid,
- self.file_props.sid)
- for content in sesn.contents.values():
- if content.transport == self:
- return content
-
- def _on_proxy_auth_ok(self, proxy):
- log.info('proxy auth ok for ' + str(proxy))
- # send activate request to proxy, send activated confirmation to peer
- if not self.connection:
- return
- sesn = self.connection.get_jingle_session(self.ourjid,
- self.file_props.sid)
- if sesn is None:
- return
-
- iq = nbxmpp.Iq(to=proxy['jid'], frm=self.ourjid, typ='set')
- auth_id = "au_" + proxy['sid']
- iq.setID(auth_id)
- query = iq.setTag('query', namespace=nbxmpp.NS_BYTESTREAM)
- query.setAttr('sid', proxy['sid'])
- activate = query.setTag('activate')
- activate.setData(sesn.peerjid)
- iq.setID(auth_id)
- self.connection.connection.send(iq)
-
-
- content = nbxmpp.Node('content')
- content.setAttr('creator', 'initiator')
- content_object = self.get_content()
- content.setAttr('name', content_object.name)
- transport = nbxmpp.Node('transport')
- transport.setNamespace(nbxmpp.NS_JINGLE_BYTESTREAM)
- transport.setAttr('sid', proxy['sid'])
- activated = nbxmpp.Node('activated')
- cid = None
-
- if 'cid' in proxy:
- cid = proxy['cid']
- else:
- for host in self.candidates:
- if host['host'] == proxy['host'] and host['jid'] == proxy['jid'] \
- and host['port'] == proxy['port']:
- cid = host['candidate_id']
- break
- if cid is None:
- raise Exception('cid is missing')
- activated.setAttr('cid', cid)
- transport.addChild(node=activated)
- content.addChild(node=transport)
- sesn.send_transport_info(content)
-
-
-class JingleTransportIBB(JingleTransport):
-
- def __init__(self, node=None, block_sz=None):
-
- JingleTransport.__init__(self, TransportType.IBB)
-
- if block_sz:
- self.block_sz = block_sz
- else:
- self.block_sz = '4096'
-
- self.connection = None
- self.sid = None
- if node and node.getAttr('sid'):
- self.sid = node.getAttr('sid')
-
-
- def make_transport(self):
-
- transport = nbxmpp.Node('transport')
- transport.setNamespace(nbxmpp.NS_JINGLE_IBB)
- transport.setAttr('block-size', self.block_sz)
- transport.setAttr('sid', self.sid)
- return transport
-
-try:
- from gi.repository import Farstream
-except ImportError:
- pass
-
-class JingleTransportICEUDP(JingleTransport):
- def __init__(self, node):
- JingleTransport.__init__(self, TransportType.ICEUDP)
-
- def make_candidate(self, candidate):
- types = {
- Farstream.CandidateType.HOST: 'host',
- Farstream.CandidateType.SRFLX: 'srflx',
- Farstream.CandidateType.PRFLX: 'prflx',
- Farstream.CandidateType.RELAY: 'relay',
- Farstream.CandidateType.MULTICAST: 'multicast'
- }
- attrs = {
- 'component': candidate.component_id,
- 'foundation': '1', # hack
- 'generation': '0',
- 'ip': candidate.ip,
- 'network': '0',
- 'port': candidate.port,
- 'priority': int(candidate.priority), # hack
- 'id': gajim.get_an_id()
- }
- if candidate.type in types:
- attrs['type'] = types[candidate.type]
- if candidate.proto == Farstream.NetworkProtocol.UDP:
- attrs['protocol'] = 'udp'
- else:
- # we actually don't handle properly different tcp options in jingle
- attrs['protocol'] = 'tcp'
- return nbxmpp.Node('candidate', attrs=attrs)
-
- def make_transport(self, candidates=None):
- transport = JingleTransport.make_transport(self, candidates)
- transport.setNamespace(nbxmpp.NS_JINGLE_ICE_UDP)
- if self.candidates and self.candidates[0].username and \
- self.candidates[0].password:
- transport.setAttr('ufrag', self.candidates[0].username)
- transport.setAttr('pwd', self.candidates[0].password)
- return transport
-
- def parse_transport_stanza(self, transport):
- candidates = []
- for candidate in transport.iterTags('candidate'):
- foundation = str(candidate['foundation'])
- component_id = int(candidate['component'])
- ip = str(candidate['ip'])
- port = int(candidate['port'])
- base_ip = None
- base_port = 0
- if candidate['protocol'] == 'udp':
- proto = Farstream.NetworkProtocol.UDP
- else:
- # we actually don't handle properly different tcp options in
- # jingle
- proto = Farstream.NetworkProtocol.TCP
- priority = int(candidate['priority'])
- types = {
- 'host': Farstream.CandidateType.HOST,
- 'srflx': Farstream.CandidateType.SRFLX,
- 'prflx': Farstream.CandidateType.PRFLX,
- 'relay': Farstream.CandidateType.RELAY,
- 'multicast': Farstream.CandidateType.MULTICAST
- }
- if 'type' in candidate and candidate['type'] in types:
- type_ = types[candidate['type']]
- else:
- log.warning('Unknown type %s', candidate['type'])
- type_ = Farstream.CandidateType.HOST
- username = str(transport['ufrag'])
- password = str(transport['pwd'])
- ttl = 0
-
- cand = Farstream.Candidate.new_full(foundation, component_id, ip,
- port, base_ip, base_port,
- proto, priority, type_,
- username, password, ttl)
-
- candidates.append(cand)
- self.remote_candidates.extend(candidates)
- return candidates
-
-transports[nbxmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP
-transports[nbxmpp.NS_JINGLE_BYTESTREAM] = JingleTransportSocks5
-transports[nbxmpp.NS_JINGLE_IBB] = JingleTransportIBB
diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py
deleted file mode 100644
index 10a60d815..000000000
--- a/src/common/jingle_xtls.py
+++ /dev/null
@@ -1,299 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/jingle_xtls.py
-##
-## 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/>.
-##
-
-import logging
-import os
-
-import nbxmpp
-from common import gajim
-
-log = logging.getLogger('gajim.c.jingle_xtls')
-
-
-PYOPENSSL_PRESENT = False
-
-# key-exchange id -> [callback, args], accept that session once key-exchange completes
-pending_contents = {}
-
-def key_exchange_pend(id_, cb, args):
- # args is a list
- pending_contents[id_] = [cb, args]
-
-def approve_pending_content(id_):
- cb = pending_contents[id_][0]
- args = pending_contents[id_][1]
- cb(*args)
-
-try:
- import OpenSSL.SSL
- PYOPENSSL_PRESENT = True
-except ImportError:
- log.info("PyOpenSSL not available")
-
-if PYOPENSSL_PRESENT:
- from OpenSSL import SSL, crypto
- TYPE_RSA = crypto.TYPE_RSA
- TYPE_DSA = crypto.TYPE_DSA
-
-SELF_SIGNED_CERTIFICATE = 'localcert'
-DH_PARAMS = 'dh_params.pem'
-DEFAULT_DH_PARAMS = 'dh4096.pem'
-
-def default_callback(connection, certificate, error_num, depth, return_code):
- log.info("certificate: %s", certificate)
- return return_code
-
-def load_cert_file(cert_path, cert_store=None):
- """
- This is almost identical to the one in nbxmpp.tls_nb
- """
- if not os.path.isfile(cert_path):
- return None
- try:
- f = open(cert_path)
- except IOError as e:
- log.warning('Unable to open certificate file %s: %s', cert_path,
- str(e))
- return None
- lines = f.readlines()
- i = 0
- begin = -1
- for line in lines:
- if 'BEGIN CERTIFICATE' in line:
- begin = i
- elif 'END CERTIFICATE' in line and begin > -1:
- cert = ''.join(lines[begin:i+2])
- try:
- x509cert = OpenSSL.crypto.load_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert)
- if cert_store:
- cert_store.add_cert(x509cert)
- f.close()
- return x509cert
- except OpenSSL.crypto.Error as exception_obj:
- log.warning('Unable to load a certificate from file %s: %s',
- cert_path, exception_obj.args[0][0][2])
- except:
- log.warning('Unknown error while loading certificate from file '
- '%s', cert_path)
- begin = -1
- i += 1
- f.close()
-
-def get_context(fingerprint, verify_cb=None, remote_jid=None):
- """
- constructs and returns the context objects
- """
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- flags = (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3 | SSL.OP_SINGLE_DH_USE \
- | SSL.OP_NO_TICKET)
- ctx.set_options(flags)
- ctx.set_cipher_list('HIGH:!aNULL:!3DES')
-
- if fingerprint == 'server': # for testing purposes only
- ctx.set_verify(SSL.VERIFY_NONE|SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
- verify_cb or default_callback)
- elif fingerprint == 'client':
- ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback)
-
- cert_name = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE)
- ctx.use_privatekey_file((cert_name + '.pkey').encode('utf-8'))
- ctx.use_certificate_file((cert_name + '.cert').encode('utf-8'))
-
- # Try to load Diffie-Hellman parameters.
- # First try user DH parameters, if this fails load the default DH parameters
- dh_params_name = os.path.join(gajim.MY_CERT_DIR, DH_PARAMS)
- try:
- with open(dh_params_name, "r") as dh_params_file:
- ctx.load_tmp_dh(dh_params_name.encode('utf-8'))
- except FileNotFoundError as err:
- default_dh_params_name = os.path.join(gajim.DATA_DIR,
- 'other', DEFAULT_DH_PARAMS)
- try:
- with open(default_dh_params_name, "r") as default_dh_params_file:
- ctx.load_tmp_dh(default_dh_params_name.encode('utf-8'))
- except FileNotFoundError as err:
- log.error('Unable to load default DH parameter file: %s, %s',
- default_dh_params_name, err)
- raise
-
- if remote_jid:
- store = ctx.get_cert_store()
- path = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH),
- remote_jid) + '.cert'
- if os.path.exists(path):
- load_cert_file(path, cert_store=store)
- log.debug('certificate file %s loaded fingerprint %s',
- path, fingerprint)
- return ctx
-
-def read_cert(certpath):
- certificate = ''
- with open(certpath, 'r') as certfile:
- for line in certfile.readlines():
- if not line.startswith('-'):
- certificate += line
- return certificate
-
-def send_cert(con, jid_from, sid):
- certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE) + \
- '.cert'
- certificate = read_cert(certpath)
- iq = nbxmpp.Iq('result', to=jid_from)
- iq.setAttr('id', sid)
-
- pubkey = iq.setTag('pubkeys')
- pubkey.setNamespace(nbxmpp.NS_PUBKEY_PUBKEY)
-
- keyinfo = pubkey.setTag('keyinfo')
- name = keyinfo.setTag('name')
- name.setData('CertificateHash')
- cert = keyinfo.setTag('x509cert')
- cert.setData(certificate)
-
- con.send(iq)
-
-def handle_new_cert(con, obj, jid_from):
- jid = gajim.get_jid_without_resource(jid_from)
- certpath = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), jid)
- certpath += '.cert'
-
- id_ = obj.getAttr('id')
-
- x509cert = obj.getTag('pubkeys').getTag('keyinfo').getTag('x509cert')
-
- cert = x509cert.getData()
-
- f = open(certpath, 'w')
- f.write('-----BEGIN CERTIFICATE-----\n')
- f.write(cert)
- f.write('-----END CERTIFICATE-----\n')
- f.close()
-
- approve_pending_content(id_)
-
-def check_cert(jid, fingerprint):
- certpath = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), jid)
- certpath += '.cert'
- if os.path.exists(certpath):
- cert = load_cert_file(certpath)
- if cert:
- try:
- digest_algo = cert.get_signature_algorithm().decode('utf-8').\
- split('With')[0]
- except AttributeError as e:
- # Old py-OpenSSL is missing get_signature_algorithm
- digest_algo = "sha256"
- if cert.digest(digest_algo) == fingerprint:
- return True
- return False
-
-def send_cert_request(con, to_jid):
- iq = nbxmpp.Iq('get', to=to_jid)
- id_ = con.connection.getAnID()
- iq.setAttr('id', id_)
- pubkey = iq.setTag('pubkeys')
- pubkey.setNamespace(nbxmpp.NS_PUBKEY_PUBKEY)
- con.connection.send(iq)
- return str(id_)
-
-# the following code is partly due to pyopenssl examples
-
-def createKeyPair(type_, bits):
- """
- Create a public/private key pair.
-
- Arguments: type_ - Key type, must be one of TYPE_RSA and TYPE_DSA
- bits - Number of bits to use in the key
- Returns: The public/private key pair in a PKey object
- """
- pkey = crypto.PKey()
- pkey.generate_key(type_, bits)
- return pkey
-
-def createCertRequest(pkey, digest="sha256", **name):
- """
- Create a certificate request.
-
- Arguments: pkey - The key to associate with the request
- digest - Digestion method to use for signing, default is sha256
- **name - The name of the subject of the request, possible
- arguments are:
- C - Country name
- ST - State or province name
- L - Locality name
- O - Organization name
- OU - Organizational unit name
- CN - Common name
- emailAddress - E-mail address
- Returns: The certificate request in an X509Req object
- """
- req = crypto.X509Req()
- subj = req.get_subject()
-
- for (key, value) in name.items():
- setattr(subj, key, value)
-
- req.set_pubkey(pkey)
- req.sign(pkey, digest)
- return req
-
-def createCertificate(req, issuerCert, issuerKey, serial, notBefore, notAfter, digest="sha256"):
- """
- Generate a certificate given a certificate request.
-
- Arguments: req - Certificate reqeust to use
- issuerCert - The certificate of the issuer
- issuerKey - The private key of the issuer
- serial - Serial number for the certificate
- notBefore - Timestamp (relative to now) when the certificate
- starts being valid
- notAfter - Timestamp (relative to now) when the certificate
- stops being valid
- digest - Digest method to use for signing, default is sha256
- Returns: The signed certificate in an X509 object
- """
- cert = crypto.X509()
- cert.set_serial_number(serial)
- cert.gmtime_adj_notBefore(notBefore)
- cert.gmtime_adj_notAfter(notAfter)
- cert.set_issuer(issuerCert.get_subject())
- cert.set_subject(req.get_subject())
- cert.set_pubkey(req.get_pubkey())
- cert.sign(issuerKey, digest)
- return cert
-
-def make_certs(filepath, CN):
- """
- make self signed certificates
- filepath : absolute path of certificate file, will be appended the '.pkey'
- and '.cert' extensions
- CN : common name
- """
- key = createKeyPair(TYPE_RSA, 4096)
- req = createCertRequest(key, CN=CN)
- cert = createCertificate(req, req, key, 0, 0, 60*60*24*365*5) # five years
- with open(filepath + '.pkey', 'wb') as f:
- os.chmod(filepath + '.pkey', 0o600)
- f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
- with open(filepath + '.cert', 'wb') as f:
- f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
-
-
-if __name__ == '__main__':
- make_certs('./selfcert', 'gajim')
diff --git a/src/common/location_listener.py b/src/common/location_listener.py
deleted file mode 100644
index 6542244f4..000000000
--- a/src/common/location_listener.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/common/location_listener.py
-##
-## Copyright (C) 2009-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## 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/>.
-##
-
-from datetime import datetime
-
-from common import gajim
-from common import dbus_support
-if dbus_support.supported:
- import dbus
-
-class LocationListener:
- _instance = None
- @classmethod
- def get(cls):
- if cls._instance is None:
- cls._instance = cls()
- return cls._instance
-
- def __init__(self):
- self._data = {}
-
- def get_data(self):
- bus = dbus.SessionBus()
- try:
- # Initializes Geoclue.
- obj = bus.get_object('org.freedesktop.Geoclue.Master',
- '/org/freedesktop/Geoclue/Master')
- # get MasterClient path
- path = obj.Create()
- # get MasterClient
- cli = bus.get_object('org.freedesktop.Geoclue.Master', path)
- cli.SetRequirements(1, 0, True, 1023)
-
- self._get_address(cli)
- self._get_position(cli)
- except:
- self._on_geoclue_position_changed()
- return
-
-
- def _get_address(self, cli):
- bus = dbus.SessionBus()
- cli.AddressStart()
- # Check that there is a provider
- name, description, service, path = cli.GetAddressProvider()
- if path:
- provider = bus.get_object(service, path)
- timestamp, address, accuracy = provider.GetAddress()
- self._on_geoclue_address_changed(timestamp, address, accuracy)
-
- def _get_position(self, cli):
- bus = dbus.SessionBus()
- cli.PositionStart()
- # Check that there is a provider
- name, description, service, path = cli.GetPositionProvider()
- if path:
- provider = bus.get_object(service, path)
- fields, timestamp, lat, lon, alt, accuracy = provider.GetPosition()
- self._on_geoclue_position_changed(fields, timestamp, lat, lon, alt,
- accuracy)
-
- def start(self):
- self.location_info = {}
- self.get_data()
- bus = dbus.SessionBus()
- # Geoclue
- bus.add_signal_receiver(self._on_geoclue_address_changed,
- 'AddressChanged', 'org.freedesktop.Geoclue.Address')
- bus.add_signal_receiver(self._on_geoclue_position_changed,
- 'PositionChanged', 'org.freedesktop.Geoclue.Position')
-
- def shut_down(self):
- pass
-
- def _on_geoclue_address_changed(self, timestamp=None, address=None,
- accuracy=None):
- # update data with info we just received
- if address is None:
- address = {}
- for field in ['country', 'countrycode', 'locality', 'postalcode',
- 'region', 'street']:
- self._data[field] = address.get(field, None)
- if timestamp:
- self._data['timestamp'] = self._timestamp_to_utc(timestamp)
- if accuracy:
- # in PEP it's horizontal accuracy
- self._data['accuracy'] = accuracy[1]
- self._send_location()
-
- def _on_geoclue_position_changed(self, fields=None, timestamp=None, lat=None,
- lon=None, alt=None, accuracy=None):
- if fields is None:
- fields = []
- # update data with info we just received
- _dict = {'lat': lat, 'lon': lon, 'alt': alt}
- for field in _dict:
- if _dict[field] is not None:
- self._data[field] = _dict[field]
- if timestamp:
- self._data['timestamp'] = self._timestamp_to_utc(timestamp)
- if accuracy:
- # in PEP it's horizontal accuracy
- self._data['accuracy'] = accuracy[1]
- self._send_location()
-
- def _send_location(self):
- accounts = gajim.connections.keys()
- for acct in accounts:
- if not gajim.account_is_connected(acct):
- continue
- if not gajim.config.get_per('accounts', acct, 'publish_location'):
- continue
- if self.location_info == self._data:
- continue
- if 'timestamp' in self.location_info and 'timestamp' in self._data:
- last_data = self.location_info.copy()
- del last_data['timestamp']
- new_data = self._data.copy()
- del new_data['timestamp']
- if last_data == new_data:
- continue
- gajim.connections[acct].send_location(self._data)
- self.location_info = self._data.copy()
-
- def _timestamp_to_utc(self, timestamp):
- time = datetime.utcfromtimestamp(timestamp)
- return time.strftime('%Y-%m-%dT%H:%MZ')
-
-def enable():
- listener = LocationListener.get()
- listener.start()
-
-def disable():
- listener = LocationListener.get()
- listener.shut_down()
diff --git a/src/common/logger.py b/src/common/logger.py
deleted file mode 100644
index b9221e8ba..000000000
--- a/src/common/logger.py
+++ /dev/null
@@ -1,1193 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/logger.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-##
-## 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/>.
-##
-
-"""
-This module allows to access the on-disk database of logs
-"""
-
-import os
-import sys
-import time
-import datetime
-import json
-from collections import namedtuple
-from gzip import GzipFile
-from io import BytesIO
-from gi.repository import GLib
-from enum import IntEnum, unique
-
-from common import exceptions
-from common import gajim
-from common import ged
-
-import sqlite3 as sqlite
-
-LOG_DB_PATH = gajim.gajimpaths['LOG_DB']
-LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH)
-CACHE_DB_PATH = gajim.gajimpaths['CACHE_DB']
-
-import logging
-log = logging.getLogger('gajim.c.logger')
-
-@unique
-class JIDConstant(IntEnum):
- NORMAL_TYPE = 0
- ROOM_TYPE = 1
-
-@unique
-class KindConstant(IntEnum):
- STATUS = 0
- GCSTATUS = 1
- GC_MSG = 2
- SINGLE_MSG_RECV = 3
- CHAT_MSG_RECV = 4
- SINGLE_MSG_SENT = 5
- CHAT_MSG_SENT = 6
- ERROR = 7
-
-@unique
-class ShowConstant(IntEnum):
- ONLINE = 0
- CHAT = 1
- AWAY = 2
- XA = 3
- DND = 4
- OFFLINE = 5
-
-@unique
-class TypeConstant(IntEnum):
- AIM = 0
- GG = 1
- HTTP_WS = 2
- ICQ = 3
- MSN = 4
- QQ = 5
- SMS = 6
- SMTP = 7
- TLEN = 8
- YAHOO = 9
- NEWMAIL = 10
- RSS = 11
- WEATHER = 12
- MRIM = 13
- NO_TRANSPORT = 14
-
-@unique
-class SubscriptionConstant(IntEnum):
- NONE = 0
- TO = 1
- FROM = 2
- BOTH = 3
-
-class Logger:
- def __init__(self):
- self.jids_already_in = [] # holds jids that we already have in DB
- self.con = None
- self.commit_timout_id = None
-
- if not os.path.exists(LOG_DB_PATH):
- # this can happen only the first time (the time we create the db)
- # db is not created here but in src/common/checks_paths.py
- return
- self.init_vars()
- if not os.path.exists(CACHE_DB_PATH):
- # this can happen cache database is not present when gajim is launched
- # db will be created in src/common/checks_paths.py
- return
- self.attach_cache_database()
- gajim.ged.register_event_handler('gc-message-received',
- ged.POSTCORE, self._nec_gc_message_received)
-
- def dispatch(self, event, error):
- gajim.ged.raise_event(event, None, str(error))
-
- def close_db(self):
- if self.con:
- self.con.close()
- self.con = None
- self.cur = None
-
- def open_db(self):
- self.close_db()
-
- # FIXME: sqlite3_open wants UTF8 strings. So a path with
- # non-ascii chars doesn't work. See #2812 and
- # http://lists.initd.org/pipermail/pysqlite/2005-August/000134.html
- back = os.getcwd()
- os.chdir(LOG_DB_FOLDER)
-
- # if locked, wait up to 20 sec to unlock
- # before raise (hopefully should be enough)
-
- self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0,
- isolation_level='IMMEDIATE')
- os.chdir(back)
- self.cur = self.con.cursor()
- self.set_synchronous(False)
-
- def attach_cache_database(self):
- try:
- self.cur.execute("ATTACH DATABASE '%s' AS cache" % \
- CACHE_DB_PATH.replace("'", "''"))
- except sqlite.Error as e:
- log.debug("Failed to attach cache database: %s" % str(e))
-
- def set_synchronous(self, sync):
- try:
- if sync:
- self.cur.execute("PRAGMA synchronous = NORMAL")
- else:
- self.cur.execute("PRAGMA synchronous = OFF")
- except sqlite.Error as e:
- log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e)))
-
- def init_vars(self):
- self.open_db()
- self.get_jids_already_in_db()
-
- def _really_commit(self):
- try:
- self.con.commit()
- except sqlite.OperationalError as e:
- print(str(e), file=sys.stderr)
- self.commit_timout_id = None
- return False
-
- def _timeout_commit(self):
- if self.commit_timout_id:
- return
- self.commit_timout_id = GLib.timeout_add(500, self._really_commit)
-
- def simple_commit(self, sql_to_commit):
- """
- Helper to commit
- """
- self.cur.execute(sql_to_commit)
- self._timeout_commit()
-
- def get_jids_already_in_db(self):
- try:
- self.cur.execute('SELECT jid FROM jids')
- # list of tuples: [('aaa@bbb',), ('cc@dd',)]
- rows = self.cur.fetchall()
- except sqlite.DatabaseError:
- raise exceptions.DatabaseMalformed(LOG_DB_PATH)
- self.jids_already_in = []
- for row in rows:
- # row[0] is first item of row (the only result here, the jid)
- if row[0] == '':
- # malformed jid, ignore line
- pass
- else:
- self.jids_already_in.append(row[0])
-
- def get_jids_in_db(self):
- return self.jids_already_in
-
- def jid_is_from_pm(self, jid):
- """
- If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
- is not a normal guy and nkour is not his resource? we ask if gajim@conf
- is already in jids (with type room jid) this fails if user disables
- logging for room and only enables for pm (so higly unlikely) and if we
- fail we do not go chaos (user will see the first pm as if it was message
- in room's public chat) and after that all okay
- """
- if jid.find('/') > -1:
- possible_room_jid = jid.split('/', 1)[0]
- return self.jid_is_room_jid(possible_room_jid)
- else:
- # it's not a full jid, so it's not a pm one
- return False
-
- def jid_is_room_jid(self, jid):
- """
- Return True if it's a room jid, False if it's not, None if we don't know
- """
- self.cur.execute('SELECT type FROM jids WHERE jid=?', (jid,))
- row = self.cur.fetchone()
- if row is None:
- return None
- else:
- if row[0] == JIDConstant.ROOM_TYPE:
- return True
- return False
-
- def get_jid_id(self, jid, typestr=None):
- """
- jids table has jid and jid_id logs table has log_id, jid_id,
- contact_name, time, kind, show, message so to ask logs we need jid_id
- that matches our jid in jids table this method wants jid and returns the
- jid_id for later sql-ing on logs typestr can be 'ROOM' or anything else
- depending on the type of JID and is only needed to be specified when the
- JID is new in DB
- """
- if jid.find('/') != -1: # if it has a /
- jid_is_from_pm = self.jid_is_from_pm(jid)
- if not jid_is_from_pm: # it's normal jid with resource
- jid = jid.split('/', 1)[0] # remove the resource
- if jid in self.jids_already_in: # we already have jids in DB
- self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid])
- row = self.cur.fetchone()
- if row:
- return row[0]
- # oh! a new jid :), we add it now
- if typestr == 'ROOM':
- typ = JIDConstant.ROOM_TYPE
- else:
- typ = JIDConstant.NORMAL_TYPE
- try:
- self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)', (jid,
- typ))
- self.con.commit()
- except sqlite.IntegrityError:
- # Jid already in DB, maybe added by another instance. re-read DB
- self.get_jids_already_in_db()
- return self.get_jid_id(jid, typestr)
- except sqlite.OperationalError as e:
- raise exceptions.PysqliteOperationalError(str(e))
- jid_id = self.cur.lastrowid
- self.jids_already_in.append(jid)
- return jid_id
-
- def convert_human_values_to_db_api_values(self, kind, show):
- """
- Convert from string style to constant ints for db
- """
- if kind == 'status':
- kind_col = KindConstant.STATUS
- elif kind == 'gcstatus':
- kind_col = KindConstant.GCSTATUS
- elif kind == 'gc_msg':
- kind_col = KindConstant.GC_MSG
- elif kind == 'single_msg_recv':
- kind_col = KindConstant.SINGLE_MSG_RECV
- elif kind == 'single_msg_sent':
- kind_col = KindConstant.SINGLE_MSG_SENT
- elif kind == 'chat_msg_recv':
- kind_col = KindConstant.CHAT_MSG_RECV
- elif kind == 'chat_msg_sent':
- kind_col = KindConstant.CHAT_MSG_SENT
- elif kind == 'error':
- kind_col = KindConstant.ERROR
-
- if show == 'online':
- show_col = ShowConstant.ONLINE
- elif show == 'chat':
- show_col = ShowConstant.CHAT
- elif show == 'away':
- show_col = ShowConstant.AWAY
- elif show == 'xa':
- show_col = ShowConstant.XA
- elif show == 'dnd':
- show_col = ShowConstant.DND
- elif show == 'offline':
- show_col = ShowConstant.OFFLINE
- elif show is None:
- show_col = None
- else: # invisible in GC when someone goes invisible
- # it's a RFC violation .... but we should not crash
- show_col = 'UNKNOWN'
-
- return kind_col, show_col
-
- def convert_human_transport_type_to_db_api_values(self, type_):
- """
- Convert from string style to constant ints for db
- """
- if type_ == 'aim':
- return TypeConstant.AIM
- if type_ == 'gadu-gadu':
- return TypeConstant.GG
- if type_ == 'http-ws':
- return TypeConstant.HTTP_WS
- if type_ == 'icq':
- return TypeConstant.ICQ
- if type_ == 'msn':
- return TypeConstant.MSN
- if type_ == 'qq':
- return TypeConstant.QQ
- if type_ == 'sms':
- return TypeConstant.SMS
- if type_ == 'smtp':
- return TypeConstant.SMTP
- if type_ in ('tlen', 'x-tlen'):
- return TypeConstant.TLEN
- if type_ == 'yahoo':
- return TypeConstant.YAHOO
- if type_ == 'newmail':
- return TypeConstant.NEWMAIL
- if type_ == 'rss':
- return TypeConstant.RSS
- if type_ == 'weather':
- return TypeConstant.WEATHER
- if type_ == 'mrim':
- return TypeConstant.MRIM
- if type_ == 'jabber':
- return TypeConstant.NO_TRANSPORT
- return None
-
- def convert_api_values_to_human_transport_type(self, type_id):
- """
- Convert from constant ints for db to string style
- """
- if type_id == TypeConstant.AIM:
- return 'aim'
- if type_id == TypeConstant.GG:
- return 'gadu-gadu'
- if type_id == TypeConstant.HTTP_WS:
- return 'http-ws'
- if type_id == TypeConstant.ICQ:
- return 'icq'
- if type_id == TypeConstant.MSN:
- return 'msn'
- if type_id == TypeConstant.QQ:
- return 'qq'
- if type_id == TypeConstant.SMS:
- return 'sms'
- if type_id == TypeConstant.SMTP:
- return 'smtp'
- if type_id == TypeConstant.TLEN:
- return 'tlen'
- if type_id == TypeConstant.YAHOO:
- return 'yahoo'
- if type_id == TypeConstant.NEWMAIL:
- return 'newmail'
- if type_id == TypeConstant.RSS:
- return 'rss'
- if type_id == TypeConstant.WEATHER:
- return 'weather'
- if type_id == TypeConstant.MRIM:
- return 'mrim'
- if type_id == TypeConstant.NO_TRANSPORT:
- return 'jabber'
-
- def convert_human_subscription_values_to_db_api_values(self, sub):
- """
- Convert from string style to constant ints for db
- """
- if sub == 'none':
- return SubscriptionConstant.NONE
- if sub == 'to':
- return SubscriptionConstant.TO
- if sub == 'from':
- return SubscriptionConstant.FROM
- if sub == 'both':
- return SubscriptionConstant.BOTH
-
- def convert_db_api_values_to_human_subscription_values(self, sub):
- """
- Convert from constant ints for db to string style
- """
- if sub == SubscriptionConstant.NONE:
- return 'none'
- if sub == SubscriptionConstant.TO:
- return 'to'
- if sub == SubscriptionConstant.FROM:
- return 'from'
- if sub == SubscriptionConstant.BOTH:
- return 'both'
-
- def commit_to_db(self, values, write_unread=False):
- sql = '''INSERT INTO logs (jid_id, contact_name, time, kind, show,
- message, subject, additional_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'''
- try:
- self.cur.execute(sql, values)
- except sqlite.OperationalError as e:
- raise exceptions.PysqliteOperationalError(str(e))
- except sqlite.DatabaseError:
- raise exceptions.DatabaseMalformed(LOG_DB_PATH)
- message_id = None
- if write_unread:
- try:
- self.con.commit()
- message_id = self.cur.lastrowid
- except sqlite.OperationalError as e:
- print(str(e), file=sys.stderr)
- else:
- self._timeout_commit()
- if message_id:
- self.insert_unread_events(message_id, values[0])
- return message_id
-
- def insert_unread_events(self, message_id, jid_id):
- """
- Add unread message with id: message_id
- """
- sql = 'INSERT INTO unread_messages VALUES (%d, %d, 0)' % (message_id,
- jid_id)
- self.simple_commit(sql)
-
- def set_read_messages(self, message_ids):
- """
- Mark all messages with ids in message_ids as read
- """
- ids = ','.join([str(i) for i in message_ids])
- sql = 'DELETE FROM unread_messages WHERE message_id IN (%s)' % ids
- self.simple_commit(sql)
-
- def set_shown_unread_msgs(self, msg_log_id):
- """
- Mark unread message as shown un GUI
- """
- sql = 'UPDATE unread_messages SET shown = 1 where message_id = %s' % \
- msg_log_id
- self.simple_commit(sql)
-
- def reset_shown_unread_messages(self):
- """
- Set shown field to False in unread_messages table
- """
- sql = 'UPDATE unread_messages SET shown = 0'
- self.simple_commit(sql)
-
- def get_unread_msgs(self):
- """
- Get all unread messages
- """
- all_messages = []
- try:
- self.cur.execute(
- 'SELECT message_id, shown from unread_messages')
- unread_results = self.cur.fetchall()
- except Exception:
- unread_results = []
- for message in unread_results:
- msg_log_id = message[0]
- shown = message[1]
- # here we get infos for that message, and related jid from jids table
- # do NOT change order of SELECTed things, unless you change function(s)
- # that called this function
- self.cur.execute('''
- SELECT logs.log_line_id, logs.message, logs.time, logs.subject,
- jids.jid, logs.additional_data
- FROM logs, jids
- WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id
- ''' % msg_log_id
- )
- results = self.cur.fetchall()
- if len(results) == 0:
- # Log line is no more in logs table. remove it from unread_messages
- self.set_read_messages([msg_log_id])
- continue
- results[0] = list(results[0])
- results[0][5] = json.loads(results[0][5])
- results[0].append(shown)
- all_messages.append(results[0])
- return all_messages
-
- def write(self, kind, jid, message=None, show=None, tim=None, subject=None,
- additional_data=None, mam_query=False):
- """
- Write a row (status, gcstatus, message etc) to logs database
-
- kind can be status, gcstatus, gc_msg, (we only recv for those 3),
- single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent we cannot
- know if it is pm or normal chat message, we try to guess see
- jid_is_from_pm()
-
- We analyze jid and store it as follows:
- jids.jid text column will hold JID if TC-related, room_jid if GC-related,
- ROOM_JID/nick if pm-related.
- """
-
- if additional_data is None:
- additional_data = {}
- if self.jids_already_in == []: # only happens if we just created the db
- self.open_db()
-
- contact_name_col = None # holds nickname for kinds gcstatus, gc_msg
- # message holds the message unless kind is status or gcstatus,
- # then it holds status message
- message_col = message
- subject_col = subject
- additional_data_col = json.dumps(additional_data)
- if tim:
- time_col = float(tim)
- else:
- time_col = float(time.time())
-
- kind_col, show_col = self.convert_human_values_to_db_api_values(kind,
- show)
-
- write_unread = False
- try:
- # now we may have need to do extra care for some values in columns
- if kind == 'status': # we store (not None) time, jid, show, msg
- # status for roster items
- jid_id = self.get_jid_id(jid)
- if show is None: # show is None (xmpp), but we say that 'online'
- show_col = ShowConstant.ONLINE
-
- elif kind == 'gcstatus':
- # status in ROOM (for pm status see status)
- if show is None: # show is None (xmpp), but we say that 'online'
- show_col = ShowConstant.ONLINE
- jid, nick = jid.split('/', 1)
-
- # re-get jid_id for the new jid
- jid_id = self.get_jid_id(jid, 'ROOM')
- contact_name_col = nick
-
- elif kind == 'gc_msg':
- if jid.find('/') != -1: # if it has a /
- jid, nick = jid.split('/', 1)
- else:
- # it's server message f.e. error message
- # when user tries to ban someone but he's not allowed to
- nick = None
-
- # re-get jid_id for the new jid
- jid_id = self.get_jid_id(jid, 'ROOM')
-
- contact_name_col = nick
- else:
- jid_id = self.get_jid_id(jid)
- if kind == 'chat_msg_recv':
- if not self.jid_is_from_pm(jid) and not mam_query:
- # Save in unread table only if it's not a pm
- write_unread = True
-
- if show_col == 'UNKNOWN': # unknown show, do not log
- return
-
- values = (jid_id, contact_name_col, time_col, kind_col, show_col,
- message_col, subject_col, additional_data_col)
- return self.commit_to_db(values, write_unread)
-
- except (exceptions.DatabaseMalformed,
- exceptions.PysqliteOperationalError) as error:
- self.dispatch('DB_ERROR', error)
-
- def get_last_conversation_lines(self, jid, restore_how_many_rows,
- pending_how_many, timeout, account):
- """
- Accept how many rows to restore and when to time them out (in minutes)
- (mark them as too old) and number of messages that are in queue and are
- already logged but pending to be viewed, returns a list of tuples
- containg time, kind, message, subject list with empty tuple if nothing
- found to meet our demands
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return []
- where_sql, jid_tuple = self._build_contact_where(account, jid)
-
- now = int(float(time.time()))
- timed_out = now - (timeout * 60) # before that they are too old
- # so if we ask last 5 lines and we have 2 pending we get
- # 3 - 8 (we avoid the last 2 lines but we still return 5 asked)
- try:
- self.cur.execute('''
- SELECT time, kind, message, subject, additional_data FROM logs
- WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d
- ORDER BY time DESC LIMIT %d OFFSET %d
- ''' % (where_sql, KindConstant.SINGLE_MSG_RECV,
- KindConstant.CHAT_MSG_RECV, KindConstant.SINGLE_MSG_SENT,
- KindConstant.CHAT_MSG_SENT, KindConstant.ERROR, timed_out,
- restore_how_many_rows, pending_how_many), jid_tuple)
-
- results = self.cur.fetchall()
- messages = []
- for entry in results:
- additional_data = json.loads(entry[4])
- parsed_entry = entry[:4] + (additional_data, ) + entry[5:]
- messages.append(parsed_entry)
- except sqlite.DatabaseError:
- self.dispatch('DB_ERROR',
- exceptions.DatabaseMalformed(LOG_DB_PATH))
- return []
-
- messages.reverse()
- return messages
-
- def get_unix_time_from_date(self, year, month, day):
- # year (fe 2005), month (fe 11), day (fe 25)
- # returns time in seconds for the second that starts that date since epoch
- # gimme unixtime from year month day:
- d = datetime.date(year, month, day)
- local_time = d.timetuple() # time tuple (compat with time.localtime())
- # we have time since epoch baby :)
- start_of_day = int(time.mktime(local_time))
- return start_of_day
-
- Message = namedtuple('Message',
- ['contact_name', 'time', 'kind', 'show', 'message', 'subject',
- 'additional_data', 'log_line_id'])
-
- def get_conversation_for_date(self, jid, year, month, day, account):
- """
- Load the complete conversation with a given jid on a specific date
-
- The conversation contains all messages that were exchanged between
- `account` and `jid` on the day specified by `year`, `month` and `day`,
- where `month` and `day` are 1-based.
-
- The conversation will be returned as a list of single messages of type
- `Logger.Message`. Messages in the list are sorted chronologically. An
- empty list will be returned if there are no messages in the log database
- for the requested combination of `jid` and `account` on the given date.
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return []
- where_sql, jid_tuple = self._build_contact_where(account, jid)
-
- start_of_day = self.get_unix_time_from_date(year, month, day)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_day = start_of_day + seconds_in_a_day - 1
-
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject,
- additional_data, log_line_id
- FROM logs
- WHERE (%s)
- AND time BETWEEN %d AND %d
- ORDER BY time
- ''' % (where_sql, start_of_day, last_second_of_day), jid_tuple)
-
- results = [self.Message(*row) for row in self.cur.fetchall()]
- for message in results:
- message._replace(additional_data=json.loads(message.additional_data))
-
- return results
-
- def search_log(self, jid, query, account, year=None, month=None, day=None):
- """
- Search the conversation log for messages containing the `query` string.
-
- The search can either span the complete log for the given `account` and
- `jid` or be restriced to a single day by specifying `year`, `month` and
- `day`, where `month` and `day` are 1-based.
-
- All messages matching the specified criteria will be returned in a list
- containing tuples of type `Logger.Message`. If no messages match the
- criteria, an empty list will be returned.
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return []
-
- where_sql, jid_tuple = self._build_contact_where(account, jid)
- like_sql = '%' + query.replace("'", "''") + '%'
- if year and month and day:
- start_of_day = self.get_unix_time_from_date(year, month, day)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_day = start_of_day + seconds_in_a_day - 1
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject,
- additional_data, log_line_id
- FROM logs
- WHERE (%s) AND message LIKE '%s'
- AND time BETWEEN %d AND %d
- ORDER BY time
- ''' % (where_sql, like_sql, start_of_day, last_second_of_day),
- jid_tuple)
- else:
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject,
- additional_data, log_line_id
- FROM logs
- WHERE (%s) AND message LIKE '%s'
- ORDER BY time
- ''' % (where_sql, like_sql), jid_tuple)
-
- results = [self.Message(*row) for row in self.cur.fetchall()]
- for message in results:
- message._replace(additional_data=json.loads(message.additional_data))
-
- return results
-
- def get_days_with_logs(self, jid, year, month, max_day, account):
- """
- Return the list of days that have logs (not status messages)
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return []
- days_with_logs = []
- where_sql, jid_tuple = self._build_contact_where(account, jid)
-
- # First select all date of month whith logs we want
- start_of_month = self.get_unix_time_from_date(year, month, 1)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_month = start_of_month + (seconds_in_a_day * max_day) - 1
-
- # Select times and 'floor' them to time 0:00
- # (by dividing, they are integers)
- # and take only one of the same values (distinct)
- # Now we have timestamps of time 0:00 of every day with logs
- self.cur.execute('''
- SELECT DISTINCT time/(86400)*86400 FROM logs
- WHERE (%s)
- AND time BETWEEN %d AND %d
- AND kind NOT IN (%d, %d)
- ORDER BY time
- ''' % (where_sql, start_of_month, last_second_of_month,
- KindConstant.STATUS, KindConstant.GCSTATUS), jid_tuple)
- result = self.cur.fetchall()
-
- # convert timestamps to day of month
- for line in result:
- days_with_logs[0:0]=[time.gmtime(line[0])[2]]
-
- return days_with_logs
-
- def get_last_date_that_has_logs(self, jid, account=None, is_room=False):
- """
- Return last time (in seconds since EPOCH) for which we had logs
- (excluding statuses)
- """
- where_sql = ''
- if not is_room:
- where_sql, jid_tuple = self._build_contact_where(account, jid)
- else:
- try:
- jid_id = self.get_jid_id(jid, 'ROOM')
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return None
- where_sql = 'jid_id = ?'
- jid_tuple = (jid_id,)
- self.cur.execute('''
- SELECT MAX(time) FROM logs
- WHERE (%s)
- AND kind NOT IN (%d, %d)
- ''' % (where_sql, KindConstant.STATUS, KindConstant.GCSTATUS),
- jid_tuple)
-
- results = self.cur.fetchone()
- if results is not None:
- result = results[0]
- else:
- result = None
- return result
-
- def get_room_last_message_time(self, jid):
- """
- Return FASTLY last time (in seconds since EPOCH) for which we had logs
- for that room from rooms_last_message_time table
- """
- try:
- jid_id = self.get_jid_id(jid, 'ROOM')
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return None
- where_sql = 'jid_id = %s' % jid_id
- self.cur.execute('''
- SELECT time FROM rooms_last_message_time
- WHERE (%s)
- ''' % (where_sql))
-
- results = self.cur.fetchone()
- if results is not None:
- result = results[0]
- else:
- result = None
- return result
-
- def set_room_last_message_time(self, jid, time):
- """
- Set last time (in seconds since EPOCH) for which we had logs for that
- room in rooms_last_message_time table
- """
- jid_id = self.get_jid_id(jid, 'ROOM')
- # jid_id is unique in this table, create or update :
- sql = 'REPLACE INTO rooms_last_message_time VALUES (%d, %d)' % \
- (jid_id, time)
- self.simple_commit(sql)
-
- def _build_contact_where(self, account, jid):
- """
- Build the where clause for a jid, including metacontacts jid(s) if any
- """
- where_sql = ''
- jid_tuple = ()
- # will return empty list if jid is not associated with
- # any metacontacts
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- for user in family:
- try:
- jid_id = self.get_jid_id(user['jid'])
- except exceptions.PysqliteOperationalError:
- continue
- where_sql += 'jid_id = ?'
- jid_tuple += (jid_id,)
- if user != family[-1]:
- where_sql += ' OR '
- else: # if jid was not associated with metacontacts
- jid_id = self.get_jid_id(jid)
- where_sql = 'jid_id = ?'
- jid_tuple += (jid_id,)
- return where_sql, jid_tuple
-
- def save_transport_type(self, jid, type_):
- """
- Save the type of the transport in DB
- """
- type_id = self.convert_human_transport_type_to_db_api_values(type_)
- if not type_id:
- # unknown type
- return
- self.cur.execute(
- 'SELECT type from transports_cache WHERE transport = "%s"' % jid)
- results = self.cur.fetchall()
- if results:
- result = results[0][0]
- if result == type_id:
- return
- sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\
- (type_id, jid)
- self.simple_commit(sql)
- return
- sql = 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id)
- self.simple_commit(sql)
-
- def get_transports_type(self):
- """
- Return all the type of the transports in DB
- """
- self.cur.execute(
- 'SELECT * from transports_cache')
- results = self.cur.fetchall()
- if not results:
- return {}
- answer = {}
- for result in results:
- answer[result[0]] = self.convert_api_values_to_human_transport_type(
- result[1])
- return answer
-
- # A longer note here:
- # The database contains a blob field. Pysqlite seems to need special care for
- # such fields.
- # When storing, we need to convert string into buffer object (1).
- # When retrieving, we need to convert it back to a string to decompress it.
- # (2)
- # GzipFile needs a file-like object, StringIO emulates file for plain strings
- def iter_caps_data(self):
- """
- Iterate over caps cache data stored in the database
-
- The iterator values are pairs of (node, ver, ext, identities, features):
- identities == {'category':'foo', 'type':'bar', 'name':'boo'},
- features being a list of feature namespaces.
- """
- # get data from table
- # the data field contains binary object (gzipped data), this is a hack
- # to get that data without trying to convert it to unicode
- try:
- self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;')
- except sqlite.OperationalError:
- # might happen when there's no caps_cache table yet
- # -- there's no data to read anyway then
- return
-
- # list of corrupted entries that will be removed
- to_be_removed = []
- for hash_method, hash_, data in self.cur:
- # for each row: unpack the data field
- # (format: (category, type, name, category, type, name, ...
- # ..., 'FEAT', feature1, feature2, ...).join(' '))
- # NOTE: if there's a need to do more gzip, put that to a function
- try:
- data = GzipFile(fileobj=BytesIO(data)).read().decode('utf-8').split('\0')
- except IOError:
- # This data is corrupted. It probably contains non-ascii chars
- to_be_removed.append((hash_method, hash_))
- continue
- i = 0
- identities = list()
- features = list()
- while i < (len(data) - 3) and data[i] != 'FEAT':
- category = data[i]
- type_ = data[i + 1]
- lang = data[i + 2]
- name = data[i + 3]
- identities.append({'category': category, 'type': type_,
- 'xml:lang': lang, 'name': name})
- i += 4
- i+=1
- while i < len(data):
- features.append(data[i])
- i += 1
-
- # yield the row
- yield hash_method, hash_, identities, features
- for hash_method, hash_ in to_be_removed:
- sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND
- hash = "%s"''' % (hash_method, hash_)
- self.simple_commit(sql)
-
- def add_caps_entry(self, hash_method, hash_, identities, features):
- data = []
- for identity in identities:
- # there is no FEAT category
- if identity['category'] == 'FEAT':
- return
- data.extend((identity.get('category'), identity.get('type', ''),
- identity.get('xml:lang', ''), identity.get('name', '')))
- data.append('FEAT')
- data.extend(features)
- data = '\0'.join(data)
- # if there's a need to do more gzip, put that to a function
- string = BytesIO()
- gzip = GzipFile(fileobj=string, mode='w')
- gzip.write(data.encode('utf-8'))
- gzip.close()
- data = string.getvalue()
- self.cur.execute('''
- INSERT INTO caps_cache ( hash_method, hash, data, last_seen )
- VALUES (?, ?, ?, ?);
- ''', (hash_method, hash_, memoryview(data), int(time.time())))
- # (1) -- note above
- self._timeout_commit()
-
- def update_caps_time(self, method, hash_):
- sql = '''UPDATE caps_cache SET last_seen = %d
- WHERE hash_method = "%s" and hash = "%s"''' % \
- (int(time.time()), method, hash_)
- self.simple_commit(sql)
-
- def clean_caps_table(self):
- """
- Remove caps which was not seen for 3 months
- """
- sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \
- int(time.time() - 3*30*24*3600)
- self.simple_commit(sql)
-
- def replace_roster(self, account_name, roster_version, roster):
- """
- Replace current roster in DB by a new one
-
- accout_name is the name of the account to change.
- roster_version is the version of the new roster.
- roster is the new version.
- """
- # First we must reset roster_version value to ensure that the server
- # sends back all the roster at the next connexion if the replacement
- # didn't work properly.
- gajim.config.set_per('accounts', account_name, 'roster_version', '')
-
- account_jid = gajim.get_jid_from_account(account_name)
- account_jid_id = self.get_jid_id(account_jid)
-
- # Delete old roster
- self.remove_roster(account_jid)
-
- # Fill roster tables with the new roster
- for jid in roster:
- self.add_or_update_contact(account_jid, jid, roster[jid]['name'],
- roster[jid]['subscription'], roster[jid]['ask'],
- roster[jid]['groups'], commit=False)
- self._timeout_commit()
-
- # At this point, we are sure the replacement works properly so we can
- # set the new roster_version value.
- gajim.config.set_per('accounts', account_name, 'roster_version',
- roster_version)
-
- def del_contact(self, account_jid, jid):
- """
- Remove jid from account_jid roster
- """
- try:
- account_jid_id = self.get_jid_id(account_jid)
- jid_id = self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError as e:
- raise exceptions.PysqliteOperationalError(str(e))
- self.cur.execute(
- 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- self.cur.execute(
- 'DELETE FROM roster_entry WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- self._timeout_commit()
-
- def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups,
- commit=True):
- """
- Add or update a contact from account_jid roster
- """
- if sub == 'remove':
- self.del_contact(account_jid, jid)
- return
-
- try:
- account_jid_id = self.get_jid_id(account_jid)
- jid_id = self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError as e:
- raise exceptions.PysqliteOperationalError(str(e))
-
- # Update groups information
- # First we delete all previous groups information
- self.cur.execute(
- 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- # Then we add all new groups information
- for group in groups:
- self.cur.execute('INSERT INTO roster_group VALUES(?, ?, ?)',
- (account_jid_id, jid_id, group))
-
- if name is None:
- name = ''
-
- self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)',
- (account_jid_id, jid_id, name,
- self.convert_human_subscription_values_to_db_api_values(sub),
- bool(ask)))
- if commit:
- self._timeout_commit()
-
- def get_roster(self, account_jid):
- """
- Return the accound_jid roster in NonBlockingRoster format
- """
- data = {}
- account_jid_id = self.get_jid_id(account_jid)
-
- # First we fill data with roster_entry informations
- self.cur.execute('''
- SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask
- FROM roster_entry re, jids j
- WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,))
- for jid, jid_id, name, subscription, ask in self.cur:
- jid = jid
- name = name
- data[jid] = {}
- if name:
- data[jid]['name'] = name
- else:
- data[jid]['name'] = None
- data[jid]['subscription'] = \
- self.convert_db_api_values_to_human_subscription_values(
- subscription)
- data[jid]['groups'] = []
- data[jid]['resources'] = {}
- if ask:
- data[jid]['ask'] = 'subscribe'
- else:
- data[jid]['ask'] = None
- data[jid]['id'] = jid_id
-
- # Then we add group for roster entries
- for jid in data:
- self.cur.execute('''
- SELECT group_name FROM roster_group
- WHERE account_jid_id=? AND jid_id=?''',
- (account_jid_id, data[jid]['id']))
- for (group_name,) in self.cur:
- group_name = group_name
- data[jid]['groups'].append(group_name)
- del data[jid]['id']
-
- return data
-
- def remove_roster(self, account_jid):
- """
- Remove all entry from account_jid roster
- """
- account_jid_id = self.get_jid_id(account_jid)
-
- self.cur.execute('DELETE FROM roster_entry WHERE account_jid_id=?',
- (account_jid_id,))
- self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?',
- (account_jid_id,))
- self._timeout_commit()
-
- def save_if_not_exists(self, with_, direction, tim, msg='', nick=None, additional_data=None):
- if additional_data is None:
- additional_data = {}
- if tim:
- time_col = float(tim)
- else:
- time_col = float(time.time())
- if not msg:
- return
- if self.jid_is_from_pm(with_) or nick:
- # It's a groupchat message
- if nick:
- # It's a message from a groupchat occupent
- type_ = 'gc_msg'
- with_ = with_ + '/' + nick
- else:
- # It's a server message message, we don't log them
- return
- else:
- if direction == 'from':
- type_ = 'chat_msg_recv'
- elif direction == 'to':
- type_ = 'chat_msg_sent'
- jid_id = self.get_jid_id(with_)
- where_sql = 'jid_id = %s AND message=?' % jid_id
- if type_ == 'gc_msg':
- # We cannot differentiate gc message and pm messages, so look in
- # both logs
- with_2 = gajim.get_jid_without_resource(with_)
- if with_ != with_2:
- jid_id2 = self.get_jid_id(with_2)
- where_sql = 'jid_id in (%s, %s) AND message=?' % (jid_id,
- jid_id2)
- start_time = time_col - 300 # 5 minutes arrount given time
- end_time = time_col + 300 # 5 minutes arrount given time
- self.cur.execute('''
- SELECT log_line_id FROM logs
- WHERE (%s)
- AND time BETWEEN %d AND %d
- ORDER BY time
- ''' % (where_sql, start_time, end_time), (msg,))
- results = self.cur.fetchall()
- if results:
- log.debug('Log already in DB, ignoring it')
- return
- log.debug('New log received from server archives, storing it')
- self.write(type_, with_, message=msg, tim=tim,
- additional_data=additional_data, mam_query=True)
-
- def _nec_gc_message_received(self, obj):
- tim_f = float(obj.timestamp)
- tim_int = int(tim_f)
- if gajim.config.should_log(obj.conn.name, obj.jid) and not \
- tim_int < obj.conn.last_history_time[obj.jid] and obj.msgtxt and \
- obj.nick:
- # if not obj.nick, it means message comes from room itself
- # usually it hold description and can be send at each connection
- # so don't store it in logs
- self.write('gc_msg', obj.fjid, obj.msgtxt, tim=obj.timestamp, additional_data=obj.additional_data)
- # store in memory time of last message logged.
- # this will also be saved in rooms_last_message_time table
- # when we quit this muc
- obj.conn.last_history_time[obj.jid] = tim_f
diff --git a/src/common/logging_helpers.py b/src/common/logging_helpers.py
deleted file mode 100644
index 30aeddddc..000000000
--- a/src/common/logging_helpers.py
+++ /dev/null
@@ -1,196 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/logging_helpers.py
-##
-## Copyright (C) 2009 Bruno Tarquini <btarquini AT gmail.com>
-##
-## 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/>.
-##
-
-import logging
-import os
-import sys
-
-def parseLogLevel(arg):
- """
- Eiter numeric value or level name from logging module
- """
- if arg.isdigit():
- return int(arg)
- elif arg.isupper() and hasattr(logging, arg):
- return getattr(logging, arg)
- else:
- print(_('%s is not a valid loglevel') % repr(arg))
- return 0
-
-def parseLogTarget(arg):
- """
- [gajim.]c.x.y -> gajim.c.x.y
- .other_logger -> other_logger
- <None> -> gajim
- """
- arg = arg.lower()
- if not arg:
- return 'gajim'
- elif arg.startswith('.'):
- return arg[1:]
- elif arg.startswith('gajim'):
- return arg
- else:
- return 'gajim.' + arg
-
-def parseAndSetLogLevels(arg):
- """
- [=]LOGLEVEL -> gajim=LOGLEVEL
- gajim=LOGLEVEL -> gajim=LOGLEVEL
- .other=10 -> other=10
- .=10 -> <nothing>
- c.x.y=c.z=20 -> gajim.c.x.y=20
- gajim.c.z=20
- gajim=10,c.x=20 -> gajim=10
- gajim.c.x=20
- """
- for directive in arg.split(','):
- directive = directive.strip()
- if not directive:
- continue
- if '=' not in directive:
- directive = '=' + directive
- targets, level = directive.rsplit('=', 1)
- level = parseLogLevel(level.strip())
- for target in targets.split('='):
- target = parseLogTarget(target.strip())
- if target:
- logging.getLogger(target).setLevel(level)
- print("Logger %s level set to %d" % (target, level))
-
-
-class colors:
- NONE = chr(27) + "[0m"
- BLACk = chr(27) + "[30m"
- RED = chr(27) + "[31m"
- GREEN = chr(27) + "[32m"
- BROWN = chr(27) + "[33m"
- BLUE = chr(27) + "[34m"
- MAGENTA = chr(27) + "[35m"
- CYAN = chr(27) + "[36m"
- LIGHT_GRAY = chr(27) + "[37m"
- DARK_GRAY = chr(27) + "[30;1m"
- BRIGHT_RED = chr(27) + "[31;1m"
- BRIGHT_GREEN = chr(27) + "[32;1m"
- YELLOW = chr(27) + "[33;1m"
- BRIGHT_BLUE = chr(27) + "[34;1m"
- PURPLE = chr(27) + "[35;1m"
- BRIGHT_CYAN = chr(27) + "[36;1m"
- WHITE = chr(27) + "[37;1m"
-
-def colorize(text, color):
- return color + text + colors.NONE
-
-class FancyFormatter(logging.Formatter):
- """
- An eye-candy formatter with colors
- """
- colors_mapping = {
- 'DEBUG': colors.BLUE,
- 'INFO': colors.GREEN,
- 'WARNING': colors.BROWN,
- 'ERROR': colors.RED,
- 'CRITICAL': colors.BRIGHT_RED,
- }
-
- def __init__(self, fmt, datefmt=None, use_color=False):
- logging.Formatter.__init__(self, fmt, datefmt)
- self.use_color = use_color
-
- def formatTime(self, record, datefmt=None):
- f = logging.Formatter.formatTime(self, record, datefmt)
- if self.use_color:
- f = colorize(f, colors.DARK_GRAY)
- return f
-
- def format(self, record):
- level = record.levelname
- record.levelname = '(%s)' % level[0]
-
- if self.use_color:
- c = FancyFormatter.colors_mapping.get(level, '')
- record.levelname = colorize(record.levelname, c)
- record.name = colorize(record.name, colors.CYAN)
- else:
- record.name += ':'
-
- return logging.Formatter.format(self, record)
-
-
-def init():
- """
- Iinitialize the logging system
- """
- use_color = False
- if os.name != 'nt':
- use_color = sys.stderr.isatty()
-
- consoleloghandler = logging.StreamHandler()
- consoleloghandler.setFormatter(
- FancyFormatter(
- '%(asctime)s %(levelname)s %(name)s %(message)s',
- '%x %H:%M:%S',
- use_color
- )
- )
-
- # fake the root logger so we have 'gajim' root name instead of 'root'
- root_log = logging.getLogger('gajim')
- root_log.setLevel(logging.WARNING)
- root_log.addHandler(consoleloghandler)
- root_log.propagate = False
-
- # handle nbxmpp logs too
- root_log = logging.getLogger('nbxmpp')
- root_log.setLevel(logging.WARNING)
- root_log.addHandler(consoleloghandler)
- root_log.propagate = False
-
-def set_loglevels(loglevels_string):
- parseAndSetLogLevels(loglevels_string)
-
-def set_verbose():
- parseAndSetLogLevels('gajim=DEBUG')
- parseAndSetLogLevels('.nbxmpp=INFO')
-
-def set_quiet():
- parseAndSetLogLevels('gajim=CRITICAL')
- parseAndSetLogLevels('.nbxmpp=CRITICAL')
-
-
-# tests
-if __name__ == '__main__':
- init()
-
- set_loglevels('gajim.c=DEBUG,INFO')
-
- log = logging.getLogger('gajim')
- log.debug('debug')
- log.info('info')
- log.warning('warn')
- log.error('error')
- log.critical('critical')
-
- log = logging.getLogger('gajim.c.x.dispatcher')
- log.debug('debug')
- log.info('info')
- log.warning('warn')
- log.error('error')
- log.critical('critical')
diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py
deleted file mode 100644
index 45c619207..000000000
--- a/src/common/message_archiving.py
+++ /dev/null
@@ -1,459 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/message_archiving.py
-##
-## Copyright (C) 2009 Anaël Verrier <elghinn AT free.fr>
-##
-## 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/>.
-##
-
-import nbxmpp
-from common import gajim
-from common import ged
-from common import helpers
-from common.connection_handlers_events import ArchivingReceivedEvent
-
-from calendar import timegm
-from time import localtime
-
-import logging
-log = logging.getLogger('gajim.c.message_archiving')
-
-ARCHIVING_COLLECTIONS_ARRIVED = 'archiving_collections_arrived'
-ARCHIVING_COLLECTION_ARRIVED = 'archiving_collection_arrived'
-ARCHIVING_MODIFICATIONS_ARRIVED = 'archiving_modifications_arrived'
-MAM_RESULTS_ARRIVED = 'mam_results_arrived'
-
-class ConnectionArchive:
- def __init__(self):
- pass
-
-
-class ConnectionArchive313(ConnectionArchive):
- def __init__(self):
- ConnectionArchive.__init__(self)
- self.archiving_313_supported = False
- self.mam_awaiting_disco_result = {}
- self.iq_answer = []
- gajim.ged.register_event_handler('archiving-finished-legacy', ged.CORE,
- self._nec_result_finished)
- gajim.ged.register_event_handler('archiving-finished', ged.CORE,
- self._nec_result_finished)
- gajim.ged.register_event_handler('agent-info-error-received', ged.CORE,
- self._nec_agent_info_error)
- gajim.ged.register_event_handler('agent-info-received', ged.CORE,
- self._nec_agent_info)
- gajim.ged.register_event_handler('mam-decrypted-message-received',
- ged.CORE, self._nec_mam_decrypted_message_received)
- gajim.ged.register_event_handler(
- 'archiving-313-preferences-changed-received', ged.CORE,
- self._nec_archiving_313_preferences_changed_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('archiving-finished-legacy', ged.CORE,
- self._nec_result_finished)
- gajim.ged.remove_event_handler('archiving-finished', ged.CORE,
- self._nec_result_finished)
- gajim.ged.remove_event_handler('agent-info-error-received', ged.CORE,
- self._nec_agent_info_error)
- gajim.ged.remove_event_handler('agent-info-received', ged.CORE,
- self._nec_agent_info)
- gajim.ged.remove_event_handler('mam-decrypted-message-received',
- ged.CORE, self._nec_mam_decrypted_message_received)
- gajim.ged.remove_event_handler(
- 'archiving-313-preferences-changed-received', ged.CORE,
- self._nec_archiving_313_preferences_changed_received)
-
- def _nec_archiving_313_preferences_changed_received(self, obj):
- if obj.id in self.iq_answer:
- obj.answer = True
-
- def _nec_agent_info_error(self, obj):
- if obj.jid in self.mam_awaiting_disco_result:
- log.warn('Unable to discover %s, ignoring those logs', obj.jid)
- del self.mam_awaiting_disco_result[obj.jid]
-
- def _nec_agent_info(self, obj):
- if obj.jid in self.mam_awaiting_disco_result:
- for identity in obj.identities:
- if identity['category'] == 'conference':
- # it's a groupchat
- for with_, direction, tim, msg_txt, res in \
- self.mam_awaiting_disco_result[obj.jid]:
- gajim.logger.get_jid_id(with_, 'ROOM')
- gajim.logger.save_if_not_exists(with_, direction, tim,
- msg=msg_txt, nick=res)
- del self.mam_awaiting_disco_result[obj.jid]
- return
- # it's not a groupchat
- for with_, direction, tim, msg_txt, res in \
- self.mam_awaiting_disco_result[obj.jid]:
- gajim.logger.get_jid_id(with_)
- gajim.logger.save_if_not_exists(with_, direction, tim,
- msg=msg_txt)
- del self.mam_awaiting_disco_result[obj.jid]
-
- def _nec_result_finished(self, obj):
- if obj.conn.name != self.name:
- return
-
- if obj.queryid not in self.awaiting_answers:
- return
-
- if self.awaiting_answers[obj.queryid][0] == MAM_RESULTS_ARRIVED:
- set_ = obj.fin.getTag('set', namespace=nbxmpp.NS_RSM)
- if set_:
- last = set_.getTagData('last')
- if last:
- gajim.config.set_per('accounts', self.name, 'last_mam_id', last)
- complete = obj.fin.getAttr('complete')
- if complete != 'true':
- self.request_archive(after=last)
- del self.awaiting_answers[obj.queryid]
-
- def _nec_mam_decrypted_message_received(self, obj):
- if obj.conn.name != self.name:
- return
- gajim.logger.save_if_not_exists(obj.with_, obj.direction, obj.tim,
- msg=obj.msgtxt, nick=obj.nick, additional_data=obj.additional_data)
-
- def request_archive(self, start=None, end=None, with_=None, after=None,
- max=30):
- iq_ = nbxmpp.Iq('set')
- query = iq_.addChild('query', namespace=self.archiving_namespace)
- x = query.addChild(node=nbxmpp.DataForm(typ='submit'))
- x.addChild(node=nbxmpp.DataField(typ='hidden', name='FORM_TYPE', value=self.archiving_namespace))
- if start:
- x.addChild(node=nbxmpp.DataField(typ='text-single', name='start', value=start))
- if end:
- x.addChild(node=nbxmpp.DataField(typ='text-single', name='end', value=end))
- if with_:
- x.addChild(node=nbxmpp.DataField(typ='jid-single', name='with', value=with_))
- set_ = query.setTag('set', namespace=nbxmpp.NS_RSM)
- set_.setTagData('max', max)
- if after:
- set_.setTagData('after', after)
- queryid_ = self.connection.getAnID()
- query.setAttr('queryid', queryid_)
- id_ = self.connection.getAnID()
- iq_.setID(id_)
- self.awaiting_answers[queryid_] = (MAM_RESULTS_ARRIVED, )
- self.connection.send(iq_)
-
- def request_archive_preferences(self):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- iq.addChild(name='prefs', namespace=self.archiving_namespace)
- self.connection.send(iq)
-
- def set_archive_preferences(self, items, default):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set')
- id_ = self.connection.getAnID()
- self.iq_answer.append(id_)
- iq.setID(id_)
- prefs = iq.addChild(name='prefs', namespace=self.archiving_namespace, attrs={'default': default})
- always = prefs.addChild(name='always')
- never = prefs.addChild(name='never')
- for item in items:
- jid, preference = item
- if preference == 'always':
- always.addChild(name='jid').setData(jid)
- else:
- never.addChild(name='jid').setData(jid)
- self.connection.send(iq)
-
-class ConnectionArchive136(ConnectionArchive):
- def __init__(self):
- ConnectionArchive.__init__(self)
- self.archiving_136_supported = False
- self.archive_auto_supported = False
- self.archive_manage_supported = False
- self.archive_manual_supported = False
- self.archive_pref_supported = False
- self.auto = None
- self.method_auto = None
- self.method_local = None
- self.method_manual = None
- self.default = None
- self.items = {}
- gajim.ged.register_event_handler(
- 'archiving-preferences-changed-received', ged.CORE,
- self._nec_archiving_changed_received)
- gajim.ged.register_event_handler('raw-iq-received', ged.CORE,
- self._nec_raw_iq_136_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler(
- 'archiving-preferences-changed-received', ged.CORE,
- self._nec_archiving_changed_received)
- gajim.ged.remove_event_handler('raw-iq-received', ged.CORE,
- self._nec_raw_iq_136_received)
-
- def _nec_raw_iq_136_received(self, obj):
- if obj.conn.name != self.name:
- return
-
- id_ = obj.stanza.getID()
- if id_ not in self.awaiting_answers:
- return
-
- if self.awaiting_answers[id_][0] == ARCHIVING_COLLECTIONS_ARRIVED:
- del self.awaiting_answers[id_]
- # TODO
- print('ARCHIVING_COLLECTIONS_ARRIVED')
-
- elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTION_ARRIVED:
- def save_if_not_exists(with_, nick, direction, tim, payload):
- assert len(payload) == 1, 'got several archiving messages in' +\
- ' the same time %s' % ''.join(payload)
- if payload[0].getName() == 'body':
- gajim.logger.save_if_not_exists(with_, direction, tim,
- msg=payload[0].getData(), nick=nick)
- elif payload[0].getName() == 'message':
- print('Not implemented')
- chat = obj.stanza.getTag('chat')
- if chat:
- with_ = chat.getAttr('with')
- start_ = chat.getAttr('start')
- tim = helpers.datetime_tuple(start_)
- tim = timegm(tim)
- nb = 0
- for element in chat.getChildren():
- try:
- secs = int(element.getAttr('secs'))
- except TypeError:
- secs = 0
- if secs:
- tim += secs
- nick = element.getAttr('name')
- if element.getName() == 'from':
- save_if_not_exists(with_, nick, 'from', localtime(tim),
- element.getPayload())
- nb += 1
- if element.getName() == 'to':
- save_if_not_exists(with_, nick, 'to', localtime(tim),
- element.getPayload())
- nb += 1
- set_ = chat.getTag('set')
- first = set_.getTag('first')
- if first:
- try:
- index = int(first.getAttr('index'))
- except TypeError:
- index = 0
- try:
- count = int(set_.getTagData('count'))
- except TypeError:
- count = 0
- if count > index + nb:
- # Request the next page
- after = element.getTagData('last')
- self.request_collection_page(with_, start_, after=after)
- del self.awaiting_answers[id_]
-
- elif self.awaiting_answers[id_][0] == ARCHIVING_MODIFICATIONS_ARRIVED:
- modified = obj.stanza.getTag('modified')
- if modified:
- for element in modified.getChildren():
- if element.getName() == 'changed':
- with_ = element.getAttr('with')
- start_ = element.getAttr('start')
- self.request_collection_page(with_, start_)
- #elif element.getName() == 'removed':
- # do nothing
- del self.awaiting_answers[id_]
-
- def request_message_archiving_preferences(self):
- iq_ = nbxmpp.Iq('get')
- iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE)
- self.connection.send(iq_)
-
- def set_pref(self, name, **data):
- '''
- data contains names and values of pref name attributes.
- '''
- iq_ = nbxmpp.Iq('set')
- pref = iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE)
- tag = pref.setTag(name)
- for key, value in data.items():
- if value is not None:
- tag.setAttr(key, value)
- self.connection.send(iq_)
-
- def set_auto(self, save):
- self.set_pref('auto', save=save)
-
- def set_method(self, type, use):
- self.set_pref('method', type=type, use=use)
-
- def set_default(self, otr, save, expire=None):
- self.set_pref('default', otr=otr, save=save, expire=expire)
-
- def append_or_update_item(self, jid, otr, save, expire):
- self.set_pref('item', jid=jid, otr=otr, save=save)
-
- def remove_item(self, jid):
- iq_ = nbxmpp.Iq('set')
- itemremove = iq_.setTag('itemremove', namespace=nbxmpp.NS_ARCHIVE)
- item = itemremove.setTag('item')
- item.setAttr('jid', jid)
- self.connection.send(iq_)
-
- def stop_archiving_session(self, thread_id):
- iq_ = nbxmpp.Iq('set')
- pref = iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE)
- session = pref.setTag('session', attrs={'thread': thread_id,
- 'save': 'false', 'otr': 'concede'})
- self.connection.send(iq_)
-
- def get_item_pref(self, jid):
- jid = nbxmpp.JID(jid)
- if str(jid) in self.items:
- return self.items[jid]
-
- if jid.getStripped() in self.items:
- return self.items[jid.getStripped()]
-
- if jid.getDomain() in self.items:
- return self.items[jid.getDomain()]
-
- return self.default
-
- def logging_preference(self, jid, initiator_options=None):
- otr = self.get_item_pref(jid)
- if not otr:
- return
- otr = otr['otr']
- if initiator_options:
- if ((initiator_options == ['mustnot'] and otr == 'forbid') or
- (initiator_options == ['may'] and otr == 'require')):
- return None
-
- if (initiator_options == ['mustnot'] or
- (initiator_options[0] == 'mustnot' and
- otr not in ('opppose', 'forbid')) or
- (initiator_options == ['may', 'mustnot'] and
- otr in ('require', 'prefer'))):
- return 'mustnot'
-
- return 'may'
-
- if otr == 'require':
- return ['mustnot']
-
- if otr in ('prefer', 'approve'):
- return ['mustnot', 'may']
-
- if otr in ('concede', 'oppose'):
- return ['may', 'mustnot']
-
- # otr == 'forbid'
- return ['may']
-
- def _ArchiveCB(self, con, iq_obj):
- log.debug('_ArchiveCB %s' % iq_obj.getType())
- gajim.nec.push_incoming_event(ArchivingReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_archiving_changed_received(self, obj):
- if obj.conn.name != self.name:
- return
- for key in ('auto', 'default'):
- if key not in obj.conf:
- self.archiving_136_supported = False
- self.archive_auto_supported = False
- self.archive_manage_supported = False
- self.archive_manual_supported = False
- self.archive_pref_supported = False
- return True
- for key in ('auto', 'method_auto', 'method_local', 'method_manual',
- 'default'):
- if key in obj.conf:
- self.__dict__[key] = obj.conf[key]
-
- for jid, pref in obj.new_items.items():
- self.items[jid] = pref
-
- for jid in obj.removed_items:
- del self.items[jid]
-
- def request_collections_list_page(self, with_='', start=None, end=None,
- after=None, max=30, exact_match=False):
- iq_ = nbxmpp.Iq('get')
- list_ = iq_.setTag('list', namespace=nbxmpp.NS_ARCHIVE)
- if with_:
- list_.setAttr('with', with_)
- if exact_match:
- list_.setAttr('exactmatch', 'true')
- if start:
- list_.setAttr('start', start)
- if end:
- list_.setAttr('end', end)
- set_ = list_.setTag('set', namespace=nbxmpp.NS_RSM)
- set_.setTagData('max', max)
- if after:
- set_.setTagData('after', after)
- id_ = self.connection.getAnID()
- iq_.setID(id_)
- self.awaiting_answers[id_] = (ARCHIVING_COLLECTIONS_ARRIVED, )
- self.connection.send(iq_)
-
- def request_collection_page(self, with_, start, end=None, after=None,
- max=30, exact_match=False):
- iq_ = nbxmpp.Iq('get')
- retrieve = iq_.setTag('retrieve', namespace=nbxmpp.NS_ARCHIVE,
- attrs={'with': with_, 'start': start})
- if exact_match:
- retrieve.setAttr('exactmatch', 'true')
- set_ = retrieve.setTag('set', namespace=nbxmpp.NS_RSM)
- set_.setTagData('max', max)
- if after:
- set_.setTagData('after', after)
- id_ = self.connection.getAnID()
- iq_.setID(id_)
- self.awaiting_answers[id_] = (ARCHIVING_COLLECTION_ARRIVED, )
- self.connection.send(iq_)
-
- def remove_collection(self, with_='', start=None, end=None,
- exact_match=False, open=False):
- iq_ = nbxmpp.Iq('set')
- remove = iq_.setTag('remove', namespace=nbxmpp.NS_ARCHIVE)
- if with_:
- remove.setAttr('with', with_)
- if exact_match:
- remove.setAttr('exactmatch', 'true')
- if start:
- remove.setAttr('start', start)
- if end:
- remove.setAttr('end', end)
- if open:
- remove.setAttr('open', 'true')
- self.connection.send(iq_)
-
- def request_modifications_page(self, start, max=30):
- iq_ = nbxmpp.Iq('get')
- moified = iq_.setTag('modified', namespace=nbxmpp.NS_ARCHIVE,
- attrs={'start': start})
- set_ = moified.setTag('set', namespace=nbxmpp.NS_RSM)
- set_.setTagData('max', max)
- id_ = self.connection.getAnID()
- iq_.setID(id_)
- self.awaiting_answers[id_] = (ARCHIVING_MODIFICATIONS_ARRIVED, )
- self.connection.send(iq_)
diff --git a/src/common/multimedia_helpers.py b/src/common/multimedia_helpers.py
deleted file mode 100644
index 19e21ed72..000000000
--- a/src/common/multimedia_helpers.py
+++ /dev/null
@@ -1,111 +0,0 @@
-##
-## Copyright (C) 2009 Thibaut GIRKA <thib AT sitedethib.com>
-##
-## 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/>.
-
-import gi
-gi.require_version('Gst', '1.0')
-from gi.repository import Gst
-
-
-class DeviceManager(object):
- def __init__(self):
- self.devices = {}
-
- def detect(self):
- self.devices = {}
-
- def get_devices(self):
- if not self.devices:
- self.detect()
- return self.devices
-
- def detect_element(self, name, text, pipe='%s'):
- if Gst.ElementFactory.find(name):
- element = Gst.ElementFactory.make(name, '%spresencetest' % name)
- if hasattr(element.props, 'device'):
- element.set_state(Gst.State.READY)
- devices = element.get_properties('device')
- if devices:
- self.devices[text % _('Default device')] = pipe % name
- for device in devices:
- element.set_state(Gst.State.NULL)
- element.set_property('device', device)
- element.set_state(Gst.State.READY)
- device_name = element.get_property('device-name')
- self.devices[text % device_name] = pipe % \
- '%s device=%s' % (name, device)
- element.set_state(Gst.State.NULL)
- else:
- self.devices[text] = pipe % name
- else:
- print('element \'%s\' not found' % name)
-
-
-class AudioInputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Test src
- self.detect_element('audiotestsrc', _('Audio test'),
- '%s is-live=true name=gajim_vol')
- # Auto src
- self.detect_element('autoaudiosrc', _('Autodetect'),
- '%s ! volume name=gajim_vol')
- # Alsa src
- self.detect_element('alsasrc', _('ALSA: %s'),
- '%s ! volume name=gajim_vol')
- # Pulseaudio src
- self.detect_element('pulsesrc', _('Pulse: %s'),
- '%s ! volume name=gajim_vol')
-
-
-class AudioOutputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Fake sink
- self.detect_element('fakesink', _('Fake audio output'))
- # Auto sink
- self.detect_element('autoaudiosink', _('Autodetect'))
- # Alsa sink
- self.detect_element('alsasink', _('ALSA: %s'), '%s sync=false')
- # Pulseaudio sink
- self.detect_element('pulsesink', _('Pulse: %s'), '%s sync=true')
-
-
-class VideoInputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Test src
- self.detect_element('videotestsrc', _('Video test'),
- '%s is-live=true ! video/x-raw,framerate=10/1')
- # Auto src
- self.detect_element('autovideosrc', _('Autodetect'))
- # V4L2 src
- self.detect_element('v4l2src', _('V4L2: %s'))
- # Funny things, just to test...
- # self.devices['GOOM'] = 'audiotestsrc ! goom'
- self.detect_element('ximagesrc', _('Screen'), '%s ! ffmpegcolorspace')
-
-
-class VideoOutputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Fake video output
- self.detect_element('fakesink', _('Fake video output'))
- # Auto sink
- self.detect_element('xvimagesink',
- _('X Window System (X11/XShm/Xv): %s'))
- # ximagesink
- self.detect_element('ximagesink', _('X Window System (without Xv)'))
- self.detect_element('autovideosink', _('Autodetect'))
-
diff --git a/src/common/nec.py b/src/common/nec.py
deleted file mode 100644
index b699085c1..000000000
--- a/src/common/nec.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## 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/>.
-##
-
-'''
-Network Events Controller.
-
-:author: Mateusz Biliński <mateusz@bilinski.it>
-:since: 10th August 2008
-:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
-:copyright: Copyright (2011) Yann Leboulanger <asterix@lagaule.org>
-:license: GPL
-'''
-
-#from plugins.helpers import log
-from common import gajim
-
-class NetworkEventsController(object):
-
- def __init__(self):
- self.incoming_events_generators = {}
- '''
- Keys: names of events
- Values: list of class objects that are subclasses
- of `NetworkIncomingEvent`
- '''
- self.outgoing_events_generators = {}
- '''
- Keys: names of events
- Values: list of class objects that are subclasses
- of `NetworkOutgoingEvent`
- '''
-
- def register_incoming_event(self, event_class):
- for base_event_name in event_class.base_network_events:
- event_list = self.incoming_events_generators.setdefault(
- base_event_name, [])
- if not event_class in event_list:
- event_list.append(event_class)
-
- def unregister_incoming_event(self, event_class):
- for base_event_name in event_class.base_network_events:
- if base_event_name in self.incoming_events_generators:
- self.incoming_events_generators[base_event_name].remove(
- event_class)
-
- def register_outgoing_event(self, event_class):
- for base_event_name in event_class.base_network_events:
- event_list = self.outgoing_events_generators.setdefault(
- base_event_name, [])
- if not event_class in event_list:
- event_list.append(event_class)
-
- def unregister_outgoing_event(self, event_class):
- for base_event_name in event_class.base_network_events:
- if base_event_name in self.outgoing_events_generators:
- self.outgoing_events_generators[base_event_name].remove(
- event_class)
-
- def push_incoming_event(self, event_object):
- if event_object.generate():
- if not gajim.ged.raise_event(event_object.name, event_object):
- self._generate_events_based_on_incoming_event(event_object)
-
- def push_outgoing_event(self, event_object):
- if event_object.generate():
- if not gajim.ged.raise_event(event_object.name, event_object):
- self._generate_events_based_on_outgoing_event(event_object)
-
- def _generate_events_based_on_incoming_event(self, event_object):
- '''
- :return: True if even_object should be dispatched through Global
- Events Dispatcher, False otherwise. This can be used to replace
- base events with those that more data computed (easier to use
- by handlers).
- :note: replacing mechanism is not implemented currently, but will be
- based on attribute in new network events object.
- '''
- base_event_name = event_object.name
- if base_event_name in self.incoming_events_generators:
- for new_event_class in self.incoming_events_generators[
- base_event_name]:
- new_event_object = new_event_class(None,
- base_event=event_object)
- if new_event_object.generate():
- if not gajim.ged.raise_event(new_event_object.name,
- new_event_object):
- self._generate_events_based_on_incoming_event(
- new_event_object)
-
- def _generate_events_based_on_outgoing_event(self, event_object):
- '''
- :return: True if even_object should be dispatched through Global
- Events Dispatcher, False otherwise. This can be used to replace
- base events with those that more data computed (easier to use
- by handlers).
- :note: replacing mechanism is not implemented currently, but will be
- based on attribute in new network events object.
- '''
- base_event_name = event_object.name
- if base_event_name in self.outgoing_events_generators:
- for new_event_class in self.outgoing_events_generators[
- base_event_name]:
- new_event_object = new_event_class(None,
- base_event=event_object)
- if new_event_object.generate():
- if not gajim.ged.raise_event(new_event_object.name,
- new_event_object):
- self._generate_events_based_on_outgoing_event(
- new_event_object)
-
-class NetworkEvent(object):
- name = ''
-
- def __init__(self, new_name, **kwargs):
- if new_name:
- self.name = new_name
-
- self.init()
-
- self._set_kwargs_as_attributes(**kwargs)
-
- def init(self):
- pass
-
-
- def generate(self):
- '''
- Generates new event (sets it's attributes) based on event object.
-
- Base event object name is one of those in `base_network_events`.
-
- Reference to base event object is stored in `self.base_event` attribute.
-
- Note that this is a reference, so modifications to that event object
- are possible before dispatching to Global Events Dispatcher.
-
- :return: True if generated event should be dispatched, False otherwise.
- '''
- return True
-
- def _set_kwargs_as_attributes(self, **kwargs):
- for k, v in kwargs.items():
- setattr(self, k, v)
-
-
-class NetworkIncomingEvent(NetworkEvent):
- base_network_events = []
- '''
- Names of base network events that new event is going to be generated on.
- '''
-
-
-class NetworkOutgoingEvent(NetworkEvent):
- base_network_events = []
- '''
- Names of base network events that new event is going to be generated on.
- '''
diff --git a/src/common/optparser.py b/src/common/optparser.py
deleted file mode 100644
index bdd550cfa..000000000
--- a/src/common/optparser.py
+++ /dev/null
@@ -1,1001 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/optparser.py
-##
-## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 James Newton <redshodan AT gmail.com>
-## Brendan Taylor <whateley AT gmail.com>
-## Tomasz Melcer <liori AT exroot.org>
-## Stephan Erb <steve-e AT h3c.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/>.
-##
-
-import os
-import sys
-import re
-from time import time
-from common import gajim
-from common import helpers
-from common import caps_cache
-
-import sqlite3 as sqlite
-from common import logger
-
-import logging
-log = logging.getLogger('gajim.c.optparser')
-
-class OptionsParser:
- def __init__(self, filename):
- self.__filename = os.path.realpath(filename)
- self.old_values = {} # values that are saved in the file and maybe
- # no longer valid
-
- def read(self):
- try:
- fd = open(self.__filename)
- except Exception:
- if os.path.exists(self.__filename):
- #we talk about a file
- print(_('Error: cannot open %s for reading') % self.__filename,
- file=sys.stderr)
- return False
-
- new_version = gajim.config.get('version')
- new_version = new_version.split('-', 1)[0]
- seen = set()
- regex = re.compile(r"(?P<optname>[^.=]+)(?:(?:\.(?P<key>.+))?\.(?P<subname>[^.=]+))?\s=\s(?P<value>.*)")
-
- for line in fd:
- match = regex.match(line)
- if match is None:
- log.warn('Invalid configuration line, ignoring it: %s', line)
- continue
- optname, key, subname, value = match.groups()
- if key is None:
- self.old_values[optname] = value
- gajim.config.set(optname, value)
- else:
- if (optname, key) not in seen:
- if optname in self.old_values:
- self.old_values[optname][key] = {}
- else:
- self.old_values[optname] = {key: {}}
- gajim.config.add_per(optname, key)
- seen.add((optname, key))
- self.old_values[optname][key][subname] = value
- gajim.config.set_per(optname, key, subname, value)
-
- old_version = gajim.config.get('version')
- old_version = old_version.split('-', 1)[0]
-
- self.update_config(old_version, new_version)
- self.old_values = {} # clean mem
-
- fd.close()
- return True
-
- def write_line(self, fd, opt, parents, value):
- if value is None:
- return
- # convert to utf8 before writing to file if needed
- value = str(value)
- s = ''
- if parents:
- if len(parents) == 1:
- return
- for p in parents:
- s += p + '.'
- s += opt
- fd.write(s + ' = ' + value + '\n')
-
- def write(self):
- (base_dir, filename) = os.path.split(self.__filename)
- self.__tempfile = os.path.join(base_dir, '.' + filename)
- try:
- f = os.fdopen(os.open(self.__tempfile,
- os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0o600), 'w')
- except IOError as e:
- return str(e)
- try:
- gajim.config.foreach(self.write_line, f)
- except IOError as e:
- return str(e)
- f.flush()
- os.fsync(f.fileno())
- f.close()
- if os.path.exists(self.__filename):
- if os.name == 'nt':
- # win32 needs this
- try:
- os.remove(self.__filename)
- except Exception:
- pass
- try:
- os.rename(self.__tempfile, self.__filename)
- except IOError as e:
- return str(e)
-
- def update_config(self, old_version, new_version):
- old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y)
- old = []
- while len(old_version_list):
- old.append(int(old_version_list.pop(0)))
- new_version_list = new_version.split('.')
- new = []
- while len(new_version_list):
- new.append(int(new_version_list.pop(0)))
-
- if old < [0, 9] and new >= [0, 9]:
- self.update_config_x_to_09()
- if old < [0, 10] and new >= [0, 10]:
- self.update_config_09_to_010()
- if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]:
- self.update_config_to_01011()
- if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]:
- self.update_config_to_01012()
- if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]:
- self.update_config_to_01013()
- if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]:
- self.update_config_to_01014()
- if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]:
- self.update_config_to_01015()
- if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]:
- self.update_config_to_01016()
- if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]:
- self.update_config_to_01017()
- if old < [0, 10, 1, 8] and new >= [0, 10, 1, 8]:
- self.update_config_to_01018()
- if old < [0, 11, 0, 1] and new >= [0, 11, 0, 1]:
- self.update_config_to_01101()
- if old < [0, 11, 0, 2] and new >= [0, 11, 0, 2]:
- self.update_config_to_01102()
- if old < [0, 11, 1, 1] and new >= [0, 11, 1, 1]:
- self.update_config_to_01111()
- if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]:
- self.update_config_to_01112()
- if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]:
- self.update_config_to_01113()
- if old < [0, 11, 1, 4] and new >= [0, 11, 1, 4]:
- self.update_config_to_01114()
- if old < [0, 11, 1, 5] and new >= [0, 11, 1, 5]:
- self.update_config_to_01115()
- if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]:
- self.update_config_to_01121()
- if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]:
- self.update_config_to_01141()
- if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]:
- self.update_config_to_01142()
- if old < [0, 11, 4, 3] and new >= [0, 11, 4, 3]:
- self.update_config_to_01143()
- if old < [0, 11, 4, 4] and new >= [0, 11, 4, 4]:
- self.update_config_to_01144()
- if old < [0, 12, 0, 1] and new >= [0, 12, 0, 1]:
- self.update_config_to_01201()
- if old < [0, 12, 1, 1] and new >= [0, 12, 1, 1]:
- self.update_config_to_01211()
- if old < [0, 12, 1, 2] and new >= [0, 12, 1, 2]:
- self.update_config_to_01212()
- if old < [0, 12, 1, 3] and new >= [0, 12, 1, 3]:
- self.update_config_to_01213()
- if old < [0, 12, 1, 4] and new >= [0, 12, 1, 4]:
- self.update_config_to_01214()
- if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]:
- self.update_config_to_01215()
- if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]:
- self.update_config_to_01231()
- if old < [0, 12, 5, 1] and new >= [0, 12, 5, 1]:
- self.update_config_from_0125()
- self.update_config_to_01251()
- if old < [0, 12, 5, 2] and new >= [0, 12, 5, 2]:
- self.update_config_to_01252()
- if old < [0, 12, 5, 3] and new >= [0, 12, 5, 3]:
- self.update_config_to_01253()
- if old < [0, 12, 5, 4] and new >= [0, 12, 5, 4]:
- self.update_config_to_01254()
- if old < [0, 12, 5, 5] and new >= [0, 12, 5, 5]:
- self.update_config_to_01255()
- if old < [0, 12, 5, 6] and new >= [0, 12, 5, 6]:
- self.update_config_to_01256()
- if old < [0, 12, 5, 7] and new >= [0, 12, 5, 7]:
- self.update_config_to_01257()
- if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]:
- self.update_config_to_01258()
- if old < [0, 13, 10, 0] and new >= [0, 13, 10, 0]:
- self.update_config_to_013100()
- if old < [0, 13, 10, 1] and new >= [0, 13, 10, 1]:
- self.update_config_to_013101()
- if old < [0, 13, 90, 1] and new >= [0, 13, 90, 1]:
- self.update_config_to_013901()
- if old < [0, 14, 0, 1] and new >= [0, 14, 0, 1]:
- self.update_config_to_01401()
- if old < [0, 14, 90, 0] and new >= [0, 14, 90, 0]:
- self.update_config_to_014900()
- if old < [0, 16, 0, 1] and new >= [0, 16, 0, 1]:
- self.update_config_to_01601()
- if old < [0, 16, 4, 1] and new >= [0, 16, 4, 1]:
- self.update_config_to_01641()
- if old < [0, 16, 10, 1] and new >= [0, 16, 10, 1]:
- self.update_config_to_016101()
- if old < [0, 16, 10, 2] and new >= [0, 16, 10, 2]:
- self.update_config_to_016102()
- if old < [0, 16, 10, 3] and new >= [0, 16, 10, 3]:
- self.update_config_to_016103()
-
- gajim.logger.init_vars()
- gajim.logger.attach_cache_database()
- gajim.config.set('version', new_version)
-
- caps_cache.capscache.initialize_from_db()
-
- @staticmethod
- def assert_unread_msgs_table_exists():
- """
- Create table unread_messages if there is no such table
- """
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE unread_messages (
- message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER
- );
- '''
- )
- con.commit()
- gajim.logger.init_vars()
- except sqlite.OperationalError:
- pass
- con.close()
-
- @staticmethod
- def update_ft_proxies(to_remove=None, to_add=None):
- if to_remove is None:
- to_remove = []
- if to_add is None:
- to_add = []
- for account in gajim.config.get_per('accounts'):
- proxies_str = gajim.config.get_per('accounts', account,
- 'file_transfer_proxies')
- proxies = [p.strip() for p in proxies_str.split(',')]
- for wrong_proxy in to_remove:
- if wrong_proxy in proxies:
- proxies.remove(wrong_proxy)
- for new_proxy in to_add:
- if new_proxy not in proxies:
- proxies.append(new_proxy)
- proxies_str = ', '.join(proxies)
- gajim.config.set_per('accounts', account, 'file_transfer_proxies',
- proxies_str)
-
- def update_config_x_to_09(self):
- # Var name that changed:
- # avatar_width /height -> chat_avatar_width / height
- if 'avatar_width' in self.old_values:
- gajim.config.set('chat_avatar_width', self.old_values['avatar_width'])
- if 'avatar_height' in self.old_values:
- gajim.config.set('chat_avatar_height', self.old_values['avatar_height'])
- if 'use_dbus' in self.old_values:
- gajim.config.set('remote_control', self.old_values['use_dbus'])
- # always_compact_view -> always_compact_view_chat / _gc
- if 'always_compact_view' in self.old_values:
- gajim.config.set('always_compact_view_chat',
- self.old_values['always_compact_view'])
- gajim.config.set('always_compact_view_gc',
- self.old_values['always_compact_view'])
- # new theme: grocery, plain
- d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
- 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
- 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont',
- 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont',
- 'bannerfontattrs']
- for theme_name in (_('grocery'), _('default')):
- if theme_name not in gajim.config.get_per('themes'):
- gajim.config.add_per('themes', theme_name)
- theme = gajim.config.themes_default[theme_name]
- for o in d:
- gajim.config.set_per('themes', theme_name, o, theme[d.index(o)])
- # Remove cyan theme if it's not the current theme
- if 'cyan' in gajim.config.get_per('themes'):
- gajim.config.del_per('themes', 'cyan')
- if _('cyan') in gajim.config.get_per('themes'):
- gajim.config.del_per('themes', _('cyan'))
- # If we removed our roster_theme, choose the default green one or another
- # one if doesn't exists in config
- if gajim.config.get('roster_theme') not in gajim.config.get_per('themes'):
- theme = _('green')
- if theme not in gajim.config.get_per('themes'):
- theme = gajim.config.get_per('themes')[0]
- gajim.config.set('roster_theme', theme)
- # new proxies in accounts.name.file_transfer_proxies
- self.update_ft_proxies(to_add=['proxy.netlab.cz'])
-
- gajim.config.set('version', '0.9')
-
- def update_config_09_to_010(self):
- if 'usetabbedchat' in self.old_values and not \
- self.old_values['usetabbedchat']:
- gajim.config.set('one_message_window', 'never')
- if 'autodetect_browser_mailer' in self.old_values and \
- self.old_values['autodetect_browser_mailer'] is True:
- gajim.config.set('autodetect_browser_mailer', False)
- if 'useemoticons' in self.old_values and \
- not self.old_values['useemoticons']:
- gajim.config.set('emoticons_theme', '')
- if 'always_compact_view_chat' in self.old_values and \
- self.old_values['always_compact_view_chat'] != 'False':
- gajim.config.set('always_hide_chat_buttons', True)
- if 'always_compact_view_gc' in self.old_values and \
- self.old_values['always_compact_view_gc'] != 'False':
- gajim.config.set('always_hide_groupchat_buttons', True)
-
- self.update_ft_proxies(to_remove=['proxy65.jabber.autocom.pl',
- 'proxy65.jabber.ccc.de'], to_add=['transfer.jabber.freenet.de'])
- # create unread_messages table if needed
- self.assert_unread_msgs_table_exists()
-
- gajim.config.set('version', '0.10')
-
- def update_config_to_01011(self):
- if 'print_status_in_muc' in self.old_values and \
- self.old_values['print_status_in_muc'] in (True, False):
- gajim.config.set('print_status_in_muc', 'in_and_out')
- gajim.config.set('version', '0.10.1.1')
-
- def update_config_to_01012(self):
- # See [6456]
- if 'emoticons_theme' in self.old_values and \
- self.old_values['emoticons_theme'] == 'Disabled':
- gajim.config.set('emoticons_theme', '')
- gajim.config.set('version', '0.10.1.2')
-
- def update_config_to_01013(self):
- """
- Create table transports_cache if there is no such table
- """
- # FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE transports_cache (
- transport TEXT UNIQUE,
- type INTEGER
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.10.1.3')
-
- def update_config_to_01014(self):
- """
- Apply indeces to the logs database
- """
- print(_('migrating logs database to indices'))
- # FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- # apply indeces
- try:
- cur.executescript(
- '''
- CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind);
- CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
- '''
- )
-
- con.commit()
- except Exception:
- pass
- con.close()
- gajim.config.set('version', '0.10.1.4')
-
- def update_config_to_01015(self):
- """
- Clean show values in logs database
- """
- #FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \
- logger.constants.__dict__.keys() if i.startswith('SHOW_'))
- for show in status:
- cur.execute('update logs set show = ? where show = ?;', (status[show],
- show))
- cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);')
- con.commit()
- cur.close() # remove this in 2007 [pysqlite old versions need this]
- con.close()
- gajim.config.set('version', '0.10.1.5')
-
- def update_config_to_01016(self):
- """
- #2494 : Now we play gc_received_message sound even if
- notify_on_all_muc_messages is false. Keep precedent behaviour
- """
- if 'notify_on_all_muc_messages' in self.old_values and \
- self.old_values['notify_on_all_muc_messages'] == 'False' and \
- gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'):
- gajim.config.set_per('soundevents',\
- 'muc_message_received', 'enabled', False)
- gajim.config.set('version', '0.10.1.6')
-
- def update_config_to_01017(self):
- """
- trayicon_notification_on_new_messages -> trayicon_notification_on_events
- """
- if 'trayicon_notification_on_new_messages' in self.old_values:
- gajim.config.set('trayicon_notification_on_events',
- self.old_values['trayicon_notification_on_new_messages'])
- gajim.config.set('version', '0.10.1.7')
-
- def update_config_to_01018(self):
- """
- chat_state_notifications -> outgoing_chat_state_notifications
- """
- if 'chat_state_notifications' in self.old_values:
- gajim.config.set('outgoing_chat_state_notifications',
- self.old_values['chat_state_notifications'])
- gajim.config.set('version', '0.10.1.8')
-
- def update_config_to_01101(self):
- """
- Fill time_stamp from before_time and after_time
- """
- if 'before_time' in self.old_values:
- gajim.config.set('time_stamp', '%s%%X%s ' % (
- self.old_values['before_time'], self.old_values['after_time']))
- gajim.config.set('version', '0.11.0.1')
-
- def update_config_to_01102(self):
- """
- Fill time_stamp from before_time and after_time
- """
- if 'ft_override_host_to_send' in self.old_values:
- gajim.config.set('ft_add_hosts_to_send',
- self.old_values['ft_override_host_to_send'])
- gajim.config.set('version', '0.11.0.2')
-
- def update_config_to_01111(self):
- """
- Always_hide_chatbuttons -> compact_view
- """
- if 'always_hide_groupchat_buttons' in self.old_values and \
- 'always_hide_chat_buttons' in self.old_values:
- gajim.config.set('compact_view', self.old_values['always_hide_groupchat_buttons'] and \
- self.old_values['always_hide_chat_buttons'])
- gajim.config.set('version', '0.11.1.1')
-
- def update_config_to_01112(self):
- """
- GTK+ theme is renamed to default
- """
- if 'roster_theme' in self.old_values and \
- self.old_values['roster_theme'] == 'gtk+':
- gajim.config.set('roster_theme', _('default'))
- gajim.config.set('version', '0.11.1.2')
-
- def update_config_to_01113(self):
- # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE caps_cache (
- node TEXT,
- ver TEXT,
- ext TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.1.3')
-
- def update_config_to_01114(self):
- # add default theme if it doesn't exist
- d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
- 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
- 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont',
- 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont',
- 'bannerfontattrs']
- theme_name = _('default')
- if theme_name not in gajim.config.get_per('themes'):
- gajim.config.add_per('themes', theme_name)
- if gajim.config.get_per('themes', 'gtk+'):
- # copy from old gtk+ theme
- for o in d:
- val = gajim.config.get_per('themes', 'gtk+', o)
- gajim.config.set_per('themes', theme_name, o, val)
- gajim.config.del_per('themes', 'gtk+')
- else:
- # copy from default theme
- theme = gajim.config.themes_default[theme_name]
- for o in d:
- gajim.config.set_per('themes', theme_name, o, theme[d.index(o)])
- gajim.config.set('version', '0.11.1.4')
-
- def update_config_to_01115(self):
- # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- DELETE FROM caps_cache;
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.1.5')
-
- def update_config_to_01121(self):
- # remove old unencrypted secrets file
- from common.configpaths import gajimpaths
-
- new_file = gajimpaths['SECRETS_FILE']
-
- old_file = os.path.dirname(new_file) + '/secrets'
-
- if os.path.exists(old_file):
- os.remove(old_file)
-
- gajim.config.set('version', '0.11.2.1')
-
- def update_config_to_01141(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS caps_cache (
- node TEXT,
- ver TEXT,
- ext TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.1')
-
- def update_config_to_01142(self):
- """
- next_message_received sound event is splittedin 2 events
- """
- gajim.config.add_per('soundevents', 'next_message_received_focused')
- gajim.config.add_per('soundevents', 'next_message_received_unfocused')
- if gajim.config.get_per('soundevents', 'next_message_received'):
- enabled = gajim.config.get_per('soundevents', 'next_message_received',
- 'enabled')
- path = gajim.config.get_per('soundevents', 'next_message_received',
- 'path')
- gajim.config.del_per('soundevents', 'next_message_received')
- gajim.config.set_per('soundevents', 'next_message_received_focused',
- 'enabled', enabled)
- gajim.config.set_per('soundevents', 'next_message_received_focused',
- 'path', path)
- gajim.config.set('version', '0.11.1.2')
-
- def update_config_to_01143(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS rooms_last_message_time(
- jid_id INTEGER PRIMARY KEY UNIQUE,
- time INTEGER
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.3')
-
- def update_config_to_01144(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript('DROP TABLE caps_cache;')
- con.commit()
- except sqlite.OperationalError:
- pass
- try:
- cur.executescript(
- '''
- CREATE TABLE caps_cache (
- hash_method TEXT,
- hash TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.4')
-
- def update_config_to_01201(self):
- if 'uri_schemes' in self.old_values:
- new_values = self.old_values['uri_schemes'].replace(' mailto', '').\
- replace(' xmpp', '')
- gajim.config.set('uri_schemes', new_values)
- gajim.config.set('version', '0.12.0.1')
-
- def update_config_to_01211(self):
- if 'trayicon' in self.old_values:
- if self.old_values['trayicon'] == 'False':
- gajim.config.set('trayicon', 'never')
- else:
- gajim.config.set('trayicon', 'always')
- gajim.config.set('version', '0.12.1.1')
-
- def update_config_to_01212(self):
- for opt in ('ignore_unknown_contacts', 'send_os_info',
- 'log_encrypted_sessions'):
- if opt in self.old_values:
- val = self.old_values[opt]
- for account in gajim.config.get_per('accounts'):
- gajim.config.set_per('accounts', account, opt, val)
- gajim.config.set('version', '0.12.1.2')
-
- def update_config_to_01213(self):
- msgs = gajim.config.statusmsg_default
- for msg_name in gajim.config.get_per('statusmsg'):
- if msg_name in msgs:
- gajim.config.set_per('statusmsg', msg_name, 'activity',
- msgs[msg_name][1])
- gajim.config.set_per('statusmsg', msg_name, 'subactivity',
- msgs[msg_name][2])
- gajim.config.set_per('statusmsg', msg_name, 'activity_text',
- msgs[msg_name][3])
- gajim.config.set_per('statusmsg', msg_name, 'mood',
- msgs[msg_name][4])
- gajim.config.set_per('statusmsg', msg_name, 'mood_text',
- msgs[msg_name][5])
- gajim.config.set('version', '0.12.1.3')
-
- def update_config_to_01214(self):
- for status in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible',
- 'offline']:
- if 'last_status_msg_' + status in self.old_values:
- gajim.config.add_per('statusmsg', '_last_' + status)
- gajim.config.set_per('statusmsg', '_last_' + status, 'message',
- self.old_values['last_status_msg_' + status])
- gajim.config.set('version', '0.12.1.4')
-
- def update_config_to_01215(self):
- """
- Remove hardcoded ../data/sounds from config
- """
- dirs = ['../data', gajim.gajimpaths.data_root, gajim.DATA_DIR]
- if os.name != 'nt':
- dirs.append(os.path.expanduser('~/.gajim'))
- for evt in gajim.config.get_per('soundevents'):
- path = gajim.config.get_per('soundevents', evt, 'path')
- # absolute and relative passes are necessary
- path = helpers.strip_soundfile_path(path, dirs, abs=False)
- path = helpers.strip_soundfile_path(path, dirs, abs=True)
- gajim.config.set_per('soundevents', evt, 'path', path)
- gajim.config.set('version', '0.12.1.5')
-
- def update_config_to_01231(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS roster_entry(
- account_jid_id INTEGER,
- jid_id INTEGER,
- name TEXT,
- subscription INTEGER,
- ask BOOLEAN,
- PRIMARY KEY (account_jid_id, jid_id)
- );
-
- CREATE TABLE IF NOT EXISTS roster_group(
- account_jid_id INTEGER,
- jid_id INTEGER,
- group_name TEXT,
- PRIMARY KEY (account_jid_id, jid_id, group_name)
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.12.3.1')
-
- def update_config_from_0125(self):
- # All those functions need to be called for 0.12.5 to 0.13 transition
- self.update_config_to_01211()
- self.update_config_to_01213()
- self.update_config_to_01214()
- self.update_config_to_01215()
- self.update_config_to_01231()
-
- def update_config_to_01251(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE unread_messages
- ADD shown BOOLEAN default 0;
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.12.5.1')
-
- def update_config_to_01252(self):
- if 'alwaysauth' in self.old_values:
- val = self.old_values['alwaysauth']
- for account in gajim.config.get_per('accounts'):
- gajim.config.set_per('accounts', account, 'autoauth', val)
- gajim.config.set('version', '0.12.5.2')
-
- def update_config_to_01253(self):
- if 'enable_zeroconf' in self.old_values:
- val = self.old_values['enable_zeroconf']
- for account in gajim.config.get_per('accounts'):
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- gajim.config.set_per('accounts', account, 'active', val)
- else:
- gajim.config.set_per('accounts', account, 'active', True)
- gajim.config.set('version', '0.12.5.3')
-
- def update_config_to_01254(self):
- vals = {'inmsgcolor': ['#a34526', '#a40000'],
- 'outmsgcolor': ['#164e6f', '#3465a4'],
- 'restored_messages_color': ['grey', '#555753'],
- 'statusmsgcolor': ['#1eaa1e', '#73d216'],
- 'urlmsgcolor': ['#0000ff', '#204a87'],
- 'gc_nicknames_colors': ['#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.4')
-
- def update_config_to_01255(self):
- vals = {'statusmsgcolor': ['#73d216', '#4e9a06'],
- 'outmsgtxtcolor': ['#a2a2a2', '#555753']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.5')
-
- def update_config_to_01256(self):
- vals = {'gc_nicknames_colors': ['#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', '#f57900:#ce5c00:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.6')
-
- def update_config_to_01257(self):
- if 'iconset' in self.old_values:
- if self.old_values['iconset'] in ('nuvola', 'crystal', 'gossip',
- 'simplebulb', 'stellar'):
- gajim.config.set('iconset', gajim.config.DEFAULT_ICONSET)
- gajim.config.set('version', '0.12.5.7')
-
- def update_config_to_01258(self):
- self.update_ft_proxies(to_remove=['proxy65.talkonaut.com',
- 'proxy.jabber.org', 'proxy.netlab.cz', 'transfer.jabber.freenet.de',
- 'proxy.jabber.cd.chalmers.se'], to_add=['proxy.eu.jabber.org',
- 'proxy.jabber.ru', 'proxy.jabbim.cz'])
- gajim.config.set('version', '0.12.5.8')
-
- def update_config_to_013100(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE caps_cache
- ADD last_seen INTEGER default %d;
- ''' % int(time())
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.13.10.0')
-
- def update_config_to_013101(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- DROP INDEX IF EXISTS idx_logs_jid_id_kind;
-
- CREATE INDEX IF NOT EXISTS
- idx_logs_jid_id_time ON logs (jid_id, time DESC);
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.13.10.1')
-
- def update_config_to_013901(self):
- schemes = 'aaa:// aaas:// acap:// cap:// cid: crid:// data: dav: dict:// dns: fax: file:/ ftp:// geo: go: gopher:// h323: http:// https:// iax: icap:// im: imap:// info: ipp:// iris: iris.beep: iris.xpc: iris.xpcs: iris.lwz: ldap:// mid: modem: msrp:// msrps:// mtqp:// mupdate:// news: nfs:// nntp:// opaquelocktoken: pop:// pres: prospero:// rtsp:// service: shttp:// sip: sips: sms: snmp:// soap.beep:// soap.beeps:// tag: tel: telnet:// tftp:// thismessage:/ tip:// tv: urn:// vemmi:// xmlrpc.beep:// xmlrpc.beeps:// z39.50r:// z39.50s:// about: apt: cvs:// daap:// ed2k:// feed: fish:// git:// iax2: irc:// ircs:// ldaps:// magnet: mms:// rsync:// ssh:// svn:// sftp:// smb:// webcal://'
- gajim.config.set('uri_schemes', schemes)
- gajim.config.set('version', '0.13.90.1')
-
- def update_config_to_01401(self):
- if 'autodetect_browser_mailer' not in self.old_values or 'openwith' \
- not in self.old_values or \
- (self.old_values['autodetect_browser_mailer'] == False and \
- self.old_values['openwith'] != 'custom'):
- gajim.config.set('autodetect_browser_mailer', True)
- gajim.config.set('openwith', gajim.config.DEFAULT_OPENWITH)
- gajim.config.set('version', '0.14.0.1')
-
- def update_config_to_014900(self):
- if 'use_stun_server' in self.old_values and self.old_values[
- 'use_stun_server'] and not self.old_values['stun_server']:
- gajim.config.set('use_stun_server', False)
- if os.name == 'nt':
- gajim.config.set('autodetect_browser_mailer', True)
-
- def update_config_to_01601(self):
- if 'last_mam_id' in self.old_values:
- last_mam_id = self.old_values['last_mam_id']
- for account in gajim.config.get_per('accounts'):
- gajim.config.set_per('accounts', account, 'last_mam_id',
- last_mam_id)
- gajim.config.set('version', '0.16.0.1')
-
- def update_config_to_01641(self):
- for account in self.old_values['accounts'].keys():
- connection_types = self.old_values['accounts'][account][
- 'connection_types'].split()
- if 'plain' in connection_types and len(connection_types) > 1:
- connection_types.remove('plain')
- gajim.config.set_per('accounts', account, 'connection_types',
- ' '.join(connection_types))
- gajim.config.set('version', '0.16.4.1')
-
- def update_config_to_016101(self):
- if 'video_input_device' in self.old_values:
- if self.old_values['video_input_device'] == 'autovideosrc ! videoscale ! ffmpegcolorspace':
- gajim.config.set('video_input_device', 'autovideosrc')
- if self.old_values['video_input_device'] == 'videotestsrc is-live=true ! video/x-raw-yuv,framerate=10/1':
- gajim.config.set('video_input_device', 'videotestsrc is-live=true ! video/x-raw,framerate=10/1')
- gajim.config.set('version', '0.16.10.1')
-
- def update_config_to_016102(self):
- for account in self.old_values['accounts'].keys():
- gajim.config.del_per('accounts', account, 'minimized_gc')
-
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE logs ADD COLUMN 'additional_data' TEXT DEFAULT '{}';
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
-
- gajim.config.set('version', '0.16.10.2')
-
- def update_config_to_016103(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE logs ADD COLUMN 'stanza_id' TEXT;
- ALTER TABLE logs ADD COLUMN 'mam_id' TEXT;
- ALTER TABLE logs ADD COLUMN 'encryption' TEXT;
- ALTER TABLE logs ADD COLUMN 'encryption_state' TEXT;
- ALTER TABLE logs ADD COLUMN 'marker' INTEGER;
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.16.10.3')
diff --git a/src/common/passwords.py b/src/common/passwords.py
deleted file mode 100644
index 72789b302..000000000
--- a/src/common/passwords.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/passwords.py
-##
-## Copyright (C) 2006 Gustavo J. A. M. Carneiro <gjcarneiro AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (c) 2009 Thorsten Glaser <t.glaser AT tarent.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/>.
-##
-
-import os
-import logging
-import gi
-from common import gajim
-
-__all__ = ['get_password', 'save_password']
-
-log = logging.getLogger('gajim.password')
-
-if os.name == 'nt':
- try:
- import keyring
- except ImportError:
- log.debug('python-keyring missing, falling back to plaintext storage')
-
-
-Secret = None
-
-class PasswordStorage(object):
- def get_password(self, account_name):
- raise NotImplementedError
- def save_password(self, account_name, password):
- raise NotImplementedError
-
-
-class SimplePasswordStorage(PasswordStorage):
- def get_password(self, account_name):
- passwd = gajim.config.get_per('accounts', account_name, 'password')
- if passwd and (passwd.startswith('libsecret:') or passwd.startswith('winvault:')):
- # this is not a real password, it’s stored through libsecret.
- return None
- else:
- return passwd
-
- def save_password(self, account_name, password):
- gajim.config.set_per('accounts', account_name, 'password', password)
- if account_name in gajim.connections:
- gajim.connections[account_name].password = password
-
-
-class SecretPasswordStorage(PasswordStorage):
- def __init__(self):
- self.GAJIM_SCHEMA = Secret.Schema.new("org.gnome.keyring.NetworkPassword",
- Secret.SchemaFlags.NONE,
- {
- 'user': Secret.SchemaAttributeType.STRING,
- 'server': Secret.SchemaAttributeType.STRING,
- 'protocol': Secret.SchemaAttributeType.STRING,
- }
- )
-
- def get_password(self, account_name):
- conf = gajim.config.get_per('accounts', account_name, 'password')
- if conf is None:
- return None
- if not conf.startswith('libsecret:'):
- password = conf
- ## migrate the password over to keyring
- try:
- self.save_password(account_name, password, update=False)
- except Exception:
- ## no keyring daemon: in the future, stop using it
- set_storage(SimplePasswordStorage())
- return password
- server = gajim.config.get_per('accounts', account_name, 'hostname')
- user = gajim.config.get_per('accounts', account_name, 'name')
- password = Secret.password_lookup_sync(self.GAJIM_SCHEMA, {'user': user,
- 'server': server, 'protocol': 'xmpp'}, None)
- return password
-
- def save_password(self, account_name, password, update=True):
- server = gajim.config.get_per('accounts', account_name, 'hostname')
- user = gajim.config.get_per('accounts', account_name, 'name')
- display_name = _('XMPP account %s@%s') % (user, server)
- if password is None:
- password = str()
- attributes = {'user': user, 'server': server, 'protocol': 'xmpp'}
- Secret.password_store_sync(self.GAJIM_SCHEMA, attributes,
- Secret.COLLECTION_DEFAULT, display_name, password, None)
- gajim.config.set_per('accounts', account_name, 'password',
- 'libsecret:')
- if account_name in gajim.connections:
- gajim.connections[account_name].password = password
-
-
-class SecretWindowsPasswordStorage(PasswordStorage):
- """ Windows Keyring """
-
- def __init__(self):
- self.win_keyring = keyring.get_keyring()
-
- def save_password(self, account_name, password):
- try:
- self.win_keyring.set_password('gajim', account_name, password)
- gajim.config.set_per(
- 'accounts', account_name, 'password', 'winvault:')
- except:
- log.exception('error:')
- set_storage(SimplePasswordStorage())
- storage.save_password(account_name, password)
-
- def get_password(self, account_name):
- log.debug('getting password')
- conf = gajim.config.get_per('accounts', account_name, 'password')
- if conf is None:
- return None
- if not conf.startswith('winvault:'):
- password = conf
- # migrate the password over to keyring
- self.save_password(account_name, password)
- return password
- return self.win_keyring.get_password('gajim', account_name)
-
-
-storage = None
-def get_storage():
- global storage
- if storage is None: # None is only in first time get_storage is called
- global Secret
- if gajim.config.get('use_keyring'):
- try:
- gi.require_version('Secret', '1')
- gir = __import__('gi.repository', globals(), locals(),
- ['Secret'], 0)
- Secret = gir.Secret
- except (ValueError, AttributeError):
- pass
- try:
- if os.name != 'nt':
- storage = SecretPasswordStorage()
- else:
- storage = SecretWindowsPasswordStorage()
- except Exception:
- storage = SimplePasswordStorage()
- else:
- storage = SimplePasswordStorage()
- return storage
-
-def set_storage(storage_):
- global storage
- storage = storage_
-
-def get_password(account_name):
- return get_storage().get_password(account_name)
-
-def save_password(account_name, password):
- if account_name in gajim.connections:
- gajim.connections[account_name].set_password(password)
- return get_storage().save_password(account_name, password)
diff --git a/src/common/pep.py b/src/common/pep.py
deleted file mode 100644
index 4a96a085a..000000000
--- a/src/common/pep.py
+++ /dev/null
@@ -1,473 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/pep.py
-##
-## Copyright (C) 2007 Piotr Gaczkowski <doomhammerng AT gmail.com>
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Jonathan Schleifer <js-common.gajim AT webkeks.org>
-## Stephan Erb <steve-e AT h3c.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/>.
-##
-
-MOODS = {
- 'afraid': _('Afraid'),
- 'amazed': _('Amazed'),
- 'amorous': _('Amorous'),
- 'angry': _('Angry'),
- 'annoyed': _('Annoyed'),
- 'anxious': _('Anxious'),
- 'aroused': _('Aroused'),
- 'ashamed': _('Ashamed'),
- 'bored': _('Bored'),
- 'brave': _('Brave'),
- 'calm': _('Calm'),
- 'cautious': _('Cautious'),
- 'cold': _('Cold'),
- 'confident': _('Confident'),
- 'confused': _('Confused'),
- 'contemplative': _('Contemplative'),
- 'contented': _('Contented'),
- 'cranky': _('Cranky'),
- 'crazy': _('Crazy'),
- 'creative': _('Creative'),
- 'curious': _('Curious'),
- 'dejected': _('Dejected'),
- 'depressed': _('Depressed'),
- 'disappointed': _('Disappointed'),
- 'disgusted': _('Disgusted'),
- 'dismayed': _('Dismayed'),
- 'distracted': _('Distracted'),
- 'embarrassed': _('Embarrassed'),
- 'envious': _('Envious'),
- 'excited': _('Excited'),
- 'flirtatious': _('Flirtatious'),
- 'frustrated': _('Frustrated'),
- 'grateful': _('Grateful'),
- 'grieving': _('Grieving'),
- 'grumpy': _('Grumpy'),
- 'guilty': _('Guilty'),
- 'happy': _('Happy'),
- 'hopeful': _('Hopeful'),
- 'hot': _('Hot'),
- 'humbled': _('Humbled'),
- 'humiliated': _('Humiliated'),
- 'hungry': _('Hungry'),
- 'hurt': _('Hurt'),
- 'impressed': _('Impressed'),
- 'in_awe': _('In Awe'),
- 'in_love': _('In Love'),
- 'indignant': _('Indignant'),
- 'interested': _('Interested'),
- 'intoxicated': _('Intoxicated'),
- 'invincible': _('Invincible'),
- 'jealous': _('Jealous'),
- 'lonely': _('Lonely'),
- 'lost': _('Lost'),
- 'lucky': _('Lucky'),
- 'mean': _('Mean'),
- 'moody': _('Moody'),
- 'nervous': _('Nervous'),
- 'neutral': _('Neutral'),
- 'offended': _('Offended'),
- 'outraged': _('Outraged'),
- 'playful': _('Playful'),
- 'proud': _('Proud'),
- 'relaxed': _('Relaxed'),
- 'relieved': _('Relieved'),
- 'remorseful': _('Remorseful'),
- 'restless': _('Restless'),
- 'sad': _('Sad'),
- 'sarcastic': _('Sarcastic'),
- 'satisfied': _('Satisfied'),
- 'serious': _('Serious'),
- 'shocked': _('Shocked'),
- 'shy': _('Shy'),
- 'sick': _('Sick'),
- 'sleepy': _('Sleepy'),
- 'spontaneous': _('Spontaneous'),
- 'stressed': _('Stressed'),
- 'strong': _('Strong'),
- 'surprised': _('Surprised'),
- 'thankful': _('Thankful'),
- 'thirsty': _('Thirsty'),
- 'tired': _('Tired'),
- 'undefined': _('Undefined'),
- 'weak': _('Weak'),
- 'worried': _('Worried')}
-
-ACTIVITIES = {
- 'doing_chores': {'category': _('Doing Chores'),
- 'buying_groceries': _('Buying Groceries'),
- 'cleaning': _('Cleaning'),
- 'cooking': _('Cooking'),
- 'doing_maintenance': _('Doing Maintenance'),
- 'doing_the_dishes': _('Doing the Dishes'),
- 'doing_the_laundry': _('Doing the Laundry'),
- 'gardening': _('Gardening'),
- 'running_an_errand': _('Running an Errand'),
- 'walking_the_dog': _('Walking the Dog')},
- 'drinking': {'category': _('Drinking'),
- 'having_a_beer': _('Having a Beer'),
- 'having_coffee': _('Having Coffee'),
- 'having_tea': _('Having Tea')},
- 'eating': {'category': _('Eating'),
- 'having_a_snack': _('Having a Snack'),
- 'having_breakfast': _('Having Breakfast'),
- 'having_dinner': _('Having Dinner'),
- 'having_lunch': _('Having Lunch')},
- 'exercising': {'category': _('Exercising'),
- 'cycling': _('Cycling'),
- 'dancing': _('Dancing'),
- 'hiking': _('Hiking'),
- 'jogging': _('Jogging'),
- 'playing_sports': _('Playing Sports'),
- 'running': _('Running'),
- 'skiing': _('Skiing'),
- 'swimming': _('Swimming'),
- 'working_out': _('Working out')},
- 'grooming': {'category': _('Grooming'),
- 'at_the_spa': _('At the Spa'),
- 'brushing_teeth': _('Brushing Teeth'),
- 'getting_a_haircut': _('Getting a Haircut'),
- 'shaving': _('Shaving'),
- 'taking_a_bath': _('Taking a Bath'),
- 'taking_a_shower': _('Taking a Shower')},
- 'having_appointment': {'category': _('Having an Appointment')},
- 'inactive': {'category': _('Inactive'),
- 'day_off': _('Day Off'),
- 'hanging_out': _('Hanging out'),
- 'hiding': _('Hiding'),
- 'on_vacation': _('On Vacation'),
- 'praying': _('Praying'),
- 'scheduled_holiday': _('Scheduled Holiday'),
- 'sleeping': _('Sleeping'),
- 'thinking': _('Thinking')},
- 'relaxing': {'category': _('Relaxing'),
- 'fishing': _('Fishing'),
- 'gaming': _('Gaming'),
- 'going_out': _('Going out'),
- 'partying': _('Partying'),
- 'reading': _('Reading'),
- 'rehearsing': _('Rehearsing'),
- 'shopping': _('Shopping'),
- 'smoking': _('Smoking'),
- 'socializing': _('Socializing'),
- 'sunbathing': _('Sunbathing'),
- 'watching_tv': _('Watching TV'),
- 'watching_a_movie': _('Watching a Movie')},
- 'talking': {'category': _('Talking'),
- 'in_real_life': _('In Real Life'),
- 'on_the_phone': _('On the Phone'),
- 'on_video_phone': _('On Video Phone')},
- 'traveling': {'category': _('Traveling'),
- 'commuting': _('Commuting'),
- 'cycling': _('Cycling'),
- 'driving': _('Driving'),
- 'in_a_car': _('In a Car'),
- 'on_a_bus': _('On a Bus'),
- 'on_a_plane': _('On a Plane'),
- 'on_a_train': _('On a Train'),
- 'on_a_trip': _('On a Trip'),
- 'walking': _('Walking')},
- 'working': {'category': _('Working'),
- 'coding': _('Coding'),
- 'in_a_meeting': _('In a Meeting'),
- 'studying': _('Studying'),
- 'writing': _('Writing')}}
-
-TUNE_DATA = ['artist', 'title', 'source', 'track', 'length']
-
-LOCATION_DATA = {
- 'accuracy': _('accuracy'),
- 'alt': _('alt'),
- 'area': _('area'),
- 'bearing': _('bearing'),
- 'building': _('building'),
- 'country': _('country'),
- 'countrycode': _('countrycode'),
- 'datum': _('datum'),
- 'description': _('description'),
- 'error': _('error'),
- 'floor': _('floor'),
- 'lat': _('lat'),
- 'locality': _('locality'),
- 'lon': _('lon'),
- 'postalcode': _('postalcode'),
- 'region': _('region'),
- 'room': _('room'),
- 'speed': _('speed'),
- 'street': _('street'),
- 'text': _('text'),
- 'timestamp': _('timestamp'),
- 'uri': _('uri')}
-
-from gi.repository import GLib
-
-import logging
-log = logging.getLogger('gajim.c.pep')
-
-import nbxmpp
-from common import gajim
-
-
-class AbstractPEP(object):
-
- type_ = ''
- namespace = ''
-
- @classmethod
- def get_tag_as_PEP(cls, jid, account, event_tag):
- items = event_tag.getTag('items', {'node': cls.namespace})
- if items:
- log.debug("Received PEP 'user %s' from %s" % (cls.type_, jid))
- return cls(jid, account, items)
- else:
- return None
-
- def __init__(self, jid, account, items):
- self._pep_specific_data, self._retracted = self._extract_info(items)
-
- self._update_contacts(jid, account)
- if jid == gajim.get_jid_from_account(account):
- self._update_account(account)
-
- def _extract_info(self, items):
- '''To be implemented by subclasses'''
- raise NotImplementedError
-
- def _update_contacts(self, jid, account):
- for contact in gajim.contacts.get_contacts(account, jid):
- if self._retracted:
- if self.type_ in contact.pep:
- del contact.pep[self.type_]
- else:
- contact.pep[self.type_] = self
-
- def _update_account(self, account):
- acc = gajim.connections[account]
- if self._retracted:
- if self.type_ in acc.pep:
- del acc.pep[self.type_]
- else:
- acc.pep[self.type_] = self
-
- def asMarkupText(self):
- '''SHOULD be implemented by subclasses'''
- return ''
-
-
-class UserMoodPEP(AbstractPEP):
- '''XEP-0107: User Mood'''
-
- type_ = 'mood'
- namespace = nbxmpp.NS_MOOD
-
- def _extract_info(self, items):
- mood_dict = {}
-
- for item in items.getTags('item'):
- mood_tag = item.getTag('mood')
- if mood_tag:
- for child in mood_tag.getChildren():
- name = child.getName().strip()
- if name == 'text':
- mood_dict['text'] = child.getData()
- else:
- mood_dict['mood'] = name
-
- retracted = items.getTag('retract') or not 'mood' in mood_dict
- return (mood_dict, retracted)
-
- def asMarkupText(self):
- assert not self._retracted
- untranslated_mood = self._pep_specific_data['mood']
- mood = self._translate_mood(untranslated_mood)
- markuptext = '<b>%s</b>' % GLib.markup_escape_text(mood)
- if 'text' in self._pep_specific_data:
- text = self._pep_specific_data['text']
- markuptext += ' (%s)' % GLib.markup_escape_text(text)
- return markuptext
-
- def _translate_mood(self, mood):
- if mood in MOODS:
- return MOODS[mood]
- else:
- return mood
-
-
-class UserTunePEP(AbstractPEP):
- '''XEP-0118: User Tune'''
-
- type_ = 'tune'
- namespace = nbxmpp.NS_TUNE
-
- def _extract_info(self, items):
- tune_dict = {}
-
- for item in items.getTags('item'):
- tune_tag = item.getTag('tune')
- if tune_tag:
- for child in tune_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if child.getName() in TUNE_DATA:
- tune_dict[name] = data
-
- retracted = items.getTag('retract') or not ('artist' in tune_dict or
- 'title' in tune_dict)
- return (tune_dict, retracted)
-
- def asMarkupText(self):
- assert not self._retracted
- tune = self._pep_specific_data
-
- artist = tune.get('artist', _('Unknown Artist'))
- artist = GLib.markup_escape_text(artist)
-
- title = tune.get('title', _('Unknown Title'))
- title = GLib.markup_escape_text(title)
-
- source = tune.get('source', _('Unknown Source'))
- source = GLib.markup_escape_text(source)
-
- tune_string = _('<b>"%(title)s"</b> by <i>%(artist)s</i>\n'
- 'from <i>%(source)s</i>') % {'title': title,
- 'artist': artist, 'source': source}
- return tune_string
-
-
-class UserActivityPEP(AbstractPEP):
- '''XEP-0108: User Activity'''
-
- type_ = 'activity'
- namespace = nbxmpp.NS_ACTIVITY
-
- def _extract_info(self, items):
- activity_dict = {}
-
- for item in items.getTags('item'):
- activity_tag = item.getTag('activity')
- if activity_tag:
- for child in activity_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if name == 'text':
- activity_dict['text'] = data
- else:
- activity_dict['activity'] = name
- for subactivity in child.getChildren():
- subactivity_name = subactivity.getName().strip()
- activity_dict['subactivity'] = subactivity_name
-
- retracted = items.getTag('retract') or not 'activity' in activity_dict
- return (activity_dict, retracted)
-
- def asMarkupText(self):
- assert not self._retracted
- pep = self._pep_specific_data
- activity = pep['activity']
- subactivity = pep['subactivity'] if 'subactivity' in pep else None
- text = pep['text'] if 'text' in pep else None
-
- if activity in ACTIVITIES:
- # Translate standard activities
- if subactivity in ACTIVITIES[activity]:
- subactivity = ACTIVITIES[activity][subactivity]
- activity = ACTIVITIES[activity]['category']
-
- markuptext = '<b>' + GLib.markup_escape_text(activity)
- if subactivity:
- markuptext += ': ' + GLib.markup_escape_text(subactivity)
- markuptext += '</b>'
- if text:
- markuptext += ' (%s)' % GLib.markup_escape_text(text)
- return markuptext
-
-
-class UserNicknamePEP(AbstractPEP):
- '''XEP-0172: User Nickname'''
-
- type_ = 'nickname'
- namespace = nbxmpp.NS_NICK
-
- def _extract_info(self, items):
- nick = ''
- for item in items.getTags('item'):
- child = item.getTag('nick')
- if child:
- nick = child.getData()
- break
-
- retracted = items.getTag('retract') or not nick
- return (nick, retracted)
-
- def _update_contacts(self, jid, account):
- nick = '' if self._retracted else self._pep_specific_data
- for contact in gajim.contacts.get_contacts(account, jid):
- contact.contact_name = nick
-
- def _update_account(self, account):
- if self._retracted:
- gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name')
- else:
- gajim.nicks[account] = self._pep_specific_data
-
-
-class UserLocationPEP(AbstractPEP):
- '''XEP-0080: User Location'''
-
- type_ = 'location'
- namespace = nbxmpp.NS_LOCATION
-
- def _extract_info(self, items):
- location_dict = {}
-
- for item in items.getTags('item'):
- location_tag = item.getTag('geoloc')
- if location_tag:
- for child in location_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if child.getName() in LOCATION_DATA:
- location_dict[name] = data
-
- retracted = items.getTag('retract') or not location_dict
- return (location_dict, retracted)
-
- def _update_account(self, account):
- AbstractPEP._update_account(self, account)
- con = gajim.connections[account].location_info = \
- self._pep_specific_data
-
- def asMarkupText(self):
- assert not self._retracted
- location = self._pep_specific_data
- location_string = ''
-
- for entry in location.keys():
- text = location[entry]
- text = GLib.markup_escape_text(text)
- # Translate standart location tag
- tag = LOCATION_DATA.get(entry, entry)
- location_string += '\n<b>%(tag)s</b>: %(text)s' % \
- {'tag': tag.capitalize(), 'text': text}
-
- return location_string.strip()
-
-
-SUPPORTED_PERSONAL_USER_EVENTS = [UserMoodPEP, UserTunePEP, UserActivityPEP,
- UserNicknamePEP, UserLocationPEP]
diff --git a/src/common/protocol/__init__.py b/src/common/protocol/__init__.py
deleted file mode 100644
index 2d167f344..000000000
--- a/src/common/protocol/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-Implementations of specific XMPP protocols and XEPs
-"""
diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py
deleted file mode 100644
index 809a682f3..000000000
--- a/src/common/protocol/bytestream.py
+++ /dev/null
@@ -1,1023 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/connection_handlers.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Junglecow J <junglecow AT gmail.com>
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import socket
-import base64
-from gi.repository import GLib
-import time
-
-import nbxmpp
-from common import gajim
-from common import helpers
-from common import ged
-from common import jingle_xtls
-from common.file_props import FilesProp
-from common.socks5 import Socks5SenderClient
-
-import logging
-log = logging.getLogger('gajim.c.p.bytestream')
-
-def is_transfer_paused(file_props):
- if file_props.stopped:
- return False
- if file_props.completed:
- return False
- if file_props.disconnect_cb:
- return False
- return file_props.paused
-
-def is_transfer_active(file_props):
- if file_props.stopped:
- return False
- if file_props.completed:
- return False
- if not file_props.started:
- return False
- if file_props.paused:
- return True
- return not file_props.paused
-
-def is_transfer_stopped(file_props):
- if not file_props:
- return True
- if file_props.error:
- return True
- if file_props.completed:
- return True
- if not file_props.stopped:
- return False
- return True
-
-
-class ConnectionBytestream:
-
- def __init__(self):
- gajim.ged.register_event_handler('file-request-received', ged.GUI1,
- self._nec_file_request_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('file-request-received', ged.GUI1,
- self._nec_file_request_received)
-
- def _ft_get_our_jid(self):
- our_jid = gajim.get_jid_from_account(self.name)
- resource = self.server_resource
- return our_jid + '/' + resource
-
- def _ft_get_receiver_jid(self, file_props):
- return file_props.receiver.jid + '/' + file_props.receiver.resource
-
- def _ft_get_from(self, iq_obj):
- return helpers.get_full_jid_from_iq(iq_obj)
-
- def _ft_get_streamhost_jid_attr(self, streamhost):
- return helpers.parse_jid(streamhost.getAttr('jid'))
-
- def send_file_request(self, file_props):
- """
- Send iq for new FT request
- """
- if not self.connection or self.connected < 2:
- return
- file_props.sender = self._ft_get_our_jid()
- fjid = self._ft_get_receiver_jid(file_props)
- iq = nbxmpp.Iq(to=fjid, typ='set')
- iq.setID(file_props.sid)
- si = iq.setTag('si', namespace=nbxmpp.NS_SI)
- si.setAttr('profile', nbxmpp.NS_FILE)
- si.setAttr('id', file_props.sid)
- file_tag = si.setTag('file', namespace=nbxmpp.NS_FILE)
- file_tag.setAttr('name', file_props.name)
- file_tag.setAttr('size', file_props.size)
- desc = file_tag.setTag('desc')
- if file_props.desc:
- desc.setData(file_props.desc)
- file_tag.setTag('range')
- feature = si.setTag('feature', namespace=nbxmpp.NS_FEATURE)
- _feature = nbxmpp.DataForm(typ='form')
- feature.addChild(node=_feature)
- field = _feature.setField('stream-method')
- field.setAttr('type', 'list-single')
- field.addOption(nbxmpp.NS_BYTESTREAM)
- field.addOption(nbxmpp.NS_IBB)
- self.connection.send(iq)
-
- def send_file_approval(self, file_props):
- """
- Send iq, confirming that we want to download the file
- """
- # user response to ConfirmationDialog may come after we've disconneted
- if not self.connection or self.connected < 2:
- return
-
- # file transfer initiated by a jingle session
- log.info("send_file_approval: jingle session accept")
- if file_props.session_type == 'jingle':
- session = self.get_jingle_session(file_props.sender,
- file_props.sid)
- if not session:
- return
- content = None
- for c in session.contents.values():
- if c.transport.sid == file_props.transport_sid:
- content = c
- break
- if not content:
- return
- if not session.accepted:
- content = session.get_content('file', content.name)
- if content.use_security:
- fingerprint = content.x509_fingerprint
- if not jingle_xtls.check_cert(
- gajim.get_jid_without_resource(file_props.sender),
- fingerprint):
- id_ = jingle_xtls.send_cert_request(self,
- file_props.sender)
- jingle_xtls.key_exchange_pend(id_,
- content.on_cert_received, [])
- return
- session.approve_session()
-
- session.approve_content('file', content.name)
- return
-
- iq = nbxmpp.Iq(to=file_props.sender, typ='result')
- iq.setAttr('id', file_props.request_id)
- si = iq.setTag('si', namespace=nbxmpp.NS_SI)
- if file_props.offset:
- file_tag = si.setTag('file', namespace=nbxmpp.NS_FILE)
- range_tag = file_tag.setTag('range')
- range_tag.setAttr('offset', file_props.offset)
- feature = si.setTag('feature', namespace=nbxmpp.NS_FEATURE)
- _feature = nbxmpp.DataForm(typ='submit')
- feature.addChild(node=_feature)
- field = _feature.setField('stream-method')
- field.delAttr('type')
- if nbxmpp.NS_BYTESTREAM in file_props.stream_methods:
- field.setValue(nbxmpp.NS_BYTESTREAM)
- else:
- file_props.transport_sid = file_props.sid
- field.setValue(nbxmpp.NS_IBB)
- self.connection.send(iq)
-
- def send_file_rejection(self, file_props, code='403', typ=None):
- """
- Inform sender that we refuse to download the file
-
- typ is used when code = '400', in this case typ can be 'strean' for
- invalid stream or 'profile' for invalid profile
- """
- # user response to ConfirmationDialog may come after we've disconneted
- if not self.connection or self.connected < 2:
- return
- if file_props.session_type == 'jingle':
- if file_props.sid in self._sessions:
- jingle = self._sessions[file_props.sid]
- jingle.cancel_session()
- return
- iq = nbxmpp.Iq(to=file_props.sender, typ='error')
- iq.setAttr('id', file_props.request_id)
- if code == '400' and typ in ('stream', 'profile'):
- name = 'bad-request'
- text = ''
- else:
- name = 'forbidden'
- text = 'Offer Declined'
- err = nbxmpp.ErrorNode(code=code, typ='cancel', name=name, text=text)
- if code == '400' and typ in ('stream', 'profile'):
- if typ == 'stream':
- err.setTag('no-valid-streams', namespace=nbxmpp.NS_SI)
- else:
- err.setTag('bad-profile', namespace=nbxmpp.NS_SI)
- iq.addChild(node=err)
- self.connection.send(iq)
-
- def _siResultCB(self, con, iq_obj):
- file_props = FilesProp.getFileProp(self.name, iq_obj.getAttr('id'))
- if not file_props:
- return
- if file_props.request_id:
- # we have already sent streamhosts info
- return
- file_props.receiver = self._ft_get_from(iq_obj)
- si = iq_obj.getTag('si')
- file_tag = si.getTag('file')
- range_tag = None
- if file_tag:
- range_tag = file_tag.getTag('range')
- if range_tag:
- offset = range_tag.getAttr('offset')
- if offset:
- file_props.offset = int(offset)
- length = range_tag.getAttr('length')
- if length:
- file_props.length = int(length)
- feature = si.setTag('feature')
- if feature.getNamespace() != nbxmpp.NS_FEATURE:
- return
- form_tag = feature.getTag('x')
- form = nbxmpp.DataForm(node=form_tag)
- field = form.getField('stream-method')
- if field.getValue() == nbxmpp.NS_BYTESTREAM:
- self._send_socks5_info(file_props)
- raise nbxmpp.NodeProcessed
- if field.getValue() == nbxmpp.NS_IBB:
- sid = file_props.sid
- file_props.transport_sid = sid
- fp = open(file_props.file_name, 'rb')
- self.OpenStream(sid, file_props.receiver, fp)
- raise nbxmpp.NodeProcessed
-
- def _siSetCB(self, con, iq_obj):
- from common.connection_handlers_events import FileRequestReceivedEvent
- gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_file_request_received(self, obj):
- pass
-
- def _siErrorCB(self, con, iq_obj):
- si = iq_obj.getTag('si')
- profile = si.getAttr('profile')
- if profile != nbxmpp.NS_FILE:
- return
- file_props = FilesProp.getFileProp(self.name, iq_obj.getAttr('id'))
- if not file_props:
- return
- jid = self._ft_get_from(iq_obj)
- file_props.error = -3
- from common.connection_handlers_events import FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
- jid=jid, file_props=file_props, error_msg=''))
- raise nbxmpp.NodeProcessed
-
-class ConnectionSocks5Bytestream(ConnectionBytestream):
-
- def send_success_connect_reply(self, streamhost):
- """
- Send reply to the initiator of FT that we made a connection
- """
- if not self.connection or self.connected < 2:
- return
- if streamhost is None:
- return None
- iq = nbxmpp.Iq(to=streamhost['initiator'], typ='result',
- frm=streamhost['target'])
- iq.setAttr('id', streamhost['id'])
- query = iq.setTag('query', namespace=nbxmpp.NS_BYTESTREAM)
- stream_tag = query.setTag('streamhost-used')
- stream_tag.setAttr('jid', streamhost['jid'])
- self.connection.send(iq)
-
- def stop_all_active_file_transfers(self, contact):
- """
- Stop all active transfer to or from the given contact
- """
- for file_props in FilesProp.getAllFileProp():
- if is_transfer_stopped(file_props):
- continue
- receiver_jid = file_props.receiver
- if contact.get_full_jid() == receiver_jid:
- file_props.error = -5
- self.remove_transfer(file_props)
- from common.connection_handlers_events import \
- FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
- conn=self, jid=contact.jid, file_props=file_props,
- error_msg=''))
- sender_jid = file_props.sender
- if contact.get_full_jid() == sender_jid:
- file_props.error = -3
- self.remove_transfer(file_props)
-
- def remove_all_transfers(self):
- """
- Stop and remove all active connections from the socks5 pool
- """
- for file_props in FilesProp.getAllFileProp():
- self.remove_transfer(file_props, remove_from_list=False)
-
- def remove_transfer(self, file_props, remove_from_list=True):
- if file_props is None:
- return
- self.disconnect_transfer(file_props)
- sid = file_props.sid
-
- def disconnect_transfer(self, file_props):
- if file_props is None:
- return
- if file_props.hash_:
- gajim.socks5queue.remove_sender(file_props.hash_)
-
- if file_props.streamhosts:
- for host in file_props.streamhosts:
- if 'idx' in host and host['idx'] > 0:
- gajim.socks5queue.remove_receiver(host['idx'])
- gajim.socks5queue.remove_sender(host['idx'])
-
- def _send_socks5_info(self, file_props):
- """
- Send iq for the present streamhosts and proxies
- """
- if not self.connection or self.connected < 2:
- return
- receiver = file_props.receiver
- sender = file_props.sender
-
- sha_str = helpers.get_auth_sha(file_props.sid, sender, receiver)
- file_props.sha_str = sha_str
-
- port = gajim.config.get('file_transfers_port')
- listener = gajim.socks5queue.start_listener(port, sha_str,
- self._result_socks5_sid, file_props)
- if not listener:
- file_props.error = -5
- from common.connection_handlers_events import FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
- jid=receiver, file_props=file_props, error_msg=''))
- self._connect_error(file_props.sid, error='not-acceptable',
- error_type='modify')
- else:
- iq = nbxmpp.Iq(to=receiver, typ='set')
- file_props.request_id = 'id_' + file_props.sid
- iq.setID(file_props.request_id)
- query = iq.setTag('query', namespace=nbxmpp.NS_BYTESTREAM)
- query.setAttr('sid', file_props.sid)
-
- self._add_addiditional_streamhosts_to_query(query, file_props)
- self._add_local_ips_as_streamhosts_to_query(query, file_props)
- self._add_proxy_streamhosts_to_query(query, file_props)
- self._add_upnp_igd_as_streamhost_to_query(query, file_props, iq)
- # Upnp-igd is ascynchronous, so it will send the iq itself
-
- def _add_streamhosts_to_query(self, query, sender, port, hosts):
- for host in hosts:
- streamhost = nbxmpp.Node(tag='streamhost')
- query.addChild(node=streamhost)
- streamhost.setAttr('port', str(port))
- streamhost.setAttr('host', host)
- streamhost.setAttr('jid', sender)
-
- def _add_local_ips_as_streamhosts_to_query(self, query, file_props):
- if not gajim.config.get_per('accounts', self.name, 'ft_send_local_ips'):
- return
- try:
- my_ips = [self.peerhost[0]] # The ip we're connected to server with
- # all IPs from local DNS
- for addr in socket.getaddrinfo(socket.gethostname(), None):
- if not addr[4][0] in my_ips and not addr[4][0].startswith('127') and not addr[4][0] == '::1':
- my_ips.append(addr[4][0])
-
- sender = file_props.sender
- port = gajim.config.get('file_transfers_port')
- self._add_streamhosts_to_query(query, sender, port, my_ips)
- except socket.gaierror:
- from common.connection_handlers_events import InformationEvent
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Wrong host'),
- sec_txt=_('Invalid local address? :-O')))
-
- def _add_addiditional_streamhosts_to_query(self, query, file_props):
- sender = file_props.sender
- port = gajim.config.get('file_transfers_port')
- ft_add_hosts_to_send = gajim.config.get('ft_add_hosts_to_send')
- additional_hosts = []
- if ft_add_hosts_to_send:
- additional_hosts = [e.strip() for e in ft_add_hosts_to_send.split(',')]
- else:
- additional_hosts = []
- self._add_streamhosts_to_query(query, sender, port, additional_hosts)
-
- def _add_upnp_igd_as_streamhost_to_query(self, query, file_props, iq):
- if not gajim.HAVE_UPNP_IGD:
- self.connection.send(iq)
- return
-
- my_ip = self.peerhost[0]
-
- # check if we are connected with an IPv4 address
- try:
- socket.inet_aton(my_ip)
- except socket.error as e:
- self.connection.send(iq)
- return
-
- def ip_is_local(ip):
- if '.' not in ip:
- # it's an IPv6
- return True
- ip_s = ip.split('.')
- ip_l = int(ip_s[0])<<24 | int(ip_s[1])<<16 | int(ip_s[2])<<8 | \
- int(ip_s[3])
- # 10/8
- if ip_l & (255<<24) == 10<<24:
- return True
- # 172.16/12
- if ip_l & (255<<24 | 240<<16) == (172<<24 | 16<<16):
- return True
- # 192.168
- if ip_l & (255<<24 | 255<<16) == (192<<24 | 168<<16):
- return True
- return False
-
- if not ip_is_local(my_ip):
- self.connection.send(iq)
- return
-
- self.no_gupnp_reply_id = 0
-
- def cleanup_gupnp():
- if self.no_gupnp_reply_id:
- GLib.source_remove(self.no_gupnp_reply_id)
- self.no_gupnp_reply_id = 0
- gajim.gupnp_igd.disconnect(self.ok_id)
- gajim.gupnp_igd.disconnect(self.fail_id)
-
- def ok(s, proto, ext_ip, re, ext_port, local_ip, local_port, desc):
- log.debug('Got GUPnP-IGD answer: external: %s:%s, internal: %s:%s',
- ext_ip, ext_port, local_ip, local_port)
- if local_port != gajim.config.get('file_transfers_port'):
- sender = file_props.sender
- receiver = file_props.receiver
- sha_str = helpers.get_auth_sha(file_props.sid, sender,
- receiver)
- listener = gajim.socks5queue.start_listener(local_port, sha_str,
- self._result_socks5_sid, file_props.sid)
- if listener:
- self._add_streamhosts_to_query(query, sender, ext_port,
- [ext_ip])
- else:
- self._add_streamhosts_to_query(query, file_props.sender,
- ext_port, [ext_ip])
- self.connection.send(iq)
- cleanup_gupnp()
-
- def fail(s, error, proto, ext_ip, local_ip, local_port, desc):
- log.debug('Got GUPnP-IGD error')
- self.connection.send(iq)
- cleanup_gupnp()
-
- def no_upnp_reply():
- log.debug('Got not GUPnP-IGD answer')
- # stop trying to use it
- gajim.HAVE_UPNP_IGD = False
- self.no_gupnp_reply_id = 0
- self.connection.send(iq)
- cleanup_gupnp()
- return False
-
-
- self.ok_id = gajim.gupnp_igd.connect('mapped-external-port', ok)
- self.fail_id = gajim.gupnp_igd.connect('error-mapping-port', fail)
-
- port = gajim.config.get('file_transfers_port')
- self.no_gupnp_reply_id = GLib.timeout_add_seconds(10, no_upnp_reply)
- gajim.gupnp_igd.add_port('TCP', 0, my_ip, port, 3600,
- 'Gajim file transfer')
-
- def _add_proxy_streamhosts_to_query(self, query, file_props):
- proxyhosts = self._get_file_transfer_proxies_from_config(file_props)
- if proxyhosts:
- file_props.proxy_receiver = file_props.receiver
- file_props.proxy_sender = file_props.sender
- file_props.proxyhosts = proxyhosts
-
- for proxyhost in proxyhosts:
- self._add_streamhosts_to_query(query, proxyhost['jid'],
- proxyhost['port'], [proxyhost['host']])
-
- def _get_file_transfer_proxies_from_config(self, file_props):
- configured_proxies = gajim.config.get_per('accounts', self.name,
- 'file_transfer_proxies')
- shall_use_proxies = gajim.config.get_per('accounts', self.name,
- 'use_ft_proxies')
- if shall_use_proxies:
- proxyhost_dicts = []
- proxies = []
- if configured_proxies:
- proxies = [item.strip() for item in configured_proxies.split(',')]
- default_proxy = gajim.proxy65_manager.get_default_for_name(self.name)
- if default_proxy:
- # add/move default proxy at top of the others
- if default_proxy in proxies:
- proxies.remove(default_proxy)
- proxies.insert(0, default_proxy)
-
- for proxy in proxies:
- (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name)
- if not host:
- continue
- host_dict = {
- 'state': 0,
- 'target': file_props.receiver,
- 'id': file_props.sid,
- 'sid': file_props.sid,
- 'initiator': proxy,
- 'host': host,
- 'port': str(_port),
- 'jid': jid
- }
- proxyhost_dicts.append(host_dict)
- return proxyhost_dicts
- else:
- return []
-
- def _result_socks5_sid(self, sid, hash_id):
- """
- Store the result of SHA message from auth
- """
- file_props = FilesProp.getFilePropBySid(sid)
- file_props.hash_ = hash_id
- return
-
- def _connect_error(self, sid, error, error_type, msg=None):
- """
- Called when there is an error establishing BS connection, or when
- connection is rejected
- """
- if not self.connection or self.connected < 2:
- return
- file_props = FilesProp.getFileProp(self.name, sid)
- if file_props is None:
- log.error('can not send iq error on failed transfer')
- return
- if file_props.type_ == 's':
- to = file_props.receiver
- else:
- to = file_props.sender
- iq = nbxmpp.Iq(to=to, typ='error')
- iq.setAttr('id', file_props.request_id)
- err = iq.setTag('error')
- err.setAttr('type', error_type)
- err.setTag(error, namespace=nbxmpp.NS_STANZAS)
- self.connection.send(iq)
- if msg:
- self.disconnect_transfer(file_props)
- file_props.error = -3
- from common.connection_handlers_events import \
- FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
- conn=self, jid=to, file_props=file_props, error_msg=msg))
-
- def _proxy_auth_ok(self, proxy):
- """
- Called after authentication to proxy server
- """
- if not self.connection or self.connected < 2:
- return
- file_props = FilesProp.getFileProp(self.name, proxy['sid'])
- iq = nbxmpp.Iq(to=proxy['initiator'], typ='set')
- auth_id = "au_" + proxy['sid']
- iq.setID(auth_id)
- query = iq.setTag('query', namespace=nbxmpp.NS_BYTESTREAM)
- query.setAttr('sid', proxy['sid'])
- activate = query.setTag('activate')
- activate.setData(file_props.proxy_receiver)
- iq.setID(auth_id)
- self.connection.send(iq)
-
- # register xmpppy handlers for bytestream and FT stanzas
- def _bytestreamErrorCB(self, con, iq_obj):
- id_ = iq_obj.getAttr('id')
- frm = helpers.get_full_jid_from_iq(iq_obj)
- query = iq_obj.getTag('query')
- gajim.proxy65_manager.error_cb(frm, query)
- jid = helpers.get_jid_from_iq(iq_obj)
- id_ = id_[3:]
- file_props = FilesProp.getFilePropBySid(id_)
- if not file_props:
- return
- file_props.error = -4
- from common.connection_handlers_events import FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
- jid=jid, file_props=file_props, error_msg=''))
- raise nbxmpp.NodeProcessed
-
- def _bytestreamSetCB(self, con, iq_obj):
- target = iq_obj.getAttr('to')
- id_ = iq_obj.getAttr('id')
- query = iq_obj.getTag('query')
- sid = query.getAttr('sid')
- file_props = FilesProp.getFileProp(self.name, sid)
- streamhosts = []
- for item in query.getChildren():
- if item.getName() == 'streamhost':
- host_dict = {
- 'state': 0,
- 'target': target,
- 'id': id_,
- 'sid': sid,
- 'initiator': self._ft_get_from(iq_obj)
- }
- for attr in item.getAttrs():
- host_dict[attr] = item.getAttr(attr)
- if 'host' not in host_dict:
- continue
- if 'jid' not in host_dict:
- continue
- if 'port' not in host_dict:
- continue
- streamhosts.append(host_dict)
- file_props = FilesProp.getFilePropBySid(sid)
- if file_props is not None:
- if file_props.type_ == 's': # FIXME: remove fast xmlns
- # only psi do this
- if file_props.streamhosts:
- file_props.streamhosts.extend(streamhosts)
- else:
- file_props.streamhosts = streamhosts
- gajim.socks5queue.connect_to_hosts(self.name, sid,
- self.send_success_connect_reply, None)
- raise nbxmpp.NodeProcessed
- else:
- log.warning('Gajim got streamhosts for unknown transfer. Ignoring it.')
- raise nbxmpp.NodeProcessed
-
- file_props.streamhosts = streamhosts
- def _connection_error(sid):
- self._connect_error(sid, 'item-not-found', 'cancel',
- msg='Could not connect to given hosts')
- if file_props.type_ == 'r':
- gajim.socks5queue.connect_to_hosts(self.name, sid,
- self.send_success_connect_reply, _connection_error)
- raise nbxmpp.NodeProcessed
-
- def _ResultCB(self, con, iq_obj):
- # if we want to respect xep-0065 we have to check for proxy
- # activation result in any result iq
- real_id = iq_obj.getAttr('id')
- if not real_id.startswith('au_'):
- return
- frm = self._ft_get_from(iq_obj)
- id_ = real_id[3:]
- file_props = FilesProp.getFilePropBySid(id_)
- if file_props.streamhost_used:
- for host in file_props.proxyhosts:
- if host['initiator'] == frm and 'idx' in host:
- gajim.socks5queue.activate_proxy(host['idx'])
- raise nbxmpp.NodeProcessed
-
- def _bytestreamResultCB(self, con, iq_obj):
- frm = self._ft_get_from(iq_obj)
- real_id = iq_obj.getAttr('id')
- query = iq_obj.getTag('query')
- gajim.proxy65_manager.resolve_result(frm, query)
-
- try:
- streamhost = query.getTag('streamhost-used')
- except Exception: # this bytestream result is not what we need
- pass
- id_ = real_id[3:]
- file_props = FilesProp.getFileProp(self.name, id_)
- if file_props is None:
- raise nbxmpp.NodeProcessed
- if streamhost is None:
- # proxy approves the activate query
- if real_id.startswith('au_'):
- if file_props.streamhost_used is False:
- raise nbxmpp.NodeProcessed
- if not file_props.proxyhosts:
- raise nbxmpp.NodeProcessed
- for host in file_props.proxyhosts:
- if host['initiator'] == frm and \
- query.getAttr('sid') == file_props.sid:
- gajim.socks5queue.activate_proxy(host['idx'])
- break
- raise nbxmpp.NodeProcessed
- jid = self._ft_get_streamhost_jid_attr(streamhost)
- if file_props.streamhost_used is True:
- raise nbxmpp.NodeProcessed
-
- if real_id.startswith('au_'):
- if file_props.stopped:
- self.remove_transfer(file_props)
- else:
- gajim.socks5queue.send_file(file_props, self.name)
- raise nbxmpp.NodeProcessed
-
- proxy = None
- if file_props.proxyhosts:
- for proxyhost in file_props.proxyhosts:
- if proxyhost['jid'] == jid:
- proxy = proxyhost
-
- if file_props.stopped:
- self.remove_transfer(file_props)
- raise nbxmpp.NodeProcessed
- if proxy is not None:
- file_props.streamhost_used = True
- file_props.streamhosts.append(proxy)
- file_props.is_a_proxy = True
- idx = gajim.socks5queue.idx
- sender = Socks5SenderClient(gajim.idlequeue, idx,
- gajim.socks5queue, _sock=None, host=str(proxy['host']),
- port=int(proxy['port']), fingerprint=None,
- connected=False, file_props=file_props)
- sender.streamhost = proxy
- gajim.socks5queue.add_sockobj(self.name, sender)
- proxy['idx'] = sender.queue_idx
- gajim.socks5queue.on_success[file_props.sid] = self._proxy_auth_ok
- raise nbxmpp.NodeProcessed
-
- else:
- if file_props.stopped:
- self.remove_transfer(file_props)
- else:
- gajim.socks5queue.send_file(file_props, self.name, 'server')
-
- raise nbxmpp.NodeProcessed
-
-
-class ConnectionIBBytestream(ConnectionBytestream):
-
- def __init__(self):
- ConnectionBytestream.__init__(self)
- self._streams = {}
-
- def IBBIqHandler(self, conn, stanza):
- """
- Handles streams state change. Used internally.
- """
- typ = stanza.getType()
- log.debug('IBBIqHandler called typ->%s' % typ)
- if typ == 'set' and stanza.getTag('open'):
- self.StreamOpenHandler(conn, stanza)
- elif typ == 'set' and stanza.getTag('close'):
- self.StreamCloseHandler(conn, stanza)
- elif typ == 'set' and stanza.getTag('data'):
- sid = stanza.getTagAttr('data', 'sid')
- file_props = FilesProp.getFilePropByTransportSid(self.name, sid)
- if not file_props:
- conn.send(nbxmpp.Error(stanza, nbxmpp.ERR_ITEM_NOT_FOUND))
- elif file_props.connected and self.IBBMessageHandler(conn,
- stanza):
- reply = stanza.buildReply('result')
- reply.delChild('data')
- conn.send(reply)
- elif not file_props.connected:
- log.debug('Received IQ for closed filetransfer, IQ dropped')
- elif typ == 'error':
- gajim.socks5queue.error_cb()
- else:
- conn.send(nbxmpp.Error(stanza, nbxmpp.ERR_BAD_REQUEST))
- raise nbxmpp.NodeProcessed
-
- def StreamOpenHandler(self, conn, stanza):
- """
- Handles opening of new incoming stream. Used internally.
- """
- err = None
- sid = stanza.getTagAttr('open', 'sid')
- blocksize = stanza.getTagAttr('open', 'block-size')
- log.debug('StreamOpenHandler called sid->%s blocksize->%s' % (sid,
- blocksize))
- file_props = FilesProp.getFilePropByTransportSid(self.name, sid)
- try:
- blocksize = int(blocksize)
- except:
- err = nbxmpp.ERR_BAD_REQUEST
- if not sid or not blocksize:
- err = nbxmpp.ERR_BAD_REQUEST
- elif not file_props:
- err = nbxmpp.ERR_UNEXPECTED_REQUEST
- if err:
- rep = nbxmpp.Error(stanza, err)
- else:
- log.debug("Opening stream: id %s, block-size %s" % (sid, blocksize))
- rep = nbxmpp.Protocol('iq', stanza.getFrom(), 'result',
- stanza.getTo(), {'id': stanza.getID()})
- file_props.block_size = blocksize
- file_props.direction = '<'
- file_props.seq = 0
- file_props.received_len = 0
- file_props.last_time = time.time()
- file_props.error = 0
- file_props.paused = False
- file_props.connected = True
- file_props.completed = False
- file_props.disconnect_cb = None
- file_props.continue_cb = None
- file_props.syn_id = stanza.getID()
- file_props.fp = open(file_props.file_name, 'wb')
- conn.send(rep)
-
- def CloseIBBStream(self, file_props):
- file_props.connected = False
- file_props.fp.close()
- file_props.stopped = True
- to = file_props.receiver
- if file_props.direction == '<':
- to = file_props.sender
- self.connection.send(
- nbxmpp.Protocol('iq', to, 'set',
- payload=[nbxmpp.Node(nbxmpp.NS_IBB + ' close',
- {'sid':file_props.transport_sid})]))
- if file_props.completed:
- gajim.socks5queue.complete_transfer_cb(self.name, file_props)
- elif file_props.session_type == 'jingle':
- peerjid = \
- file_props.receiver if file_props.type_ == 's' else file_props.sender
- session = self.get_jingle_session(peerjid, file_props.sid, 'file')
- # According to the xep, the initiator also cancels the jingle session
- # if there are no more files to send using IBB
- if session.weinitiate:
- session.cancel_session()
-
- def OpenStream(self, sid, to, fp, blocksize=4096):
- """
- Start new stream. You should provide stream id 'sid', the endpoind jid
- 'to', the file object containing info for send 'fp'. Also the desired
- blocksize can be specified.
- Take into account that recommended stanza size is 4k and IBB uses
- base64 encoding that increases size of data by 1/3.
- """
- file_props = FilesProp.getFilePropBySid(sid)
- file_props.direction = '>'
- file_props.block_size = blocksize
- file_props.fp = fp
- file_props.seq = 0
- file_props.error = 0
- file_props.paused = False
- file_props.received_len = 0
- file_props.last_time = time.time()
- file_props.connected = True
- file_props.completed = False
- file_props.disconnect_cb = None
- file_props.continue_cb = None
- syn = nbxmpp.Protocol('iq', to, 'set', payload=[nbxmpp.Node(
- nbxmpp.NS_IBB + ' open', {'sid': file_props.transport_sid,
- 'block-size': blocksize, 'stanza': 'iq'})])
- self.connection.send(syn)
- file_props.syn_id = syn.getID()
- return file_props
-
- def SendHandler(self, file_props):
- """
- Send next portion of data if it is time to do it. Used internally.
- """
- log.debug('SendHandler called')
- if file_props.completed:
- self.CloseIBBStream(file_props)
- if file_props.paused:
- return
- if not file_props.connected:
- #TODO: Reply with out of order error
- return
- chunk = file_props.fp.read(file_props.block_size)
- if chunk:
- datanode = nbxmpp.Node(nbxmpp.NS_IBB + ' data', {
- 'sid': file_props.transport_sid,
- 'seq': file_props.seq},
- base64.b64encode(chunk).decode('ascii'))
- file_props.seq += 1
- file_props.started = True
- if file_props.seq == 65536:
- file_props.seq = 0
- file_props.syn_id = self.connection.send(
- nbxmpp.Protocol(name='iq', to=file_props.receiver,
- typ='set', payload=[datanode]))
- current_time = time.time()
- file_props.elapsed_time += current_time - file_props.last_time
- file_props.last_time = current_time
- file_props.received_len += len(chunk)
- if file_props.size == file_props.received_len:
- file_props.completed = True
- gajim.socks5queue.progress_transfer_cb(self.name,
- file_props)
- else:
- log.debug('Nothing to read, but file not completed')
-
- def IBBMessageHandler(self, conn, stanza):
- """
- Receive next portion of incoming datastream and store it write
- it to temporary file. Used internally.
- """
- sid = stanza.getTagAttr('data', 'sid')
- seq = stanza.getTagAttr('data', 'seq')
- data = stanza.getTagData('data')
- log.debug('ReceiveHandler called sid->%s seq->%s' % (sid, seq))
- try:
- seq = int(seq)
- data = base64.b64decode(data.encode('utf-8'))
- except Exception:
- seq = ''
- data = b''
- err = None
- file_props = FilesProp.getFilePropByTransportSid(self.name, sid)
- if file_props is None:
- err = nbxmpp.ERR_ITEM_NOT_FOUND
- else:
- if not data:
- err = nbxmpp.ERR_BAD_REQUEST
- elif seq != file_props.seq:
- err = nbxmpp.ERR_UNEXPECTED_REQUEST
- else:
- log.debug('Successfull receive sid->%s %s+%s bytes' % (sid,
- file_props.fp.tell(), len(data)))
- file_props.seq += 1
- file_props.started = True
- file_props.fp.write(data)
- current_time = time.time()
- file_props.elapsed_time += current_time - file_props.last_time
- file_props.last_time = current_time
- file_props.received_len += len(data)
- gajim.socks5queue.progress_transfer_cb(self.name, file_props)
- if file_props.received_len >= file_props.size:
- file_props.completed = True
- if err:
- log.debug('Error on receive: %s' % err)
- conn.send(nbxmpp.Error(nbxmpp.Iq(to=stanza.getFrom(),
- frm=stanza.getTo(),
- payload=[nbxmpp.Node(nbxmpp.NS_IBB + ' close')]), err, reply=0))
- else:
- return True
-
- def StreamCloseHandler(self, conn, stanza):
- """
- Handle stream closure due to all data transmitted.
- Raise xmpppy event specifying successfull data receive.
- """
- sid = stanza.getTagAttr('close', 'sid')
- log.debug('StreamCloseHandler called sid->%s' % sid)
- # look in sending files
- file_props = FilesProp.getFilePropByTransportSid(self.name, sid)
- if file_props:
- reply = stanza.buildReply('result')
- reply.delChild('close')
- conn.send(reply)
- # look in receiving files
- file_props.fp.close()
- file_props.completed = file_props.received_len >= file_props.size
- if not file_props.completed:
- file_props.error = -1
- gajim.socks5queue.complete_transfer_cb(self.name, file_props)
- else:
- conn.send(nbxmpp.Error(stanza, nbxmpp.ERR_ITEM_NOT_FOUND))
-
-
- def IBBAllIqHandler(self, conn, stanza):
- """
- Handle remote side reply about if it agree or not to receive our
- datastream.
- Used internally. Raises xmpppy event specfiying if the data transfer
- is agreed upon.
- """
- syn_id = stanza.getID()
- log.debug('IBBAllIqHandler called syn_id->%s' % syn_id)
- for file_props in FilesProp.getAllFileProp():
- if not file_props.direction or not file_props.connected:
- # It's socks5 bytestream
- # Or we closed the IBB stream
- continue
- if file_props.syn_id == syn_id:
- if stanza.getType() == 'error':
- if file_props.direction[0] == '<':
- conn.Event('IBB', 'ERROR ON RECEIVE', file_props)
- else:
- conn.Event('IBB', 'ERROR ON SEND', file_props)
- elif stanza.getType() == 'result':
- self.SendHandler(file_props)
- break
-
-
-class ConnectionSocks5BytestreamZeroconf(ConnectionSocks5Bytestream):
-
- def _ft_get_from(self, iq_obj):
- return iq_obj.getFrom()
-
- def _ft_get_our_jid(self):
- return gajim.get_jid_from_account(self.name)
-
- def _ft_get_receiver_jid(self, file_props):
- return file_props.receiver.jid
-
- def _ft_get_streamhost_jid_attr(self, streamhost):
- return streamhost.getAttr('jid')
diff --git a/src/common/protocol/caps.py b/src/common/protocol/caps.py
deleted file mode 100644
index 8753449a2..000000000
--- a/src/common/protocol/caps.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/protocol/caps.py
-##
-## Copyright (C) 2009 Stephan Erb <steve-e AT h3c.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/>.
-##
-
-"""
-Module containing the network portion of XEP-115 (Entity Capabilities)
-"""
-
-import logging
-log = logging.getLogger('gajim.c.p.caps')
-
-from common import gajim
-from common import ged
-from common.connection_handlers_events import CapsPresenceReceivedEvent, \
- CapsDiscoReceivedEvent, CapsReceivedEvent
-
-
-class ConnectionCaps(object):
-
- def __init__(self, account, capscache, client_caps_factory):
- self._account = account
- self._capscache = capscache
- self._create_suitable_client_caps = client_caps_factory
- gajim.nec.register_incoming_event(CapsPresenceReceivedEvent)
- gajim.nec.register_incoming_event(CapsReceivedEvent)
- gajim.ged.register_event_handler('caps-presence-received', ged.GUI1,
- self._nec_caps_presence_received)
- gajim.ged.register_event_handler('agent-info-received', ged.GUI1,
- self._nec_agent_info_received_caps)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('caps-presence-received', ged.GUI1,
- self._nec_caps_presence_received)
- gajim.ged.remove_event_handler('agent-info-received', ged.GUI1,
- self._nec_agent_info_received_caps)
-
- def caps_change_account_name(self, new_name):
- self._account = new_name
-
- def _nec_caps_presence_received(self, obj):
- if obj.conn.name != self._account:
- return
- obj.client_caps = self._create_suitable_client_caps(obj.node,
- obj.caps_hash, obj.hash_method, obj.fjid)
- if obj.show == 'offline' and obj.client_caps._hash_method == 'no':
- self._capscache.forget_caps(obj.client_caps)
- obj.client_caps = self._create_suitable_client_caps(obj.node,
- obj.caps_hash, obj.hash_method)
- else:
- self._capscache.query_client_of_jid_if_unknown(self, obj.fjid,
- obj.client_caps)
- self._update_client_caps_of_contact(obj)
-
- def _update_client_caps_of_contact(self, obj):
- contact = self._get_contact_or_gc_contact_for_jid(obj.fjid)
- if contact:
- contact.client_caps = obj.client_caps
- else:
- log.info('Received Caps from unknown contact %s' % obj.fjid)
-
- def _get_contact_or_gc_contact_for_jid(self, jid):
- contact = gajim.contacts.get_contact_from_full_jid(self._account, jid)
- if contact is None:
- room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
- contact = gajim.contacts.get_gc_contact(self._account, room_jid, nick)
- return contact
-
- def _nec_agent_info_received_caps(self, obj):
- """
- callback to update our caps cache with queried information after
- we have retrieved an unknown caps hash and issued a disco
- """
- if obj.conn.name != self._account:
- return
- contact = self._get_contact_or_gc_contact_for_jid(obj.fjid)
- if not contact:
- log.info('Received Disco from unknown contact %s' % obj.fjid)
- return
-
- lookup = contact.client_caps.get_cache_lookup_strategy()
- cache_item = lookup(self._capscache)
-
- if cache_item.is_valid():
- # we already know that the hash is fine and have already cached
- # the identities and features
- return
- else:
- validate = contact.client_caps.get_hash_validation_strategy()
- hash_is_valid = validate(obj.identities, obj.features, obj.data)
-
- if hash_is_valid:
- cache_item.set_and_store(obj.identities, obj.features)
- else:
- node = caps_hash = hash_method = None
- contact.client_caps = self._create_suitable_client_caps(
- obj.node, caps_hash, hash_method)
- log.info('Computed and retrieved caps hash differ.' +
- 'Ignoring caps of contact %s' % contact.get_full_jid())
-
- gajim.nec.push_incoming_event(CapsDiscoReceivedEvent(None,
- conn=self, fjid=obj.fjid, jid=obj.jid, resource=obj.resource,
- client_caps=contact.client_caps))
diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py
deleted file mode 100644
index 0ebab1356..000000000
--- a/src/common/proxy65_manager.py
+++ /dev/null
@@ -1,487 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/proxy65_manager.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## 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/>.
-##
-
-import socket
-import struct
-import errno
-import logging
-log = logging.getLogger('gajim.c.proxy65_manager')
-
-import nbxmpp
-from common import gajim
-from common import helpers
-from common.socks5 import Socks5
-from nbxmpp.idlequeue import IdleObject
-from common.file_props import FilesProp
-
-S_INITIAL = 0
-S_STARTED = 1
-S_RESOLVED = 2
-S_ACTIVATED = 3
-S_FINISHED = 4
-
-CONNECT_TIMEOUT = 20
-
-class Proxy65Manager:
- """
- Keep records for file transfer proxies. Each time account establishes a
- connection to its server call proxy65manger.resolve(proxy) for every proxy
- that is convigured within the account. The class takes care to resolve and
- test each proxy only once
- """
-
- def __init__(self, idlequeue):
- # dict {proxy: proxy properties}
- self.idlequeue = idlequeue
- self.proxies = {}
- # dict {account: proxy} default proxy for account
- self.default_proxies = {}
-
- def resolve(self, proxy, connection, sender_jid, default=None,
- testit=True):
- """
- Start
- if testit=False, Gajim won't try to resolve it
- """
- if proxy in self.proxies:
- resolver = self.proxies[proxy]
- else:
- # proxy is being ressolved for the first time
- resolver = ProxyResolver(proxy, sender_jid, testit)
- self.proxies[proxy] = resolver
- resolver.add_connection(connection)
- if default:
- # add this proxy as default for account
- self.default_proxies[default] = proxy
-
- def disconnect(self, connection):
- for resolver in self.proxies.values():
- resolver.disconnect(connection)
-
- def resolve_result(self, proxy, query):
- if proxy not in self.proxies:
- return
- jid = None
- for item in query.getChildren():
- if item.getName() == 'streamhost':
- host = item.getAttr('host')
- jid = item.getAttr('jid')
- port = item.getAttr('port')
- try:
- port = int(port)
- except (ValueError, TypeError) as e:
- port = 1080
- if not host or not jid:
- self.proxies[proxy]._on_connect_failure()
- self.proxies[proxy].resolve_result(host, port, jid)
- # we can have only one streamhost
- raise nbxmpp.NodeProcessed
-
- def error_cb(self, proxy, query):
- sid = query.getAttr('sid')
- for resolver in self.proxies.values():
- if resolver.sid == sid:
- resolver.keep_conf()
- break
-
- def get_default_for_name(self, account):
- if account in self.default_proxies:
- return self.default_proxies[account]
-
- def get_proxy(self, proxy, account):
- if proxy in self.proxies:
- resolver = self.proxies[proxy]
- if resolver.state == S_FINISHED:
- return (resolver.host, resolver.port, resolver.jid)
- return (None, 0, None)
-
-class ProxyResolver:
- def resolve_result(self, host, port, jid):
- """
- Test if host has a real proxy65 listening on port
- """
- self.host = str(host)
- self.port = int(port)
- self.jid = str(jid)
- if not self.testit:
- self.state = S_FINISHED
- return
- self.state = S_INITIAL
- log.info('start resolving %s:%s' % (self.host, self.port))
- self.receiver_tester = ReceiverTester(self.host, self.port, self.jid,
- self.sid, self.sender_jid, self._on_receiver_success,
- self._on_connect_failure)
- self.receiver_tester.connect()
-
- def _on_receiver_success(self):
- log.debug('Receiver successfully connected %s:%s' % (self.host,
- self.port))
- self.host_tester = HostTester(self.host, self.port, self.jid,
- self.sid, self.sender_jid, self._on_connect_success,
- self._on_connect_failure)
- self.host_tester.connect()
-
- def _on_connect_success(self):
- log.debug('Host successfully connected %s:%s' % (self.host, self.port))
- iq = nbxmpp.Protocol(name='iq', to=self.jid, typ='set')
- query = iq.setTag('query')
- query.setNamespace(nbxmpp.NS_BYTESTREAM)
- query.setAttr('sid', self.sid)
-
- activate = query.setTag('activate')
- activate.setData('test@gajim.org/test2')
-
- if self.active_connection:
- log.debug('Activating bytestream on %s:%s' % (self.host, self.port))
- self.active_connection.SendAndCallForResponse(iq,
- self._result_received)
- self.state = S_ACTIVATED
- else:
- self.state = S_INITIAL
-
- def _result_received(self, data):
- self.disconnect(self.active_connection)
- if data.getType() == 'result':
- self.keep_conf()
- else:
- self._on_connect_failure()
-
- def keep_conf(self):
- log.debug('Bytestream activated %s:%s' % (self.host, self.port))
- self.state = S_FINISHED
-
- def _on_connect_failure(self):
- log.debug('Connection failed with %s:%s' % (self.host, self.port))
- self.state = S_FINISHED
- self.host = None
- self.port = 0
- self.jid = None
-
- def disconnect(self, connection):
- if self.host_tester:
- self.host_tester.disconnect()
- FilesProp.deleteFileProp(self.host_tester.file_props)
- self.host_tester = None
- if self.receiver_tester:
- self.receiver_tester.disconnect()
- FilesProp.deleteFileProp(self.receiver_tester.file_props)
- self.receiver_tester = None
- try:
- self.connections.remove(connection)
- except ValueError:
- pass
- if connection == self.active_connection:
- self.active_connection = None
- if self.state != S_FINISHED:
- self.state = S_INITIAL
- self.try_next_connection()
-
- def try_next_connection(self):
- """
- Try to resolve proxy with the next possible connection
- """
- if self.connections:
- connection = self.connections.pop(0)
- self.start_resolve(connection)
-
- def add_connection(self, connection):
- """
- Add a new connection in case the first fails
- """
- self.connections.append(connection)
- if self.state == S_INITIAL:
- self.start_resolve(connection)
-
- def start_resolve(self, connection):
- """
- Request network address from proxy
- """
- self.state = S_STARTED
- self.active_connection = connection
- iq = nbxmpp.Protocol(name='iq', to=self.proxy, typ='get')
- query = iq.setTag('query')
- query.setNamespace(nbxmpp.NS_BYTESTREAM)
- connection.send(iq)
-
- def __init__(self, proxy, sender_jid, testit):
- """
- if testit is False, don't test it, only get IP/port
- """
- self.proxy = proxy
- self.state = S_INITIAL
- self.active_connection = None
- self.connections = []
- self.host_tester = None
- self.receiver_tester = None
- self.jid = None
- self.host = None
- self.port = None
- self.sid = helpers.get_random_string_16()
- self.sender_jid = sender_jid
- self.testit = testit
-
-class HostTester(Socks5, IdleObject):
- """
- Fake proxy tester
- """
-
- def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
- """
- Try to establish and auth to proxy at (host, port)
-
- Calls on_success, or on_failure according to the result.
- """
- self.host = host
- self.port = port
- self.jid = jid
- self.on_success = on_success
- self.on_failure = on_failure
- self._sock = None
- self.file_props = FilesProp.getNewFileProp(jid, sid)
- self.file_props.is_a_proxy = True
- self.file_props.proxy_sender = sender_jid
- self.file_props.proxy_receiver = 'test@gajim.org/test2'
- Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
- self.sid = sid
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.host is None:
- self.on_failure()
- return None
- self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- gajim.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- self.pollend()
-
- def pollend(self):
- self.disconnect()
- self.on_failure()
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- data = self._get_auth_buff()
- self.send_raw(data)
- else:
- return
- self.state += 1
- # unplug and plug for reading
- gajim.idlequeue.plug_idle(self, False, True)
- gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 2:
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- # begin negotiation. on success 'address' != 0
- buff = self.receive()
- if buff == b'':
- # end connection
- self.pollend()
- return
- # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.pollend()
- return
- data = self._get_request_buff(self._get_sha1_auth())
- self.send_raw(data)
- self.state += 1
- log.debug('Host authenticating to %s:%s' % (self.host, self.port))
- elif self.state == 3:
- log.debug('Host authenticated to %s:%s' % (self.host, self.port))
- self.on_success()
- self.disconnect()
- self.state += 1
- else:
- assert False, 'unexpected state: %d' % self.state
-
- def do_connect(self):
- try:
- self._sock.connect((self.host, self.port))
- self._sock.setblocking(False)
- log.debug('Host Connecting to %s:%s' % (self.host, self.port))
- self._send = self._sock.send
- self._recv = self._sock.recv
- except Exception as ee:
- errnum = ee.errno
- # 56 is for freebsd
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- # still trying to connect
- return
- # win32 needs this
- if errnum not in (0, 10056, errno.EISCONN):
- # connection failed
- self.on_failure()
- return
- # socket is already connected
- self._sock.setblocking(False)
- self._send = self._sock.send
- self._recv = self._sock.recv
- self.buff = b''
- self.state = 1 # connected
- log.debug('Host connected to %s:%s' % (self.host, self.port))
- self.idlequeue.plug_idle(self, True, False)
- return
-
-class ReceiverTester(Socks5, IdleObject):
- """
- Fake proxy tester
- """
-
- def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
- """
- Try to establish and auth to proxy at (host, port)
-
- Call on_success, or on_failure according to the result.
- """
- self.host = host
- self.port = port
- self.jid = jid
- self.on_success = on_success
- self.on_failure = on_failure
- self._sock = None
- self.file_props = FilesProp.getNewFileProp(jid, sid)
- self.file_props.is_a_proxy = True
- self.file_props.proxy_sender = sender_jid
- self.file_props.proxy_receiver = 'test@gajim.org/test2'
- Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
- self.sid = sid
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.host is None:
- self.on_failure()
- return None
- self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- gajim.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- self.pollend()
-
- def pollend(self):
- self.disconnect()
- self.on_failure()
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- data = self._get_auth_buff()
- self.send_raw(data)
- else:
- return
- self.state += 1
- # unplug and plug for reading
- gajim.idlequeue.plug_idle(self, False, True)
- gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state in (2, 3):
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- # begin negotiation. on success 'address' != 0
- buff = self.receive()
- if buff == b'':
- # end connection
- self.pollend()
- return
- if self.state == 2:
- # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.pollend()
- return
- log.debug('Receiver authenticating to %s:%s' % (self.host, self.port))
- data = self._get_request_buff(self._get_sha1_auth())
- self.send_raw(data)
- self.state += 1
- elif self.state == 3:
- # read connect response
- if buff is None or len(buff) < 2:
- return None
- version, reply = struct.unpack('!BB', buff[:2])
- if version != 0x05 or reply != 0x00:
- self.pollend()
- return
- log.debug('Receiver authenticated to %s:%s' % (self.host, self.port))
- self.on_success()
- self.disconnect()
- self.state += 1
- else:
- assert False, 'unexpected state: %d' % self.state
-
- def do_connect(self):
- try:
- self._sock.setblocking(False)
- self._sock.connect((self.host, self.port))
- log.debug('Receiver Connecting to %s:%s' % (self.host, self.port))
- self._send = self._sock.send
- self._recv = self._sock.recv
- except Exception as ee:
- errnum = ee.errno
- # 56 is for freebsd
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- # still trying to connect
- return
- # win32 needs this
- if errnum not in (0, 10056, errno.EISCONN):
- # connection failed
- self.on_failure()
- return
- # socket is already connected
- self._sock.setblocking(False)
- self._send = self._sock.send
- self._recv = self._sock.recv
- self.buff = ''
- self.state = 1 # connected
- log.debug('Receiver connected to %s:%s' % (self.host, self.port))
- self.idlequeue.plug_idle(self, True, False)
diff --git a/src/common/pubsub.py b/src/common/pubsub.py
deleted file mode 100644
index 3a219fce2..000000000
--- a/src/common/pubsub.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/pubsub.py
-##
-## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Stephan Erb <steve-e AT h3c.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/>.
-##
-
-import nbxmpp
-from common import gajim
-#TODO: Doesn't work
-#from common.connection_handlers import PEP_CONFIG
-PEP_CONFIG = 'pep_config'
-from common import ged
-from common.connection_handlers_events import PubsubReceivedEvent
-from common.connection_handlers_events import PubsubBookmarksReceivedEvent
-import logging
-log = logging.getLogger('gajim.c.pubsub')
-
-class ConnectionPubSub:
- def __init__(self):
- self.__callbacks = {}
- gajim.nec.register_incoming_event(PubsubBookmarksReceivedEvent)
- gajim.ged.register_event_handler('pubsub-bookmarks-received',
- ged.CORE, self._nec_pubsub_bookmarks_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('pubsub-bookmarks-received',
- ged.CORE, self._nec_pubsub_bookmarks_received)
-
- def send_pb_subscription_query(self, jid, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('get', to=jid)
- pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- pb.addChild('subscriptions')
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_] = (cb, args, kwargs)
-
- def send_pb_subscribe(self, jid, node, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- query = nbxmpp.Iq('set', to=jid)
- pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- pb.addChild('subscribe', {'node': node, 'jid': our_jid})
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_] = (cb, args, kwargs)
-
- def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- query = nbxmpp.Iq('set', to=jid)
- pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- pb.addChild('unsubscribe', {'node': node, 'jid': our_jid})
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_] = (cb, args, kwargs)
-
- def send_pb_publish(self, jid, node, item, id_=None, options=None):
- """
- Publish item to a node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- p = e.addChild('publish', {'node': node})
- attrs = {}
- if id_:
- attrs = {'id': id_}
- p.addChild('item', attrs, [item])
- if options:
- p = e.addChild('publish-options')
- p.addChild(node=options)
-
- self.connection.send(query)
-
- def send_pb_retrieve(self, jid, node, cb=None, *args, **kwargs):
- """
- Get items from a node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('get', to=jid)
- r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- r = r.addChild('items', {'node': node})
- id_ = self.connection.send(query)
-
- if cb:
- self.__callbacks[id_] = (cb, args, kwargs)
-
- def send_pb_retract(self, jid, node, id_):
- """
- Delete item from a node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- r = r.addChild('retract', {'node': node, 'notify': '1'})
- r = r.addChild('item', {'id': id_})
-
- self.connection.send(query)
-
- def send_pb_purge(self, jid, node):
- """
- Purge node: Remove all items
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- d = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
- d = d.addChild('purge', {'node': node})
-
- self.connection.send(query)
-
- def send_pb_delete(self, jid, node, on_ok=None, on_fail=None):
- """
- Delete node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- d = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
- d = d.addChild('delete', {'node': node})
-
- def response(con, resp, jid, node):
- if resp.getType() == 'result' and on_ok:
- on_ok(jid, node)
- elif on_fail:
- msg = resp.getErrorMsg()
- on_fail(jid, node, msg)
-
- self.connection.SendAndCallForResponse(query, response, {'jid': jid,
- 'node': node})
-
- def send_pb_create(self, jid, node, configure=False, configure_form=None):
- """
- Create a new node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- c = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- c = c.addChild('create', {'node': node})
- if configure:
- conf = c.addChild('configure')
- if configure_form is not None:
- conf.addChild(node=configure_form)
-
- self.connection.send(query)
-
- def send_pb_configure(self, jid, node, form):
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- c = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
- c = c.addChild('configure', {'node': node})
- c.addChild(node=form)
-
- self.connection.send(query)
-
- def _PubSubCB(self, conn, stanza):
- log.debug('_PubsubCB')
- try:
- cb, args, kwargs = self.__callbacks.pop(stanza.getID())
- cb(conn, stanza, *args, **kwargs)
- except Exception:
- pass
- gajim.nec.push_incoming_event(PubsubReceivedEvent(None,
- conn=self, stanza=stanza))
-
- def _nec_pubsub_bookmarks_received(self, obj):
- if obj.conn.name != self.name:
- return
- bm_jids = [b['jid'] for b in self.bookmarks]
- for bm in obj.bookmarks:
- if bm['jid'] not in bm_jids:
- self.bookmarks.append(bm)
- # We got bookmarks from pubsub, now get those from xml to merge them
- self.get_bookmarks(storage_type='xml')
-
- def _PubSubErrorCB(self, conn, stanza):
- log.debug('_PubsubErrorCB')
- pubsub = stanza.getTag('pubsub')
- if not pubsub:
- return
- items = pubsub.getTag('items')
- if not items:
- return
- if items.getAttr('node') == 'storage:bookmarks':
- # Receiving bookmarks from pubsub failed, so take them from xml
- self.get_bookmarks(storage_type='xml')
-
- def request_pb_configuration(self, jid, node):
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('get', to=jid)
- e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
- e = e.addChild('configure', {'node': node})
- id_ = self.connection.getAnID()
- query.setID(id_)
- self.awaiting_answers[id_] = (PEP_CONFIG,)
- self.connection.send(query)
diff --git a/src/common/resolver.py b/src/common/resolver.py
deleted file mode 100644
index 736463655..000000000
--- a/src/common/resolver.py
+++ /dev/null
@@ -1,157 +0,0 @@
-## common/resolver.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov@gmail.com>
-##
-## 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/>.
-##
-
-import sys
-import logging
-import functools
-log = logging.getLogger('gajim.c.resolver')
-
-if __name__ == '__main__':
- sys.path.append('..')
- from common import i18n
- import common.configpaths
- common.configpaths.gajimpaths.init(None)
-
-from gi.repository import Gio, GLib
-
-
-def get_resolver(idlequeue):
- return GioResolver()
-
-
-class CommonResolver():
- def __init__(self):
- # dict {"host+type" : list of records}
- self.resolved_hosts = {}
- # dict {"host+type" : list of callbacks}
- self.handlers = {}
-
- def resolve(self, host, on_ready, type_='srv'):
- host = host.lower()
- log.debug('resolve %s type=%s' % (host, type_))
- assert(type_ in ['srv', 'txt'])
- if not host:
- # empty host, return empty list of srv records
- on_ready([])
- return
- if host + type_ in self.resolved_hosts:
- # host is already resolved, return cached values
- log.debug('%s already resolved: %s' % (host,
- self.resolved_hosts[host + type_]))
- on_ready(host, self.resolved_hosts[host + type_])
- return
- if host + type_ in self.handlers:
- # host is about to be resolved by another connection,
- # attach our callback
- log.debug('already resolving %s' % host)
- self.handlers[host + type_].append(on_ready)
- else:
- # host has never been resolved, start now
- log.debug('Starting to resolve %s using %s' % (host, self))
- self.handlers[host + type_] = [on_ready]
- self.start_resolve(host, type_)
-
- def _on_ready(self, host, type_, result_list):
- # practically it is impossible to be the opposite, but who knows :)
- host = host.lower()
- log.debug('Resolving result for %s: %s' % (host, result_list))
- if host + type_ not in self.resolved_hosts:
- self.resolved_hosts[host + type_] = result_list
- if host + type_ in self.handlers:
- for callback in self.handlers[host + type_]:
- callback(host, result_list)
- del(self.handlers[host + type_])
-
- def start_resolve(self, host, type_):
- pass
-
-
-class GioResolver(CommonResolver):
- """
- Asynchronous resolver using GIO. process() method has to be
- called in order to proceed the pending requests.
- """
-
- def __init__(self):
- super().__init__()
- self.gio_resolver = Gio.Resolver.get_default()
-
- def start_resolve(self, host, type_):
- if type_ == 'txt':
- # TXT record resolution isn't used anywhere at the moment so
- # implementing it here isn't urgent
- raise NotImplemented("Gio resolver does not currently implement TXT records")
- else:
- callback = functools.partial(self._on_ready_srv, host)
- type_ = Gio.ResolverRecordType.SRV
-
- resq = self.gio_resolver.lookup_records_async(host, type_, None, callback)
-
- def _on_ready_srv(self, host, source_object, result):
- try:
- variant_results = source_object.lookup_records_finish(result)
- except GLib.Error as e:
- if e.domain == 'g-resolver-error-quark':
- result_list = []
- log.warning("Could not resolve host: %s", e.message)
- else:
- raise
- else:
- result_list = [
- {
- 'weight': weight,
- 'prio': prio,
- 'port': port,
- 'host': host,
- }
- for prio, weight, port, host
- in variant_results
- ]
- super()._on_ready(host, 'srv', result_list)
-
-
-# below lines is on how to use API and assist in testing
-if __name__ == '__main__':
- from gi.repository import Gtk
- from nbxmpp import idlequeue
-
- idlequeue = idlequeue.get_idlequeue()
- resolver = get_resolver(idlequeue)
-
- def clicked(widget):
- global resolver
- host = text_view.get_text()
- def on_result(host, result_array):
- print('Result:\n' + repr(result_array))
- resolver.resolve(host, on_result)
- win = Gtk.Window()
- win.set_border_width(6)
- win.connect('remove', Gtk.main_quit)
- text_view = Gtk.Entry()
- text_view.set_text('_xmpp-client._tcp.jabber.org')
- hbox = Gtk.HBox()
- hbox.set_spacing(3)
- but = Gtk.Button(' Lookup SRV ')
- hbox.pack_start(text_view, 5, True, 0)
- hbox.pack_start(but, 0, True, 0)
- but.connect('clicked', clicked)
- win.add(hbox)
- win.show_all()
- GLib.timeout_add(200, idlequeue.process)
- Gtk.main()
diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py
deleted file mode 100644
index 77d73e562..000000000
--- a/src/common/rst_xhtml_generator.py
+++ /dev/null
@@ -1,169 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/rst_xhtml_generator.py
-##
-## Copyright (C) 2006 Santiago Gala
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-##
-## 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/>.
-##
-
-try:
- from docutils import io
- from docutils.core import Publisher
- from docutils.parsers.rst import roles
- from docutils import nodes, utils
- from docutils.parsers.rst.roles import set_classes
-except ImportError:
- print("Requires docutils 0.4 for set_classes to be available")
- def create_xhtml(text):
- return None
-else:
- def pos_int_validator(text):
- """
- Validates that text can be evaluated as a positive integer
- """
- result = int(text)
- if result < 0:
- raise ValueError("Error: value '%(text)s' "
- "must be a positive integer")
- return result
-
- def generate_uri_role( role_name, aliases, anchor_text, base_url,
- interpret_url, validator):
- """
- Create and register a uri based "interpreted role"
-
- Those are similar to the RFC, and PEP ones, and take
- role_name:
- name that will be registered
- aliases:
- list of alternate names
- anchor_text:
- text that will be used, together with the role
- base_url:
- base url for the link
- interpret_url:
- this, modulo the validated text, will be added to it
- validator:
- should return the validated text, or raise ValueError
- """
- def uri_reference_role(role, rawtext, text, lineno, inliner,
- options=None, content=None):
- if options is None:
- options = {}
- try:
- valid_text = validator(text)
- except ValueError as e:
- msg = inliner.reporter.error( e.message % dict(text=text), line=lineno)
- prb = inliner.problematic(rawtext, rawtext, msg)
- return [prb], [msg]
- ref = base_url + interpret_url % valid_text
- set_classes(options)
- node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref,
- **options)
- return [node], []
-
- uri_reference_role.__doc__ = """Role to make handy references to URIs.
-
- Use as :%(role_name)s:`71` (or any of %(aliases)s).
- It will use %(base_url)s+%(interpret_url)s
- validator should throw a ValueError, containing optionally
- a %%(text)s format, if the interpreted text is not valid.
- """ % locals()
- roles.register_canonical_role(role_name, uri_reference_role)
- from docutils.parsers.rst.languages import en
- en.roles[role_name] = role_name
- for alias in aliases:
- en.roles[alias] = role_name
-
- generate_uri_role('xep-reference', ('jep', 'xep'),
- 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html',
- pos_int_validator)
- generate_uri_role('gajim-ticket-reference', ('ticket', 'gtrack'),
- 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d',
- pos_int_validator)
-
- class HTMLGenerator:
- """
- Really simple HTMLGenerator starting from publish_parts
-
- It reuses the docutils.core.Publisher class, which means it is *not*
- threadsafe.
- """
- def __init__(self, settings_spec=None, settings_overrides=None,
- config_section='general'):
- if settings_overrides is None:
- settings_overrides = {'report_level': 5, 'halt_level': 5}
- self.pub = Publisher(reader=None, parser=None, writer=None,
- settings=None,
- source_class=io.StringInput,
- destination_class=io.StringOutput)
- self.pub.set_components(reader_name='standalone',
- parser_name='restructuredtext',
- writer_name='html')
- # hack: JEP-0071 does not allow HTML char entities, so we hack our way
- # out of it.
- # &mdash; == u"\u2014"
- # a setting to only emit charater entities in the writer would be nice
- # FIXME: several &nbsp; are emitted, and they are explicitly forbidden
- # in the JEP
- # &nbsp; == u"\u00a0"
- self.pub.writer.translator_class.attribution_formats['dash'] = (
- '\u2014', '')
- self.pub.process_programmatic_settings(settings_spec,
- settings_overrides,
- config_section)
-
-
- def create_xhtml(self, text, destination=None, destination_path=None,
- enable_exit_status=None):
- """
- Create xhtml for a fragment of IM dialog. We can use the source_name
- to store info about the message
- """
- self.pub.set_source(text, None)
- self.pub.set_destination(destination, destination_path)
- output = self.pub.publish(enable_exit_status=enable_exit_status)
- # kludge until we can get docutils to stop generating (rare) &nbsp;
- # entities
- return '\u00a0'.join(self.pub.writer.parts['fragment'].strip().split(
- '&nbsp;'))
-
- Generator = HTMLGenerator()
-
- def create_xhtml(text):
- return Generator.create_xhtml(text)
-
-
-if __name__ == '__main__':
- print("test 1\n" + Generator.create_xhtml("""
-test::
-
->>> print 1
-1
-
-*I* like it. It is for :JEP:`71`
-
-this `` should trigger`` should trigger the &nbsp; problem.
-
-"""))
- print("test 2\n" + Generator.create_xhtml("""
-*test1
-
-test2_
-"""))
- print("test 3\n" + Generator.create_xhtml(""":ticket:`316` implements :xep:`71`"""))
diff --git a/src/common/sleepy.py b/src/common/sleepy.py
deleted file mode 100644
index ca1a34d3e..000000000
--- a/src/common/sleepy.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/sleepy.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Mateusz Biliński <mateusz AT bilinski.it>
-##
-## 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/>.
-##
-
-from common import gajim
-import os
-
-
-STATE_UNKNOWN = 'OS probably not supported'
-STATE_XA = 'extended away'
-STATE_AWAY = 'away'
-STATE_AWAKE = 'awake'
-
-SUPPORTED = True
-try:
- if os.name == 'nt':
- import ctypes
-
- GetTickCount = ctypes.windll.kernel32.GetTickCount
- GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo
-
- class LASTINPUTINFO(ctypes.Structure):
- _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)]
-
- lastInputInfo = LASTINPUTINFO()
- lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)
-
- # one or more of these may not be supported before XP.
- OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop
- CloseDesktop = ctypes.windll.user32.CloseDesktop
- SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW
- else: # unix
- from common import idle
- idle.xss_available
-except Exception:
- gajim.log.debug('Unable to load idle module')
- SUPPORTED = False
-
-class SleepyWindows:
- def __init__(self, away_interval = 60, xa_interval = 120):
- self.away_interval = away_interval
- self.xa_interval = xa_interval
- self.state = STATE_AWAKE # assume we are awake
-
- def getIdleSec(self):
- GetLastInputInfo(ctypes.byref(lastInputInfo))
- idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000
- return idleDelta
-
- def poll(self):
- """
- Check to see if we should change state
- """
- if not SUPPORTED:
- return False
-
- # screen saver, in windows >= XP
- saver_runing = ctypes.c_int(0)
- # 0x72 is SPI_GETSCREENSAVERRUNNING
- if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \
- saver_runing.value:
- self.state = STATE_XA
- return True
-
- desk = OpenInputDesktop(0, False, 0)
- if not desk:
- # Screen locked
- self.state = STATE_XA
- return True
- CloseDesktop(desk)
-
- idleTime = self.getIdleSec()
-
- # xa is stronger than away so check for xa first
- if idleTime > self.xa_interval:
- self.state = STATE_XA
- elif idleTime > self.away_interval:
- self.state = STATE_AWAY
- else:
- self.state = STATE_AWAKE
- return True
-
- def getState(self):
- return self.state
-
- def setState(self, val):
- self.state = val
-
-class SleepyUnix:
- def __init__(self, away_interval = 60, xa_interval = 120):
- global SUPPORTED
- self.away_interval = away_interval
- self.xa_interval = xa_interval
- self.state = STATE_AWAKE # assume we are awake
-
- def getIdleSec(self):
- return idle.getIdleSec()
-
- def poll(self):
- """
- Check to see if we should change state
- """
- if not SUPPORTED:
- return False
-
- idleTime = self.getIdleSec()
-
- # xa is stronger than away so check for xa first
- if idleTime > self.xa_interval:
- self.state = STATE_XA
- elif idleTime > self.away_interval:
- self.state = STATE_AWAY
- else:
- self.state = STATE_AWAKE
- return True
-
- def getState(self):
- return self.state
-
- def setState(self, val):
- self.state = val
-
-if os.name == 'nt':
- Sleepy = SleepyWindows
-else:
- Sleepy = SleepyUnix
diff --git a/src/common/socks5.py b/src/common/socks5.py
deleted file mode 100644
index 1273e6486..000000000
--- a/src/common/socks5.py
+++ /dev/null
@@ -1,1474 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/socks5.py
-##
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-import socket
-import struct
-import hashlib
-import os
-import time
-from errno import EWOULDBLOCK
-from errno import ENOBUFS
-from errno import EINTR
-from errno import EISCONN
-from errno import EINPROGRESS
-from errno import EAFNOSUPPORT
-from nbxmpp.idlequeue import IdleObject
-from common.file_props import FilesProp
-from common import gajim
-from common import jingle_xtls
-if jingle_xtls.PYOPENSSL_PRESENT:
- import OpenSSL
-import logging
-log = logging.getLogger('gajim.c.socks5')
-MAX_BUFF_LEN = 65536
-# after foo seconds without activity label transfer as 'stalled'
-STALLED_TIMEOUT = 10
-# after foo seconds of waiting to connect, disconnect from
-# streamhost and try next one
-CONNECT_TIMEOUT = 30
-# nothing received for the last foo seconds - stop transfer
-# if it is 0, then transfer will wait forever
-READ_TIMEOUT = 180
-# nothing sent for the last foo seconds - stop transfer
-# if it is 0, then transfer will wait forever
-SEND_TIMEOUT = 180
-
-
-class SocksQueue:
- """
- Queue for all file requests objects
- """
-
- def __init__(self, idlequeue, complete_transfer_cb=None,
- progress_transfer_cb=None, error_cb=None):
- self.connected = 0
- self.readers = {}
- self.senders = {}
- self.idx = 1
- self.listener = None
- self.sha_handlers = {}
- # handle all io events in the global idle queue, instead of processing
- # each foo seconds
- self.idlequeue = idlequeue
- self.complete_transfer_cb = complete_transfer_cb
- self.progress_transfer_cb = progress_transfer_cb
- self.error_cb = error_cb
- self.on_success = {} # {id: cb}
- self.on_failure = {} # {id: cb}
-
- def start_listener(self, port, sha_str, sha_handler, file_props,
- fingerprint=None, typ='sender'):
- """
- Start waiting for incomming connections on (host, port) and do a socks5
- authentication using sid for generated SHA
- """
- log.debug('Start listening for socks5 connection')
- sid = file_props.sid
- self.sha_handlers[sha_str] = (sha_handler, sid)
- if self.listener is None or self.listener.connections == []:
- self.listener = Socks5Listener(self.idlequeue, port, file_props,
- fingerprint=fingerprint)
- self.listener.queue = self
- self.listener.bind()
- else:
- # There is already a listener, we update the file's information
- # on the new connection.
- self.listener.file_props = file_props
- self.connected += 1
- return self.listener
-
- def send_success_reply(self, file_props, streamhost):
- if file_props.streamhost_used == True:
- for proxy in file_props.proxyhosts:
- if proxy['host'] == streamhost['host']:
- self.on_success[file_props.sid](proxy)
- return 1
- return 0
- for host in file_props.streamhosts:
- if streamhost['state'] == 1:
- return 0
- streamhost['state'] = 1
- self.on_success[file_props.sid](streamhost)
- return 1
-
- def connect_to_hosts(self, account, sid, on_success=None, on_failure=None,
- fingerprint=None, receiving=True):
- self.on_success[sid] = on_success
- self.on_failure[sid] = on_failure
- file_props = FilesProp.getFileProp(account, sid)
- file_props.failure_cb = on_failure
- streamhosts_to_test = []
- # Remove local IPs to not connect to ourself
- for streamhost in file_props.streamhosts:
- if streamhost['host'] == '127.0.0.1' or streamhost['host'] == '::1':
- continue
- streamhosts_to_test.append(streamhost)
- if not streamhosts_to_test:
- on_failure(file_props.sid)
- # add streamhosts to the queue
- for streamhost in streamhosts_to_test:
- if 'type' in streamhost and streamhost['type'] == 'proxy':
- fp = None
- else:
- fp = fingerprint
- if receiving:
- log.debug('Trying to connect as receiver to cid ' + streamhost['cid'])
- file_props.type_ = 'r'
- socks5obj = Socks5ReceiverClient(self.idlequeue, streamhost,
- sid, file_props, fingerprint=fp)
- self.add_sockobj(account, socks5obj)
- else:
- log.debug('Trying to connect as sender to cid' + streamhost['cid'])
- if file_props.sha_str:
- idx = file_props.sha_str
- else:
- idx = self.idx
- self.idx = self.idx + 1
- file_props.type_ = 's'
- if 'type' in streamhost and streamhost['type'] == 'proxy':
- file_props.is_a_proxy = True
- file_props.proxy_sender = streamhost['target']
- file_props.proxy_receiver = streamhost['initiator']
- socks5obj = Socks5SenderClient(self.idlequeue, idx,
- self, _sock=None,host=str(streamhost['host']),
- port=int(streamhost['port']),fingerprint=fp,
- connected=False, file_props=file_props,
- initiator=streamhost['initiator'],
- target=streamhost['target'])
- socks5obj.streamhost = streamhost
- self.add_sockobj(account, socks5obj)
-
- streamhost['idx'] = socks5obj.queue_idx
-
- def _socket_connected(self, streamhost, file_props):
- """
- Called when there is a host connected to one of the senders's
- streamhosts. Stop other attempts for connections
- """
- log.debug('Connected to cid ' + streamhost['cid'])
- for host in file_props.streamhosts:
- if host != streamhost and 'idx' in host:
- if host['state'] == 1:
- # remove current
- if file_props.type_ == 's':
- self.remove_sender(streamhost['idx'], False)
- else:
- self.remove_receiver(streamhost['idx'])
- return
- # set state -2, meaning that this streamhost is stopped,
- # but it may be connectected later
- if host['state'] >= 0:
- if file_props.type_ == 's':
- self.remove_sender(host['idx'], False)
- else:
- self.remove_receiver(host['idx'])
- host['idx'] = -1
- host['state'] = -2
-
- def reconnect_client(self, client, streamhost):
- """
- Check the state of all streamhosts and if all has failed, then emit
- connection failure cb. If there are some which are still not connected
- try to establish connection to one of them
- """
- self.idlequeue.remove_timeout(client.fd)
- self.idlequeue.unplug_idle(client.fd)
- file_props = client.file_props
- streamhost['state'] = -1
- # boolean, indicates that there are hosts, which are not tested yet
- unused_hosts = False
- for host in file_props.streamhosts:
- if 'idx' in host:
- if host['state'] >= 0:
- return
- elif host['state'] == -2:
- unused_hosts = True
- if unused_hosts:
- for host in file_props.streamhosts:
- if host['state'] == -2:
- host['state'] = 0
- # FIXME: make the sender reconnect also
- client = Socks5ReceiverClient(self.idlequeue, host,
- host['sid'], file_props)
- self.add_sockobj(client.account, client)
- host['idx'] = client.queue_idx
- # we still have chances to connect
- return
- if file_props.received_len == 0:
- # there are no other streamhosts and transfer hasn't started
- self._connection_refused(streamhost, file_props, client.queue_idx)
- else:
- # transfer stopped, it is most likely stopped from sender
- client.disconnect()
- file_props.error = -1
- self.process_result(-1, client)
-
- def _connection_refused(self, streamhost, file_props, idx):
- """
- Called when we loose connection during transfer
- """
- log.debug('Connection refused to cid ' + streamhost['cid'])
- if file_props is None:
- return
- streamhost['state'] = -1
- # FIXME: should only the receiver be remove? what if we are sending?
- self.remove_receiver(idx, False)
- for host in file_props.streamhosts:
- if host['state'] != -1:
- return
- self.readers = {}
- # failure_cb exists - this means that it has never been called
- if file_props.failure_cb:
- file_props.failure_cb(file_props.sid)
- file_props.failure_cb = None
-
- def add_sockobj(self, account, sockobj):
- """
- Add new file a sockobj type receiver or sender, and use it to connect
- to server
- """
- if sockobj.file_props.type_ == 'r':
- self._add(sockobj, self.readers, sockobj.file_props, self.idx)
- else:
- self._add(sockobj, self.senders, sockobj.file_props, self.idx)
- sockobj.queue_idx = self.idx
- sockobj.queue = self
- sockobj.account = account
- self.idx += 1
- result = sockobj.connect()
- self.connected += 1
- if result is not None:
- result = sockobj.main()
- self.process_result(result, sockobj)
- return 1
- return None
-
- def _add(self, sockobj, sockobjects, file_props, hash_):
- '''
- Adds the sockobj to the current list of sockobjects
- '''
- keys = (file_props.sid, file_props.name, hash_)
- sockobjects[keys] = sockobj
-
- def result_sha(self, sha_str, idx):
- if sha_str in self.sha_handlers:
- props = self.sha_handlers[sha_str]
- props[0](props[1], idx)
-
- def activate_proxy(self, idx):
- if not self.isHashInSockObjs(self.senders, idx):
- return
- for key in self.senders.keys():
- if idx in key:
- sender = self.senders[key]
- if sender.file_props.type_ != 's':
- return
- sender.state = 6
- if sender.connected:
- sender.file_props.error = 0
- sender.file_props.disconnect_cb = sender.disconnect
- sender.file_props.started = True
- sender.file_props.completed = False
- sender.file_props.paused = False
- sender.file_props.stalled = False
- sender.file_props.elapsed_time = 0
- sender.file_props.last_time = time.time()
- sender.file_props.received_len = 0
- sender.pauses = 0
- # start sending file to proxy
- self.idlequeue.set_read_timeout(sender.fd, STALLED_TIMEOUT)
- self.idlequeue.plug_idle(sender, True, False)
- result = sender.write_next()
- self.process_result(result, sender)
-
- def send_file(self, file_props, account, mode):
- for key in self.senders.keys():
- if self.senders == {}:
- # Python acts very weird with this. When there is no keys
- # in the dictionary It says that it has a key.
- # Maybe it is my machine. Without this there is a KeyError
- # traceback.
- return
- if file_props.name in key and file_props.sid in key \
- and self.senders[key].mode == mode:
- log.info('socks5: sending file')
- sender = self.senders[key]
- file_props.streamhost_used = True
- sender.account = account
- sender.file_props = file_props
- result = sender.send_file()
- self.process_result(result, sender)
-
- def isHashInSockObjs(self, sockobjs, hash):
- '''
- It tells wether there is a particular hash in sockobjs or not
- '''
- for key in sockobjs:
- if hash in key:
- return True
- return False
-
- def on_connection_accepted(self, sock, listener):
- sock_hash = sock.__hash__()
- if listener.file_props.type_ == 's' and \
- not self.isHashInSockObjs(self.senders, sock_hash):
- sockobj = Socks5SenderServer(self.idlequeue, sock_hash, self,
- sock[0], sock[1][0], sock[1][1], fingerprint=None,
- file_props=listener.file_props)
- self._add(sockobj, self.senders, listener.file_props, sock_hash)
- # Start waiting for data
- self.idlequeue.plug_idle(sockobj, False, True)
- self.connected += 1
- if listener.file_props.type_ == 'r' and \
- not self.isHashInSockObjs(self.readers, sock_hash):
- sh = {}
- sh['host'] = sock[1][0]
- sh['port'] = sock[1][1]
- sh['initiator'] = None
- sh['target'] = None
- sockobj = Socks5ReceiverServer(idlequeue=self.idlequeue,
- streamhost=sh,sid=None, file_props=listener.file_props,
- fingerprint=None)
-
- self._add(sockobj, self.readers, listener.file_props, sock_hash)
- sockobj.set_sock(sock[0])
- sockobj.queue = self
- self.connected += 1
-
- def process_result(self, result, actor):
- """
- Take appropriate actions upon the result:
- [ 0, - 1 ] complete/end transfer
- [ > 0 ] send progress message
- [ None ] do nothing
- """
- if result is None:
- return
- if result in (0, -1) and self.complete_transfer_cb is not None:
- account = actor.account
- if account is None and actor.file_props.tt_account:
- account = actor.file_props.tt_account
- self.complete_transfer_cb(account, actor.file_props)
- elif self.progress_transfer_cb is not None:
- self.progress_transfer_cb(actor.account, actor.file_props)
-
- def remove_receiver_by_key(self, key, do_disconnect=True):
- reader = self.readers[key]
- self.idlequeue.unplug_idle(reader.fd)
- self.idlequeue.remove_timeout(reader.fd)
- if do_disconnect:
- reader.disconnect()
- else:
- if reader.streamhost is not None:
- reader.streamhost['state'] = -1
- del(self.readers[key])
-
- def remove_sender_by_key(self, key, do_disconnect=True):
- sender = self.senders[key]
- if do_disconnect:
- sender.disconnect()
- else:
- self.idlequeue.unplug_idle(sender.fd)
- self.idlequeue.remove_timeout(sender.fd)
- del(self.senders[key])
- if self.connected > 0:
- self.connected -= 1
-
- def remove_receiver(self, idx, do_disconnect=True, remove_all=False):
- """
- Remove reciver from the list and decrease the number of active
- connections with 1
- """
- if idx != -1:
- for key in list(self.readers.keys()):
- if idx in key:
- self.remove_receiver_by_key(key,
- do_disconnect=do_disconnect)
- if not remove_all:
- break
-
- def remove_sender(self, idx, do_disconnect=True, remove_all=False):
- """
- Remove sender from the list of senders and decrease the number of active
- connections with 1
- """
- if idx != -1:
- for key in self.senders.keys():
- if idx in key:
- self.remove_sender_by_key(key, do_disconnect=do_disconnect)
- if not remove_all:
- break
- if len(self.senders) == 0 and self.listener is not None:
- self.listener.disconnect()
- self.listener = None
- self.connected -= 1
-
- def remove_by_mode(self, sid, mode, do_disconnect=True):
- for (key, sock) in self.senders.copy().items():
- if key[0] == sid and sock.mode == mode:
- self.remove_sender_by_key(key)
- for (key, sock) in self.readers.copy().items():
- if key[0] == sid and sock.mode == mode:
- self.remove_receiver_by_key(key)
-
- def remove_server(self, sid, do_disconnect=True):
- self.remove_by_mode(sid, 'server')
-
- def remove_client(self, sid, do_disconnect=True):
- self.remove_by_mode(sid, 'client')
-
-class Socks5(object):
- def __init__(self, idlequeue, host, port, initiator, target, sid):
- if host is not None:
- try:
- self.host = host
- self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM)
- except socket.gaierror:
- self.ais = None
- self.idlequeue = idlequeue
- self.fd = -1
- self.port = port
- self.initiator = initiator
- self.target = target
- self.sid = sid
- self._sock = None
- self.account = None
- self.state = 0 # not connected
- self.pauses = 0
- self.size = 0
- self.remaining_buff = b''
- self.file = None
- self.connected = False
- self.mode = ''
- self.ssl_cert = None
- self.ssl_errnum = 0
-
- def _is_connected(self):
- if self.state < 5:
- return False
- return True
-
- def ssl_verify_cb(self, ssl_conn, cert, error_num, depth, return_code):
- if depth == 0:
- self.ssl_cert = cert
- self.ssl_errnum = error_num
- return True
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.ais is None:
- return None
- for ai in self.ais:
- try:
- self._sock = socket.socket(*ai[:3])
- if self.fingerprint is not None:
- if self.file_props.type_ == 's':
- remote_jid = gajim.get_jid_without_resource(
- self.file_props.receiver)
- else:
- remote_jid = gajim.get_jid_without_resource(
- self.file_props.sender)
- self._sock = OpenSSL.SSL.Connection(
- jingle_xtls.get_context('client',
- verify_cb=self.ssl_verify_cb, remote_jid=remote_jid),
- self._sock)
- # this will not block the GUI
- self._sock.setblocking(False)
- self._server = ai[4]
- break
- except socket.error as e:
- if e.errno == EINPROGRESS:
- break
- # for all other errors, we try other addresses
- continue
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- self.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def do_connect(self):
- try:
- self._sock.connect(self._server)
- self._send=self._sock.send
- self._recv=self._sock.recv
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError) as e:
- pass
- except Exception as ee:
- errnum = ee.errno
- self.connect_timeout += 1
- if errnum == 111 or self.connect_timeout > 1000:
- self.queue._connection_refused(self.streamhost, self.file_props,
- self.queue_idx)
- self.connected = False
- return None
- # win32 needs this
- elif errnum not in (10056, EISCONN) or self.state != 0:
- return None
- else: # socket is already connected
- self._sock.setblocking(False)
- self._send=self._sock.send
- self._recv=self._sock.recv
- self.buff = ''
- self.connected = True
- self.file_props.connected = True
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.paused = False
- self.state = 1 # connected
- # stop all others connections to sender's streamhosts
- self.queue._socket_connected(self.streamhost, self.file_props)
- self.idlequeue.plug_idle(self, True, False)
- return 1 # we are connected
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state > 5:
- # no activity for foo seconds
- if self.file_props.stalled == False:
- self.file_props.stalled = True
- self.queue.process_result(-1, self)
- if not self.file_props.received_len:
- self.file_props.received_len = 0
- if SEND_TIMEOUT > 0:
- self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT)
- else:
- # stop transfer, there is no error code for this
- self.pollend()
- else:
- if self.mode == 'client':
- self.queue.reconnect_client(self, self.streamhost)
-
- def open_file_for_reading(self):
- if self.file is None:
- try:
- self.file = open(self.file_props.file_name, 'rb')
- if self.file_props.offset:
- self.size = self.file_props.offset
- self.file.seek(self.size)
- self.file_props.received_len = self.size
- except IOError as e:
- self.close_file()
- raise IOError(str(e))
-
- def close_file(self):
- # Close file we're sending from
- if self.file:
- if not self.file.closed:
- try:
- self.file.close()
- except Exception:
- pass
- self.file = None
- # Close file we're receiving into
- if self.file_props.fd:
- try:
- self.file_props.fd.close()
- except Exception:
- pass
-
- def get_fd(self):
- """
- Test if file is already open and return its fd, or just open the file
- and return the fd
- """
- if self.file_props.fd:
- fd = self.file_props.fd
- else:
- offset = 0
- opt = 'wb'
- if self.file_props.offset:
- offset = self.file_props.offset
- opt = 'ab'
- fd = open(self.file_props.file_name, opt)
- self.file_props.fd = fd
- self.file_props.elapsed_time = 0
- self.file_props.last_time = time.time()
- self.file_props.received_len = offset
- return fd
-
- def rem_fd(self, fd):
- if self.file_props.fd:
- self.file_props.fd = None
- try:
- fd.close()
- except Exception:
- pass
-
- def receive(self):
- """
- Read small chunks of data. Call owner's disconnected() method if
- appropriate
- """
- received = b''
- try:
- add = self._recv(64)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('SSL rehandshake request : ' + repr(e))
- raise e
- except Exception:
- add = b''
- received += add
- if len(add) == 0:
- self.disconnect()
- return add
-
- def send_raw(self, raw_data):
- """
- Write raw outgoing data
- """
- try:
- self._send(raw_data)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('SSL rehandshake request :' + repr(e))
- raise e
- except Exception:
- self.disconnect()
- return len(raw_data)
-
- def write_next(self):
- if self.remaining_buff != b'':
- buff = self.remaining_buff
- else:
- try:
- self.open_file_for_reading()
- except IOError:
- self.state = 8 # end connection
- self.disconnect()
- self.file_props.error = -7 # unable to read from file
- return -1
- buff = self.file.read(MAX_BUFF_LEN)
- if len(buff) > 0:
- lenn = 0
- try:
- lenn = self._send(buff)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('SSL rehandshake request :' + repr(e))
- raise e
- except Exception as e:
- if e.errno not in (EINTR, ENOBUFS, EWOULDBLOCK):
- # peer stopped reading
- self.state = 8 # end connection
- self.disconnect()
- self.file_props.error = -1
- return -1
- self.size += lenn
- current_time = time.time()
- self.file_props.elapsed_time += current_time - \
- self.file_props.last_time
- self.file_props.last_time = current_time
- self.file_props.received_len = self.size
- if self.size >= self.file_props.size:
- self.state = 8 # end connection
- self.file_props.error = 0
- self.disconnect()
- return -1
- if lenn != len(buff):
- self.remaining_buff = buff[lenn:]
- else:
- self.remaining_buff = b''
- self.state = 7 # continue to write in the socket
- if lenn == 0:
- return None
- self.file_props.stalled = False
- return lenn
- else:
- self.state = 8 # end connection
- self.disconnect()
- return -1
-
- def get_file_contents(self, timeout):
- """
- Read file contents from socket and write them to file
- """
- if self.file_props is None or not self.file_props.file_name:
- self.file_props.error = -2
- return None
- fd = None
- if self.remaining_buff != b'':
- try:
- fd = self.get_fd()
- except IOError:
- self.disconnect(False)
- self.file_props.error = -6 # file system error
- return 0
- fd.write(self.remaining_buff)
- lenn = len(self.remaining_buff)
- current_time = time.time()
- self.file_props.elapsed_time += current_time - \
- self.file_props.last_time
- self.file_props.last_time = current_time
- self.file_props.received_len += lenn
- self.remaining_buff = b''
- if self.file_props.received_len == self.file_props.size:
- self.rem_fd(fd)
- self.disconnect()
- self.file_props.error = 0
- self.file_props.completed = True
- return 0
- else:
- try:
- fd = self.get_fd()
- except IOError:
- self.disconnect(False)
- self.file_props.error = -6 # file system error
- return 0
- try:
- buff = self._recv(MAX_BUFF_LEN)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('SSL rehandshake request :' + repr(e))
- raise e
- except Exception:
- buff = b''
- current_time = time.time()
- self.file_props.elapsed_time += current_time - \
- self.file_props.last_time
- self.file_props.last_time = current_time
- self.file_props.received_len += len(buff)
- if len(buff) == 0:
- # Transfer stopped somehow:
- # reset, paused or network error
- self.rem_fd(fd)
- self.disconnect()
- self.file_props.error = -1
- return 0
- try:
- fd.write(buff)
- except IOError:
- self.rem_fd(fd)
- self.disconnect()
- self.file_props.error = -6 # file system error
- return 0
- if self.file_props.received_len >= self.file_props.size:
- # transfer completed
- self.rem_fd(fd)
- self.disconnect()
- self.file_props.error = 0
- self.file_props.completed = True
- return 0
- # return number of read bytes. It can be used in progressbar
- if fd is not None:
- self.file_props.stalled = False
- if fd is None and self.file_props.stalled is False:
- return None
- if self.file_props.received_len:
- if self.file_props.received_len != 0:
- return self.file_props.received_len
- return None
-
- def disconnect(self):
- """
- Close open descriptors and remover socket descr. from idleque
- """
- # be sure that we don't leave open file
- self.close_file()
- self.idlequeue.remove_timeout(self.fd)
- self.idlequeue.unplug_idle(self.fd)
- if self.mode == 'server' and self.queue.listener:
- try:
- self.queue.listener.connections.remove(self._sock)
- except ValueError:
- pass # Not in list
- if self.queue.listener.connections == []:
- self.queue.listener.disconnect()
- try:
- if isinstance(self._sock, OpenSSL.SSL.Connection):
- self._sock.shutdown()
- else:
- self._sock.shutdown(socket.SHUT_RDWR)
- self._sock.close()
- except Exception:
- # socket is already closed
- pass
- self.connected = False
- self.fd = -1
- self.state = -1
-
- def _get_auth_buff(self):
- """
- Message, that we support 1 one auth mechanism: the 'no auth' mechanism
- """
- return struct.pack('!BBB', 0x05, 0x01, 0x00)
-
- def _parse_auth_buff(self, buff):
- """
- Parse the initial message and create a list of auth mechanisms
- """
- auth_mechanisms = []
- try:
- num_auth = struct.unpack('!xB', buff[:2])[0]
- for i in range(num_auth):
- mechanism, = struct.unpack('!B', buff[1 + i])
- auth_mechanisms.append(mechanism)
- except Exception:
- return None
- return auth_mechanisms
-
- def _get_auth_response(self):
- """
- Socks version(5), number of extra auth methods (we send 0x00 - no auth)
- """
- return struct.pack('!BB', 0x05, 0x00)
-
- def _get_connect_buff(self):
- """
- Connect request by domain name
- """
- buff = struct.pack('!BBBBB%dsBB' % len(self.host),
- 0x05, 0x01, 0x00, 0x03, len(self.host), self.host.encode('utf-8'),
- self.port >> 8, self.port & 0xff)
- return buff
-
- def _get_request_buff(self, msg, command = 0x01):
- """
- Connect request by domain name, sid sha, instead of domain name (jep
- 0096)
- """
- if isinstance(msg, str):
- msg = msg.encode('utf-8')
- buff = struct.pack('!BBBBB%dsBB' % len(msg), 0x05, command, 0x00, 0x03,
- len(msg), msg, 0, 0)
- return buff
-
- def _parse_request_buff(self, buff):
- try: # don't trust on what comes from the outside
- req_type, host_type, = struct.unpack('!xBxB', buff[:4])
- if host_type == 0x01:
- host_arr = struct.unpack('!iiii', buff[4:8])
- host, = '.'.join(str(s) for s in host_arr)
- host_len = len(host)
- elif host_type == 0x03:
- host_len, = struct.unpack('!B', buff[4])
- host, = struct.unpack('!%ds' % host_len, buff[5:5 + host_len])
- portlen = len(buff[host_len + 5:])
- if portlen == 1:
- port, = struct.unpack('!B', buff[host_len + 5])
- elif portlen == 2:
- port, = struct.unpack('!H', buff[host_len + 5:])
- # file data, comes with auth message (Gaim bug)
- else:
- port, = struct.unpack('!H', buff[host_len + 5: host_len + 7])
- self.remaining_buff = buff[host_len + 7:]
- except Exception:
- return (None, None, None)
- return (req_type, host, port)
-
- def read_connect(self):
- """
- Connect response: version, auth method
- """
- try:
- buff = self._recv().decode('utf-8')
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info("SSL rehandshake request : " + repr(e))
- raise e
- try:
- version, method = struct.unpack('!BB', buff)
- except Exception:
- version, method = None, None
- if version != 0x05 or method == 0xff:
- self.disconnect()
-
- def continue_paused_transfer(self):
- if self.state < 5:
- return
- if self.file_props.type_ == 'r':
- self.idlequeue.plug_idle(self, False, True)
- else:
- self.idlequeue.plug_idle(self, True, False)
-
- def _get_sha1_auth(self):
- """
- Get sha of sid + Initiator jid + Target jid
- """
- if self.file_props.is_a_proxy:
- return hashlib.sha1(('%s%s%s' % (self.sid,
- self.file_props.proxy_sender, self.file_props.proxy_receiver)).\
- encode('utf-8')).hexdigest()
- return hashlib.sha1(('%s%s%s' % (self.sid, self.initiator,
- self.target)).encode('utf-8')).hexdigest()
-
-
-class Socks5Sender(IdleObject):
- """
- Class for sending file to socket over socks5
- """
- def __init__(self, idlequeue, sock_hash, parent, _sock, host=None,
- port=None, fingerprint = None, connected=True, file_props=None):
- self.fingerprint = fingerprint
- self.queue_idx = sock_hash
- self.queue = parent
- self.file_props = file_props
- self.proxy = False
- self._sock = _sock
- if _sock is not None:
- if self.fingerprint is not None and not isinstance(self._sock,
- OpenSSL.SSL.Connection):
- self._sock = OpenSSL.SSL.Connection(
- jingle_xtls.get_context('server'), _sock)
- else:
- self._sock.setblocking(False)
- self.fd = _sock.fileno()
- self._recv = _sock.recv
- self._send = _sock.send
- self.connected = connected
- self.state = 1 # waiting for first bytes
- self.connect_timeout = 0
-
- self.file_props.error = 0
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.started = True
- self.file_props.completed = False
- self.file_props.paused = False
- self.file_props.continue_cb = self.continue_paused_transfer
- self.file_props.stalled = False
- self.file_props.connected = True
- self.file_props.elapsed_time = 0
- self.file_props.last_time = time.time()
- self.file_props.received_len = 0
-
- def start_transfer(self):
- """
- Send the file
- """
- return self.write_next()
-
- def set_connection_sock(self, _sock):
- self._sock = _sock
- if self.fingerprint is not None:
- self._sock = OpenSSL.SSL.Connection(
- jingle_xtls.get_context('client'), _sock)
- else:
- self._sock.setblocking(False)
- self.fd = _sock.fileno()
- self._recv = _sock.recv
- self._send = _sock.send
- self.connected = True
- self.state = 1 # waiting for first bytes
- self.file_props = None
- # start waiting for data
- self.idlequeue.plug_idle(self, False, True)
-
- def send_file(self):
- """
- Start sending the file over verified connection
- """
- self.pauses = 0
- self.state = 7
- # plug for writing
- self.idlequeue.plug_idle(self, True, False)
- return self.write_next() # initial for nl byte
-
- def disconnect(self, cb=True):
- """
- Close the socket
- """
- # close connection and remove us from the queue
- Socks5.disconnect(self)
- if self.file_props is not None:
- self.file_props.connected = False
- self.file_props.disconnect_cb = None
- if self.queue is not None:
- self.queue.remove_sender(self.queue_idx, False)
-
-
-class Socks5Receiver(IdleObject):
- def __init__(self, idlequeue, streamhost, sid, file_props = None,
- fingerprint=None):
- """
- fingerprint: fingerprint of certificates we shall use, set to None if
- TLS connection not desired
- """
- self.queue_idx = -1
- self.streamhost = streamhost
- self.queue = None
- self.fingerprint = fingerprint
- self.connect_timeout = 0
- self.connected = False
- self.pauses = 0
- self.file_props = file_props
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.error = 0
- self.file_props.started = True
- self.file_props.completed = False
- self.file_props.paused = False
- self.file_props.continue_cb = self.continue_paused_transfer
- self.file_props.stalled = False
- self.file_props.received_len = 0
-
- def receive_file(self):
- """
- Start receiving the file over verified connection
- """
- if self.file_props.started:
- return
- self.file_props.error = 0
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.started = True
- self.file_props.completed = False
- self.file_props.paused = False
- self.file_props.continue_cb = self.continue_paused_transfer
- self.file_props.stalled = False
- self.file_props.connected = True
- self.file_props.elapsed_time = 0
- self.file_props.last_time = time.time()
- self.file_props.received_len = 0
- self.pauses = 0
- self.state = 7
- # plug for reading
- self.idlequeue.plug_idle(self, False, True)
- return self.get_file_contents(0) # initial for nl byte
-
- def start_transfer(self):
- """
- Receive the file
- """
- return self.get_file_contents(0)
-
- def set_sock(self, _sock):
- self._sock = _sock
- self._sock.setblocking(False)
- self.fd = _sock.fileno()
- self._recv = _sock.recv
- self._send = _sock.send
- self.connected = True
- self.state = 1 # waiting for first bytes
- # start waiting for data
- self.idlequeue.plug_idle(self, False, True)
-
- def disconnect(self, cb=True):
- """
- Close the socket. Remove self from queue if cb is True
- """
- # close connection
- Socks5.disconnect(self)
- if cb is True:
- self.file_props.disconnect_cb = None
- if self.queue is not None:
- self.queue.remove_receiver(self.queue_idx, False)
-
-class Socks5Server(Socks5):
-
- def __init__(self, idlequeue, host, port, initiator, target, sid):
- Socks5.__init__(self, idlequeue, host, port, initiator, target, sid)
- self.mode = 'server'
-
- def main(self):
- """
- Initial requests for verifying the connection
- """
- if self.state == 1: # initial read
- buff = self.receive()
- if not self.connected:
- return -1
- mechs = self._parse_auth_buff(buff)
- if mechs is None:
- return -1 # invalid auth methods received
- elif self.state == 3: # get next request
- buff = self.receive()
- req_type, self.sha_msg = self._parse_request_buff(buff)[:2]
- if req_type != 0x01:
- return -1 # request is not of type 'connect'
- self.state += 1 # go to the next step
- # unplug & plug for writing
- self.idlequeue.plug_idle(self, True, False)
- return None
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.connected:
- try:
- if self.state < 5:
- result = self.main()
- if self.state == 4:
- self.queue.result_sha(self.sha_msg, self.queue_idx)
- if result == -1:
- self.disconnect()
- elif self.state == 5:
- self.state = 7
- if self.file_props.type_ == 's':
- # We wait for the end of the negotiation to
- # send the file
- self.idlequeue.plug_idle(self, False, False)
- else:
- # We plug for reading
- self.idlequeue.plug_idle(self, False, True)
- return
- elif self.state == 7:
- if self.file_props.paused:
- self.file_props.continue_cb = \
- self.continue_paused_transfer
- self.idlequeue.plug_idle(self, False, False)
- return
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- result = self.start_transfer() # send
- self.queue.process_result(result, self)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('caught SSL exception, ignored')
- else:
- self.disconnect()
-
- def pollend(self):
- self.state = 8 # end connection
- self.disconnect()
- self.file_props.error = -1
- self.queue.process_result(-1, self)
-
- def pollout(self):
- if not self.connected:
- self.disconnect()
- return
- self.idlequeue.remove_timeout(self.fd)
- try:
- if self.state == 2: # send reply with desired auth type
- self.send_raw(self._get_auth_response())
- elif self.state == 4: # send positive response to the 'connect'
- self.send_raw(self._get_request_buff(self.sha_msg, 0x00))
- elif self.state == 7:
- if self.file_props.paused:
- self.file_props.continue_cb = self.continue_paused_transfer
- self.idlequeue.plug_idle(self, False, False)
- return
- result = self.start_transfer() # send
- self.queue.process_result(result, self)
- if result is None or result <= 0:
- self.disconnect()
- return
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- elif self.state == 8:
- self.disconnect()
- return
- else:
- self.disconnect()
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('caught SSL exception, ignored')
- return
- if self.state < 5:
- self.state += 1
- # unplug and plug this time for reading
- self.idlequeue.plug_idle(self, False, True)
-
-
-class Socks5Client(Socks5):
-
- def __init__(self, idlequeue, host, port, initiator, target, sid):
- Socks5.__init__(self, idlequeue, host, port, initiator, target, sid)
- self.mode = 'client'
-
- def main(self, timeout=0):
- """
- Begin negotiation. on success 'address' != 0
- """
- result = 1
- buff = self.receive()
- if buff == '':
- # end connection
- self.pollend()
- return
- if self.state == 2: # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.disconnect()
- elif self.state == 4: # get approve of our request
- if buff is None:
- return None
- sub_buff = buff[:4]
- if len(sub_buff) < 4:
- return None
- version, address_type = struct.unpack('!BxxB', buff[:4])
- addrlen = 0
- if address_type == 0x03:
- addrlen = buff[4]
- address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5])
- portlen = len(buff[addrlen + 5:])
- if portlen == 1:
- port, = struct.unpack('!B', buff[addrlen + 5])
- elif portlen == 2:
- port, = struct.unpack('!H', buff[addrlen + 5:])
- else: # Gaim bug :)
- port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7])
- self.remaining_buff = buff[addrlen + 7:]
- self.state = 5 # for senders: init file_props and send '\n'
- if self.queue.on_success:
- result = self.queue.send_success_reply(self.file_props,
- self.streamhost)
- if self.file_props.type_ == 's' and self.proxy:
- self.queue.process_result(self.send_file(), self)
- return
- if result == 0:
- self.state = 8
- self.disconnect()
- # for senders: init file_props
- if result == 1 and self.state == 5:
- if self.file_props.type_ == 's':
- self.file_props.error = 0
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.started = True
- self.file_props.completed = False
- self.file_props.paused = False
- self.file_props.stalled = False
- self.file_props.elapsed_time = 0
- self.file_props.last_time = time.time()
- self.file_props.received_len = 0
- self.pauses = 0
- # start sending file contents to socket
- #self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- #self.idlequeue.plug_idle(self, True, False)
- self.idlequeue.plug_idle(self, False, False)
- else:
- # receiving file contents from socket
- self.idlequeue.plug_idle(self, False, True)
- self.file_props.continue_cb = self.continue_paused_transfer
- # we have set up the connection, next - retrieve file
- self.state = 6
- if self.state < 5:
- self.idlequeue.plug_idle(self, True, False)
- self.state += 1
- return None
-
- def send_file(self):
- if self.ssl_errnum > 0:
- log.error('remote certificate does not match the announced one.' + \
- '\nSSL Error: %d\nCancelling file transfer' % self.ssl_errnum)
- self.file_props.error = -12
- return -1
- return super(Socks5Client, self).send_file()
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.connected:
- try:
- if self.file_props.paused:
- self.idlequeue.plug_idle(self, False, False)
- return
- if self.state < 5:
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- result = self.main(0)
- self.queue.process_result(result, self)
- elif self.state == 5: # wait for proxy reply
- pass
- elif self.file_props.type_ == 'r':
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- result = self.start_transfer() # receive
- self.queue.process_result(result, self)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('caught SSL exception, ignored')
- return
- else:
- self.disconnect()
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- try:
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- self.send_raw(self._get_auth_buff())
- elif self.state == 3: # send 'connect' request
- self.send_raw(self._get_request_buff(self._get_sha1_auth()))
- elif self.file_props.type_ != 'r':
- if self.file_props.paused:
- self.idlequeue.plug_idle(self, False, False)
- return
- result = self.start_transfer() # send
- self.queue.process_result(result, self)
- return
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('caught SSL exception, ignored')
- return
- self.state += 1
- # unplug and plug for reading
- self.idlequeue.plug_idle(self, False, True)
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollend(self):
- if self.state >= 5:
- # error during transfer
- self.disconnect()
- self.file_props.error = -1
- self.queue.process_result(-1, self)
- else:
- self.queue.reconnect_client(self, self.streamhost)
-
-
-class Socks5SenderClient(Socks5Client, Socks5Sender):
-
- def __init__(self, idlequeue, sock_hash, parent,_sock, host=None,
- port=None, fingerprint = None, connected=True, file_props=None,
- initiator=None, target=None):
- Socks5Client.__init__(self, idlequeue, host, port, initiator, target,
- file_props.sid)
- Socks5Sender.__init__(self,idlequeue, sock_hash, parent,_sock,
- host, port, fingerprint , connected, file_props)
-
-
-class Socks5SenderServer(Socks5Server, Socks5Sender):
-
- def __init__(self, idlequeue, sock_hash, parent,_sock, host=None,
- port=None, fingerprint = None, connected=True, file_props=None):
- Socks5Server.__init__(self, idlequeue, host, port, None, None,
- file_props.sid)
- Socks5Sender.__init__(self,idlequeue, sock_hash, parent, _sock,
- host, port, fingerprint , connected, file_props)
-
-
-class Socks5ReceiverClient(Socks5Client, Socks5Receiver):
- def __init__(self, idlequeue, streamhost, sid, file_props=None,
- fingerprint=None):
- Socks5Client.__init__(self, idlequeue, streamhost['host'],
- int(streamhost['port']), streamhost['initiator'],
- streamhost['target'], sid)
- Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props,
- fingerprint)
-
-
-class Socks5ReceiverServer(Socks5Server, Socks5Receiver):
-
- def __init__(self, idlequeue, streamhost, sid, file_props=None,
- fingerprint=None):
- Socks5Server.__init__(self, idlequeue, streamhost['host'],
- int(streamhost['port']), streamhost['initiator'],
- streamhost['target'], sid)
- Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props,
- fingerprint)
-
-
-class Socks5Listener(IdleObject):
- def __init__(self, idlequeue, port, fp, fingerprint=None):
- """
- Handle all incomming connections on (0.0.0.0, port)
-
- This class implements IdleObject, but we will expect
- only pollin events though
-
- fingerprint: fingerprint of certificates we shall use, set to None if
- TLS connection not desired
- """
- self.port = port
- self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_PASSIVE)
- self.ais.sort(reverse=True) # Try IPv6 first
- self.queue_idx = -1
- self.idlequeue = idlequeue
- self.queue = None
- self.started = False
- self._sock = None
- self.fd = -1
- self.fingerprint = fingerprint
- self.file_props = fp
- self.connections = []
-
- def bind(self):
- for ai in self.ais:
- # try the different possibilities (ipv6, ipv4, etc.)
- try:
- self._serv = socket.socket(*ai[:3])
- if self.fingerprint is not None:
- self._serv = OpenSSL.SSL.Connection(
- jingle_xtls.get_context('server'), self._serv)
- except socket.error as e:
- if e.errno == EAFNOSUPPORT:
- self.ai = None
- continue
- raise
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
- self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- # Under windows Vista, we need that to listen on ipv6 AND ipv4
- # Doesn't work under windows XP
- if os.name == 'nt':
- ver = os.sys.getwindowsversion()
- if (ver[3], ver[0]) == (2, 6): # Win Vista +
- # 47 is socket.IPPROTO_IPV6
- # 27 is socket.IPV6_V6ONLY under windows, but not defined ...
- self._serv.setsockopt(41, 27, 0)
- # will fail when port as busy, or we don't have rights to bind
- try:
- self._serv.bind(ai[4])
- f = ai[4]
- self.ai = ai
- break
- except Exception:
- self.ai = None
- continue
- if not self.ai:
- log.error('unable to bind to port ' + str(self.port))
- return None
- self._serv.listen(socket.SOMAXCONN)
- self._serv.setblocking(False)
- self.fd = self._serv.fileno()
- self.idlequeue.plug_idle(self, False, True)
- self.started = True
-
- def pollend(self):
- """
- Called when we stop listening on (host, port)
- """
- self.disconnect()
-
- def pollin(self):
- """
- Accept a new incomming connection and notify queue
- """
- sock = self.accept_conn()
- self.queue.on_connection_accepted(sock, self)
-
- def disconnect(self):
- """
- Free all resources, we are not listening anymore
- """
- self.idlequeue.remove_timeout(self.fd)
- self.idlequeue.unplug_idle(self.fd)
- self.fd = -1
- self.state = -1
- self.started = False
- try:
- self._serv.close()
- except Exception:
- pass
-
- def accept_conn(self):
- """
- Accept a new incomming connection
- """
- _sock = self._serv.accept()
- _sock[0].setblocking(False)
- self.connections.append(_sock[0])
- return _sock
-
diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py
deleted file mode 100644
index 523d8f64c..000000000
--- a/src/common/stanza_session.py
+++ /dev/null
@@ -1,1213 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/stanza_session.py
-##
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## 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/>.
-##
-
-from common import gajim
-import nbxmpp
-from common.exceptions import DecryptionError, NegotiationError
-import nbxmpp.c14n
-
-import itertools
-import random
-import string
-import time
-import base64
-import os
-from hashlib import sha256
-from hmac import HMAC
-from common import crypto
-
-import logging
-log = logging.getLogger('gajim.c.stanza_session')
-
-if gajim.HAVE_PYCRYPTO:
- from Crypto.Cipher import AES
- from Crypto.PublicKey import RSA
-
- from common import dh
- import secrets
-
-XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
-
-class StanzaSession(object):
- '''
- '''
- def __init__(self, conn, jid, thread_id, type_):
- '''
- '''
- self.conn = conn
- self.jid = jid
- self.type_ = type_
- self.resource = jid.getResource()
-
- if thread_id:
- self.received_thread_id = True
- self.thread_id = thread_id
- else:
- self.received_thread_id = False
- if type_ == 'normal':
- self.thread_id = None
- else:
- self.thread_id = self.generate_thread_id()
-
- self.loggable = True
-
- self.last_send = 0
- self.last_receive = 0
- self.status = None
- self.negotiated = {}
-
- def is_loggable(self):
- return self.loggable and gajim.config.should_log(self.conn.name,
- self.jid.getStripped())
-
- def get_to(self):
- to = str(self.jid)
- return gajim.get_jid_without_resource(to) + '/' + self.resource
-
- def remove_events(self, types):
- """
- Remove events associated with this session from the queue
-
- Returns True if any events were removed (unlike events.py remove_events)
- """
- any_removed = False
-
- for j in (self.jid, self.jid.getStripped()):
- for event in gajim.events.get_events(self.conn.name, j, types=types):
- # the event wasn't in this session
- if (event.type_ == 'chat' and event.session != self) or \
- (event.type_ == 'printed_chat' and event.control.session != \
- self):
- continue
-
- # events.remove_events returns True when there were no events
- # for some reason
- r = gajim.events.remove_events(self.conn.name, j, event)
-
- if not r:
- any_removed = True
-
- return any_removed
-
- def generate_thread_id(self):
- return ''.join([f(string.ascii_letters) for f in itertools.repeat(
- random.choice, 32)])
-
- def send(self, msg):
- if self.thread_id:
- msg.NT.thread = self.thread_id
-
- msg.setAttr('to', self.get_to())
- self.conn.send_stanza(msg)
-
- if isinstance(msg, nbxmpp.Message):
- self.last_send = time.time()
-
- def reject_negotiation(self, body=None):
- msg = nbxmpp.Message()
- feature = msg.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='0'))
-
- feature.addChild(node=x)
-
- if body:
- msg.setBody(body)
-
- self.send(msg)
-
- self.cancelled_negotiation()
-
- def cancelled_negotiation(self):
- """
- A negotiation has been cancelled, so reset this session to its default
- state
- """
- if self.control:
- self.control.on_cancel_session_negotiation()
-
- self.status = None
- self.negotiated = {}
-
- def terminate(self, send_termination = True):
- # only send termination message if we've sent a message and think they
- # have XEP-0201 support
- if send_termination and self.last_send > 0 and \
- (self.received_thread_id or self.last_receive == 0):
- msg = nbxmpp.Message()
- feature = msg.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='terminate', value='1'))
-
- feature.addChild(node=x)
-
- self.send(msg)
-
- self.status = None
-
- def acknowledge_termination(self):
- # we could send an acknowledgement message to the remote client here
- self.status = None
-
-
-class ArchivingStanzaSession(StanzaSession):
- def __init__(self, conn, jid, thread_id, type_='chat'):
- StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
- self.archiving = False
-
- def archiving_logging_preference(self, initiator_options=None):
- return self.conn.logging_preference(self.jid, initiator_options)
-
- def negotiate_archiving(self):
- self.negotiated = {}
-
- request = nbxmpp.Message()
- feature = request.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='form')
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='1',
- typ='boolean', required=True))
-
- x.addChild(node=nbxmpp.DataField(name='logging', typ='list-single',
- options=self.archiving_logging_preference(), required=True))
-
- x.addChild(node=nbxmpp.DataField(name='disclosure', typ='list-single',
- options=['never'], required=True))
- x.addChild(node=nbxmpp.DataField(name='security', typ='list-single',
- options=['none'], required=True))
-
- feature.addChild(node=x)
-
- self.status = 'requested-archiving'
-
- self.send(request)
-
- def respond_archiving(self, form):
- field = form.getField('logging')
- options = [x[1] for x in field.getOptions()]
- values = field.getValues()
-
- logging = self.archiving_logging_preference(options)
- self.negotiated['logging'] = logging
-
- response = nbxmpp.Message()
- feature = response.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='true'))
-
- x.addChild(node=nbxmpp.DataField(name='logging', value=logging))
-
- self.status = 'responded-archiving'
-
- feature.addChild(node=x)
-
- if not logging:
- response = nbxmpp.Error(response, nbxmpp.ERR_NOT_ACCEPTABLE)
-
- feature = nbxmpp.Node(nbxmpp.NS_FEATURE + ' feature')
-
- n = nbxmpp.Node('field')
- n['var'] = 'logging'
- feature.addChild(node=n)
-
- response.T.error.addChild(node=feature)
-
- self.send(response)
-
- def we_accept_archiving(self, form):
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
- log.debug('archiving session accepted: %s' % self.loggable)
- self.status = 'active'
- self.archiving = True
- if self.control:
- self.control.print_archiving_session_details()
-
- def archiving_accepted(self, form):
- negotiated = {}
- ask_user = {}
- not_acceptable = []
-
- if form['logging'] not in self.archiving_logging_preference():
- raise
-
- self.negotiated['logging'] = form['logging']
-
- accept = nbxmpp.Message()
- feature = accept.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- result = nbxmpp.DataForm(typ='result')
-
- result.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- result.addChild(node=nbxmpp.DataField(name='accept', value='1'))
-
- feature.addChild(node=result)
-
- self.send(accept)
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
- log.debug('archiving session accepted: %s' % self.loggable)
- self.status = 'active'
- self.archiving = True
- if self.control:
- self.control.print_archiving_session_details()
-
- def stop_archiving_for_session(self):
- self.conn.stop_archiving_session(self.thread_id)
-
-
-class EncryptedStanzaSession(ArchivingStanzaSession):
- """
- An encrypted stanza negotiation has several states. They arerepresented as
- the following values in the 'status' attribute of the session object:
-
- 1. None:
- default state
- 2. 'requested-e2e':
- this client has initiated an esession negotiation and is waiting
- for a response
- 3. 'responded-e2e':
- this client has responded to an esession negotiation request and
- is waiting for the initiator to identify itself and complete the
- negotiation
- 4. 'identified-alice':
- this client identified itself and is waiting for the responder to
- identify itself and complete the negotiation
- 5. 'active':
- an encrypted session has been successfully negotiated. messages
- of any of the types listed in 'encryptable_stanzas' should be
- encrypted before they're sent.
-
- The transition between these states is handled in gajim.py's
- handle_session_negotiation method.
- """
-
- def __init__(self, conn, jid, thread_id, type_='chat'):
- ArchivingStanzaSession.__init__(self, conn, jid, thread_id,
- type_='chat')
-
- self.xes = {}
- self.es = {}
- self.n = 128
- self.enable_encryption = False
-
- # _s denotes 'self' (ie. this client)
- self._kc_s = None
- # _o denotes 'other' (ie. the client at the other end of the session)
- self._kc_o = None
-
- # has the remote contact's identity ever been verified?
- self.verified_identity = False
-
- def _get_contact(self):
- c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource)
- if not c:
- c = gajim.contacts.get_contact(self.conn.name, self.jid)
- return c
-
- def _is_buggy_gajim(self):
- c = self._get_contact()
- if c and c.supports(nbxmpp.NS_ROSTERX):
- return False
- return True
-
- def set_kc_s(self, value):
- """
- Keep the encrypter updated with my latest cipher key
- """
- self._kc_s = value
- self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR,
- counter=self.encryptcounter)
-
- def get_kc_s(self):
- return self._kc_s
-
- def set_kc_o(self, value):
- """
- Keep the decrypter updated with the other party's latest cipher key
- """
- self._kc_o = value
- self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR,
- counter=self.decryptcounter)
-
- def get_kc_o(self):
- return self._kc_o
-
- kc_s = property(get_kc_s, set_kc_s)
- kc_o = property(get_kc_o, set_kc_o)
-
- def encryptcounter(self):
- self.c_s = (self.c_s + 1) % (2 ** self.n)
- return crypto.encode_mpi_with_padding(self.c_s)
-
- def decryptcounter(self):
- self.c_o = (self.c_o + 1) % (2 ** self.n)
- return crypto.encode_mpi_with_padding(self.c_o)
-
- def sign(self, string):
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- hash_ = crypto.sha256(string)
- return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0])
-
- def encrypt_stanza(self, stanza):
- encryptable = [x for x in stanza.getChildren() if x.getName() not in
- ('error', 'amp', 'thread')]
-
- # FIXME can also encrypt contents of <error/> elements in stanzas @type =
- # 'error'
- # (except for <defined-condition
- # xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
-
- old_en_counter = self.c_s
-
- for element in encryptable:
- stanza.delChild(element)
-
- plaintext = ''.join(map(str, encryptable))
-
- m_compressed = self.compress(plaintext)
- m_final = self.encrypt(m_compressed)
-
- c = stanza.NT.c
- c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
- c.NT.data = base64.b64encode(m_final).decode('utf-8')
-
- # FIXME check for rekey request, handle <key/> elements
-
- m_content = (''.join(map(str, c.getChildren()))).encode('utf-8')
- c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
- crypto.encode_mpi(old_en_counter))).decode('utf-8')
-
- msgtxt = '[This is part of an encrypted session. ' \
- 'If you see this message, something went wrong.]'
- lang = os.getenv('LANG')
- if lang is not None and lang != 'en': # we're not english
- msgtxt = _('[This is part of an encrypted session. '
- 'If you see this message, something went wrong.]') + ' (' + \
- msgtxt + ')'
- stanza.setBody(msgtxt)
-
- return stanza
-
- def is_xep_200_encrypted(self, msg):
- msg.getTag('c', namespace=nbxmpp.NS_STANZA_CRYPTO)
-
- def hmac(self, key, content):
- return HMAC(key, content, self.hash_alg).digest()
-
- def generate_initiator_keys(self, k):
- return (self.hmac(k, b'Initiator Cipher Key'),
- self.hmac(k, b'Initiator MAC Key'),
- self.hmac(k, b'Initiator SIGMA Key'))
-
- def generate_responder_keys(self, k):
- return (self.hmac(k, b'Responder Cipher Key'),
- self.hmac(k, b'Responder MAC Key'),
- self.hmac(k, b'Responder SIGMA Key'))
-
- def compress(self, plaintext):
- if self.compression is None:
- return plaintext
-
- def decompress(self, compressed):
- if self.compression is None:
- return compressed
-
- def encrypt(self, encryptable):
- padded = crypto.pad_to_multiple(encryptable, 16, ' ', False)
-
- return self.encrypter.encrypt(padded)
-
- def decrypt_stanza(self, stanza):
- """
- Delete the unencrypted explanation body, if it exists
- """
- orig_body = stanza.getTag('body')
- if orig_body:
- stanza.delChild(orig_body)
-
- c = stanza.getTag(name='c',
- namespace='http://www.xmpp.org/extensions/xep-0200.html#ns')
-
- stanza.delChild(c)
-
- # contents of <c>, minus <mac>, minus whitespace
- macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac')
- macable = macable.encode('utf-8')
-
- received_mac = base64.b64decode(c.getTagData('mac'))
- calculated_mac = self.hmac(self.km_o, macable + \
- crypto.encode_mpi_with_padding(self.c_o))
-
- if not calculated_mac == received_mac:
- raise DecryptionError('bad signature')
-
- m_final = base64.b64decode(c.getTagData('data'))
- m_compressed = self.decrypt(m_final)
- plaintext = self.decompress(m_compressed).decode('utf-8')
-
- try:
- parsed = nbxmpp.Node(node='<node>' + plaintext + '</node>')
- except Exception:
- raise DecryptionError('decrypted <data/> not parseable as XML')
-
- for child in parsed.getChildren():
- stanza.addChild(node=child)
-
- # replace non-character unicode
- body = stanza.getBody()
- if body:
- stanza.setBody(
- self.conn.connection.Dispatcher.replace_non_character(body))
-
- return stanza
-
- def decrypt(self, ciphertext):
- return self.decrypter.decrypt(ciphertext)
-
- def logging_preference(self):
- if gajim.config.get_per('accounts', self.conn.name,
- 'log_encrypted_sessions'):
- return ['may', 'mustnot']
- else:
- return ['mustnot', 'may']
-
- def get_shared_secret(self, e, y, p):
- if (not 1 < e < (p - 1)):
- raise NegotiationError('invalid DH value')
-
- return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p)))
-
- def c7lize_mac_id(self, form):
- kids = form.getChildren()
- macable = [x for x in kids if x.getVar() not in ('mac', 'identity')]
- return ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \
- macable)
-
- def verify_identity(self, form, dh_i, sigmai, i_o):
- m_o = base64.b64decode(form['mac'])
- id_o = base64.b64decode(form['identity'])
-
- m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o)
-
- if m_o_calculated != m_o:
- raise NegotiationError('calculated m_%s differs from received m_%s' %
- (i_o, i_o))
-
- if i_o == 'a' and self.sas_algs == 'sas28x5':
- # we don't need to calculate this if there's a verified retained secret
- # (but we do anyways)
- self.sas = crypto.sas_28x5(m_o, self.form_s.encode('utf-8'))
-
- if self.negotiated['recv_pubkey']:
- plaintext = self.decrypt(id_o)
- parsed = nbxmpp.Node(node='<node>' + plaintext + '</node>')
-
- if self.negotiated['recv_pubkey'] == 'hash':
- # fingerprint = parsed.getTagData('fingerprint')
- # FIXME find stored pubkey or terminate session
- raise NotImplementedError()
- else:
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
-
- n, e = (crypto.decode_mpi(base64.b64decode(
- keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent'))
- eir_pubkey = RSA.construct((n, int(e)))
-
- pubkey_o = nbxmpp.c14n.c14n(keyvalue, self._is_buggy_gajim())
- else:
- # FIXME DSA, etc.
- raise NotImplementedError()
-
- enc_sig = parsed.getTag(name='SignatureValue',
- namespace=XmlDsig).getData()
- signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), )
- else:
- mac_o = self.decrypt(id_o)
- pubkey_o = b''
-
- c7l_form = self.c7lize_mac_id(form)
-
- content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o
-
- if sigmai:
- self.form_o = c7l_form.encode('utf-8')
- content += self.form_o
- else:
- form_o2 = c7l_form.encode('utf-8')
- content += self.form_o.encode('utf-8') + form_o2
-
- mac_o_calculated = self.hmac(self.ks_o, content)
-
- if self.negotiated['recv_pubkey']:
- hash_ = crypto.sha256(mac_o_calculated)
-
- if not eir_pubkey.verify(hash_, signature):
- raise NegotiationError('public key signature verification failed!')
-
- elif mac_o_calculated != mac_o:
- raise NegotiationError('calculated mac_%s differs from received mac_%s'
- % (i_o, i_o))
-
- def make_identity(self, form, dh_i):
- if self.negotiated['send_pubkey']:
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- pubkey = secrets.secrets().my_pubkey(self.conn.name)
- fields = (pubkey.n, pubkey.e)
-
- cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in
- fields]
-
- pubkey_s = b'<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"'
- '><Modulus>%s</Modulus><Exponent>%s</Exponent></RSAKeyValue>' % \
- tuple(cb_fields)
- else:
- pubkey_s = b''
-
- form_s2 = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
- in form.getChildren())
-
- old_c_s = self.c_s
- content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \
- self.form_s.encode('utf-8') + form_s2.encode('utf-8')
-
- mac_s = self.hmac(self.ks_s, content)
-
- if self.negotiated['send_pubkey']:
- signature = self.sign(mac_s)
-
- sign_s = '<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">'
- '%s</SignatureValue>' % base64.b64encode(signature)
-
- if self.negotiated['send_pubkey'] == 'hash':
- b64ed = base64.b64encode(self.hash(pubkey_s))
- pubkey_s = '<fingerprint>%s</fingerprint>' % b64ed
-
- id_s = self.encrypt(pubkey_s + sign_s)
- else:
- id_s = self.encrypt(mac_s)
-
- m_s = self.hmac(self.km_s, crypto.encode_mpi(old_c_s) + id_s)
-
- if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5':
- # we're alice; check for a retained secret
- # if none exists, prompt the user with the SAS
- self.sas = crypto.sas_28x5(m_s, self.form_o.encode('utf-8'))
-
- if self.sigmai:
- # FIXME save retained secret?
- self.check_identity(tuple)
-
- return (nbxmpp.DataField(name='identity',
- value=base64.b64encode(id_s).decode('utf-8')),
- nbxmpp.DataField(name='mac',
- value=base64.b64encode(m_s).decode('utf-8')))
-
- def negotiate_e2e(self, sigmai):
- self.negotiated = {}
-
- request = nbxmpp.Message()
- feature = request.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='form')
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='1',
- typ='boolean', required=True))
-
- # this field is incorrectly called 'otr' in XEPs 0116 and 0217
- x.addChild(node=nbxmpp.DataField(name='logging', typ='list-single',
- options=self.logging_preference(), required=True))
-
- # unsupported options: 'disabled', 'enabled'
- x.addChild(node=nbxmpp.DataField(name='disclosure', typ='list-single',
- options=['never'], required=True))
- x.addChild(node=nbxmpp.DataField(name='security', typ='list-single',
- options=['e2e'], required=True))
- x.addChild(node=nbxmpp.DataField(name='crypt_algs', value='aes128-ctr',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='hash_algs', value='sha256',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='compress', value='none',
- typ='hidden'))
-
- # unsupported options: 'iq', 'presence'
- x.addChild(node=nbxmpp.DataField(name='stanzas', typ='list-multi',
- options=['message']))
-
- x.addChild(node=nbxmpp.DataField(name='init_pubkey', options=['none',
- 'key', 'hash'], typ='list-single'))
-
- # FIXME store key, use hash
- x.addChild(node=nbxmpp.DataField(name='resp_pubkey', options=['none',
- 'key'], typ='list-single'))
-
- x.addChild(node=nbxmpp.DataField(name='ver', value='1.0', typ='hidden'))
-
- x.addChild(node=nbxmpp.DataField(name='rekey_freq', value='4294967295',
- typ='hidden'))
-
- x.addChild(node=nbxmpp.DataField(name='sas_algs', value='sas28x5',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='sign_algs',
- value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden'))
-
- self.n_s = crypto.generate_nonce()
-
- x.addChild(node=nbxmpp.DataField(name='my_nonce',
- value=base64.b64encode(self.n_s).decode('utf-8'), typ='hidden'))
-
- modp_options = [ int(g) for g in gajim.config.get('esession_modp').split(
- ',') ]
-
- x.addChild(node=nbxmpp.DataField(name='modp', typ='list-single',
- options=[[None, y] for y in modp_options]))
-
- x.addChild(node=self.make_dhfield(modp_options, sigmai))
- self.sigmai = sigmai
-
- self.form_s = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
- el in x.getChildren())
-
- feature.addChild(node=x)
-
- self.status = 'requested-e2e'
-
- self.send(request)
-
- def verify_options_bob(self, form):
- """
- 4.3 esession response (bob)
- """
- negotiated = {'recv_pubkey': None, 'send_pubkey': None}
- not_acceptable = []
- ask_user = {}
-
- fixed = { 'disclosure': 'never', 'security': 'e2e',
- 'crypt_algs': 'aes128-ctr', 'hash_algs': 'sha256', 'compress': 'none',
- 'stanzas': 'message', 'init_pubkey': 'none', 'resp_pubkey': 'none',
- 'ver': '1.0', 'sas_algs': 'sas28x5' }
-
- self.encryptable_stanzas = ['message']
-
- self.sas_algs = 'sas28x5'
- self.cipher = AES
- self.hash_alg = sha256
- self.compression = None
-
- for name in form.asDict():
- field = form.getField(name)
- options = [x[1] for x in field.getOptions()]
- values = field.getValues()
-
- if not field.getType() in ('list-single', 'list-multi'):
- options = values
-
- if name in fixed:
- if fixed[name] in options:
- negotiated[name] = fixed[name]
- else:
- not_acceptable.append(name)
- elif name == 'rekey_freq':
- preferred = int(options[0])
- negotiated['rekey_freq'] = preferred
- self.rekey_freq = preferred
- elif name == 'logging':
- my_prefs = self.logging_preference()
-
- if my_prefs[0] in options: # our first choice is offered, select it
- pref = my_prefs[0]
- negotiated['logging'] = pref
- else: # see if other acceptable choices are offered
- for pref in my_prefs:
- if pref in options:
- ask_user['logging'] = pref
- break
-
- if not 'logging' in ask_user:
- not_acceptable.append(name)
- elif name == 'init_pubkey':
- for x in ('key'):
- if x in options:
- negotiated['recv_pubkey'] = x
- break
- elif name == 'resp_pubkey':
- for x in ('hash', 'key'):
- if x in options:
- negotiated['send_pubkey'] = x
- break
- elif name == 'sign_algs':
- if (XmlDsig + 'rsa-sha256') in options:
- negotiated['sign_algs'] = XmlDsig + 'rsa-sha256'
- else:
- # FIXME some things are handled elsewhere, some things are
- # not-implemented
- pass
-
- return (negotiated, not_acceptable, ask_user)
-
- def respond_e2e_bob(self, form, negotiated, not_acceptable):
- """
- 4.3 esession response (bob)
- """
- response = nbxmpp.Message()
- feature = response.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='true'))
-
- for name in negotiated:
- # some fields are internal and should not be sent
- if not name in ('send_pubkey', 'recv_pubkey'):
- x.addChild(node=nbxmpp.DataField(name=name,
- value=negotiated[name]))
-
- self.negotiated = negotiated
-
- # the offset of the group we chose (need it to match up with the dhhash)
- group_order = 0
- modp_f = form.getField('modp')
- if not modp_f:
- return
- self.modp = int(modp_f.getOptions()[group_order][1])
- x.addChild(node=nbxmpp.DataField(name='modp', value=self.modp))
-
- g = dh.generators[self.modp]
- p = dh.primes[self.modp]
-
- self.n_o = base64.b64decode(form['my_nonce'])
-
- dhhashes_f = form.getField('dhhashes')
- if not dhhashes_f:
- return
- dhhashes = dhhashes_f.getValues()
- self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode(
- 'utf8'))
-
- bytes = int(self.n / 8)
-
- self.n_s = crypto.generate_nonce()
-
- # n-bit random number
- self.c_o = crypto.decode_mpi(crypto.random_bytes(bytes))
- self.c_s = self.c_o ^ (2 ** (self.n - 1))
-
- self.y = crypto.srand(2 ** (2 * self.n - 1), p - 1)
- self.d = crypto.powmod(g, self.y, p)
-
- to_add = {'my_nonce': self.n_s,
- 'dhkeys': crypto.encode_mpi(self.d),
- 'counter': crypto.encode_mpi(self.c_o),
- 'nonce': self.n_o}
-
- for name in to_add:
- b64ed = base64.b64encode(to_add[name]).decode('utf-8')
- x.addChild(node=nbxmpp.DataField(name=name, value=b64ed))
-
- self.form_o = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
- el in form.getChildren())
- self.form_s = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
- el in x.getChildren())
-
- self.status = 'responded-e2e'
-
- feature.addChild(node=x)
-
- if not_acceptable:
- response = nbxmpp.Error(response, nbxmpp.ERR_NOT_ACCEPTABLE)
-
- feature = nbxmpp.Node(nbxmpp.NS_FEATURE + ' feature')
-
- for f in not_acceptable:
- n = nbxmpp.Node('field')
- n['var'] = f
- feature.addChild(node=n)
-
- response.T.error.addChild(node=feature)
-
- self.send(response)
-
- def verify_options_alice(self, form):
- """
- 'Alice Accepts'
- """
- negotiated = {}
- ask_user = {}
- not_acceptable = []
-
- if not form['logging'] in self.logging_preference():
- not_acceptable.append(form['logging'])
- elif form['logging'] != self.logging_preference()[0]:
- ask_user['logging'] = form['logging']
- else:
- negotiated['logging'] = self.logging_preference()[0]
-
- for r, a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey',
- 'init_pubkey')):
- negotiated[r] = None
-
- if a in form.asDict() and form[a] in ('key', 'hash'):
- negotiated[r] = form[a]
-
- if 'sign_algs' in form.asDict():
- if form['sign_algs'] in (XmlDsig + 'rsa-sha256', ):
- negotiated['sign_algs'] = form['sign_algs']
- else:
- not_acceptable.append(form['sign_algs'])
-
- return (negotiated, not_acceptable, ask_user)
-
- def accept_e2e_alice(self, form, negotiated):
- """
- 'Alice Accepts', continued
- """
- self.encryptable_stanzas = ['message']
- self.sas_algs = 'sas28x5'
- self.cipher = AES
- self.hash_alg = sha256
- self.compression = None
-
- self.negotiated = negotiated
-
- accept = nbxmpp.Message()
- feature = accept.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- result = nbxmpp.DataForm(typ='result')
-
- self.c_s = crypto.decode_mpi(base64.b64decode(form['counter']))
- self.c_o = self.c_s ^ (2 ** (self.n - 1))
- self.n_o = base64.b64decode(form['my_nonce'])
-
- mod_p = int(form['modp'])
- p = dh.primes[mod_p]
- x = self.xes[mod_p]
- e = self.es[mod_p]
-
- self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
- self.k = self.get_shared_secret(self.d, x, p)
-
- result.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- result.addChild(node=nbxmpp.DataField(name='accept', value='1'))
- result.addChild(node=nbxmpp.DataField(name='nonce',
- value=base64.b64encode(self.n_o).decode('utf-8')))
-
- self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k)
-
- if self.sigmai:
- self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k)
- self.verify_identity(form, self.d, True, 'b')
- else:
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
- rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses]
-
- if not rshashes:
- # we've never spoken before, but we'll pretend we have
- rshash_size = self.hash_alg().digest_size
- rshashes.append(crypto.random_bytes(rshash_size))
-
- rshashes = [base64.b64encode(rshash).decode('utf-8') for rshash in \
- rshashes]
- result.addChild(node=nbxmpp.DataField(name='rshashes',
- value=rshashes))
- result.addChild(node=nbxmpp.DataField(name='dhkeys',
- value=base64.b64encode(crypto.encode_mpi(e)).decode('utf-8')))
-
- self.form_o = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) \
- for el in form.getChildren())
-
- # MUST securely destroy K unless it will be used later to generate the
- # final shared secret
-
- for datafield in self.make_identity(result, e):
- result.addChild(node=datafield)
-
- feature.addChild(node=result)
- self.send(accept)
-
- if self.sigmai:
- self.status = 'active'
- self.enable_encryption = True
- else:
- self.status = 'identified-alice'
-
- def accept_e2e_bob(self, form):
- """
- 4.5 esession accept (bob)
- """
- response = nbxmpp.Message()
-
- init = response.NT.init
- init.setNamespace(nbxmpp.NS_ESESSION_INIT)
-
- x = nbxmpp.DataForm(typ='result')
-
- for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'):
- # FIXME: will do nothing in real world...
- assert field in form.asDict(), "alice's form didn't have a %s field" \
- % field
-
- # 4.5.1 generating provisory session keys
- e = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
- p = dh.primes[self.modp]
-
- if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']:
- raise NegotiationError('SHA256(e) != He')
-
- k = self.get_shared_secret(e, self.y, p)
- self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
-
- # 4.5.2 verifying alice's identity
- self.verify_identity(form, e, False, 'a')
-
- # 4.5.4 generating bob's final session keys
- srs = b''
-
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
- rshashes = [base64.b64decode(rshash) for rshash in form.getField(
- 'rshashes').getValues()]
-
- for s in srses:
- secret = s[0]
- if self.hmac(self.n_o, secret) in rshashes:
- srs = secret
- break
-
- # other shared secret
- # (we're not using one)
- oss = b''
-
- k = crypto.sha256(k + srs + oss)
-
- self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k)
- self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
-
- # 4.5.5
- if srs:
- srshash = self.hmac(srs, b'Shared Retained Secret')
- else:
- srshash = crypto.random_bytes(32)
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='nonce', value=base64.b64encode(
- self.n_o).decode('utf-8')))
- x.addChild(node=nbxmpp.DataField(name='srshash', value=base64.b64encode(
- srshash).decode('utf-8')))
-
- for datafield in self.make_identity(x, self.d):
- x.addChild(node=datafield)
-
- init.addChild(node=x)
-
- self.send(response)
-
- self.do_retained_secret(k, srs)
-
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
-
- self.status = 'active'
- self.enable_encryption = True
-
- if self.control:
- self.control.print_esession_details()
-
- self.stop_archiving_for_session()
-
- def final_steps_alice(self, form):
- srs = b''
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
-
- try:
- srshash = base64.b64decode(form['srshash'])
- except IndexError:
- return
-
- for s in srses:
- secret = s[0]
- if self.hmac(secret, b'Shared Retained Secret') == srshash:
- srs = secret
- break
-
- oss = b''
- k = crypto.sha256(self.k + srs + oss)
- del self.k
-
- self.do_retained_secret(k, srs)
-
- # ks_s doesn't need to be calculated here
- self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k)
- self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k)
-
- # 4.6.2 Verifying Bob's Identity
- self.verify_identity(form, self.d, False, 'b')
- # Note: If Alice discovers an error then she SHOULD ignore any encrypted
- # content she received in the stanza.
-
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
-
- self.status = 'active'
- self.enable_encryption = True
-
- if self.control:
- self.control.print_esession_details()
-
- self.stop_archiving_for_session()
-
- def do_retained_secret(self, k, old_srs):
- """
- Calculate the new retained secret. determine if the user needs to check
- the remote party's identity. Set up callbacks for when the identity has
- been verified
- """
- new_srs = self.hmac(k, b'New Retained Secret')
- self.srs = new_srs
-
- account = self.conn.name
- bjid = self.jid.getStripped()
-
- self.verified_identity = False
-
- if old_srs:
- if secrets.secrets().srs_verified(account, bjid, old_srs):
- # already had a stored secret verified by the user.
- secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True)
- # continue without warning.
- self.verified_identity = True
- else:
- # had a secret, but it wasn't verified.
- secrets.secrets().replace_srs(account, bjid, old_srs, new_srs,
- False)
- else:
- # we don't even have an SRS
- secrets.secrets().save_new_srs(account, bjid, new_srs, False)
-
- def _verified_srs_cb(self):
- secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(),
- self.srs, self.srs, True)
-
- def _unverified_srs_cb(self):
- secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(),
- self.srs, self.srs, False)
-
- def make_dhfield(self, modp_options, sigmai):
- dhs = []
-
- for modp in modp_options:
- p = dh.primes[modp]
- g = dh.generators[modp]
-
- x = crypto.srand(2 ** (2 * self.n - 1), p - 1)
-
- # FIXME this may be a source of performance issues
- e = crypto.powmod(g, x, p)
-
- self.xes[modp] = x
- self.es[modp] = e
-
- if sigmai:
- dhs.append(base64.b64encode(crypto.encode_mpi(e)).decode('utf-8'))
- name = 'dhkeys'
- else:
- He = crypto.sha256(crypto.encode_mpi(e))
- dhs.append(base64.b64encode(He).decode('utf-8'))
- name = 'dhhashes'
-
- return nbxmpp.DataField(name=name, typ='hidden', value=dhs)
-
- def terminate_e2e(self):
- self.enable_encryption = False
- if self.control:
- self.control.print_session_details()
- self.terminate()
-
- def acknowledge_termination(self):
- StanzaSession.acknowledge_termination(self)
- self.enable_encryption = False
-
- def fail_bad_negotiation(self, reason, fields=None):
- """
- Send an error and cancels everything
-
- If fields is None, the remote party has given us a bad cryptographic
- value of some kind. Otherwise, list the fields we haven't implemented.
- """
- err = nbxmpp.Error(nbxmpp.Message(), nbxmpp.ERR_FEATURE_NOT_IMPLEMENTED)
- err.T.error.T.text.setData(reason)
-
- if fields:
- feature = nbxmpp.Node(nbxmpp.NS_FEATURE + ' feature')
-
- for field in fields:
- fn = nbxmpp.Node('field')
- fn['var'] = field
- feature.addChild(node=feature)
-
- err.addChild(node=feature)
-
- self.send(err)
-
- self.status = None
- self.enable_encryption = False
-
- # this prevents the MAC check on decryption from succeeding,
- # preventing falsified messages from going through.
- self.km_o = ''
-
- def cancelled_negotiation(self):
- StanzaSession.cancelled_negotiation(self)
- self.enable_encryption = False
- self.km_o = ''
diff --git a/src/common/zeroconf/__init__.py b/src/common/zeroconf/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/src/common/zeroconf/__init__.py
+++ /dev/null
diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py
deleted file mode 100644
index 5a1bd541e..000000000
--- a/src/common/zeroconf/client_zeroconf.py
+++ /dev/null
@@ -1,864 +0,0 @@
-## common/zeroconf/client_zeroconf.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
-## 2006 Dimitur Kirov <dkirov@gmail.com>
-##
-## 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/>.
-##
-from common import gajim
-import nbxmpp
-from nbxmpp.idlequeue import IdleObject
-from nbxmpp import dispatcher_nb, simplexml
-from nbxmpp.plugin import *
-from nbxmpp.transports_nb import DATA_RECEIVED, DATA_SENT, DATA_ERROR
-from common.zeroconf import zeroconf
-
-from nbxmpp.protocol import *
-import socket
-import ssl
-import errno
-import sys
-import os
-import string
-from random import Random
-
-import logging
-log = logging.getLogger('gajim.c.z.client_zeroconf')
-
-from common.zeroconf import roster_zeroconf
-
-MAX_BUFF_LEN = 65536
-TYPE_SERVER, TYPE_CLIENT = range(2)
-
-# wait XX sec to establish a connection
-CONNECT_TIMEOUT_SECONDS = 10
-
-# after XX sec with no activity, close the stream
-ACTIVITY_TIMEOUT_SECONDS = 30
-
-class ZeroconfListener(IdleObject):
- def __init__(self, port, conn_holder):
- """
- Handle all incomming connections on ('0.0.0.0', port)
- """
- self.port = port
- self.queue_idx = -1
- #~ self.queue = None
- self.started = False
- self._sock = None
- self.fd = -1
- self.caller = conn_holder.caller
- self.conn_holder = conn_holder
-
- def bind(self):
- flags = socket.AI_PASSIVE
- if hasattr(socket, 'AI_ADDRCONFIG'):
- flags |= socket.AI_ADDRCONFIG
- ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, 0, flags)[0]
- self._serv = socket.socket(ai[0], ai[1])
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
- self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- if os.name == 'nt':
- ver = os.sys.getwindowsversion()
- if (ver[3], ver[0]) == (2, 6): # Win Vista +
- # 47 is socket.IPPROTO_IPV6
- # 27 is socket.IPV6_V6ONLY under windows, but not defined ...
- self._serv.setsockopt(41, 27, 0)
- # will fail when port is busy, or we don't have rights to bind
- try:
- self._serv.bind((ai[4][0], self.port))
- except Exception:
- # unable to bind, show error dialog
- return None
- self._serv.listen(socket.SOMAXCONN)
- self._serv.setblocking(False)
- self.fd = self._serv.fileno()
- gajim.idlequeue.plug_idle(self, False, True)
- self.started = True
-
- def pollend(self):
- """
- Called when we stop listening on (host, port)
- """
- self.disconnect()
-
- def pollin(self):
- """
- Accept a new incomming connection and notify queue
- """
- sock = self.accept_conn()
- # loop through roster to find who has connected to us
- from_jid = None
- ipaddr = sock[1][0]
- for jid in self.conn_holder.getRoster().keys():
- entry = self.conn_holder.getRoster().getItem(jid)
- for address in entry['addresses']:
- if (address['address'] == ipaddr):
- from_jid = jid
- break
- P2PClient(sock[0], [{'host': ipaddr, 'address': ipaddr, 'port': sock[1][1]}], self.conn_holder, [], from_jid)
-
- def disconnect(self, message=''):
- """
- Free all resources, we are not listening anymore
- """
- log.info('Disconnecting ZeroconfListener: %s' % message)
- gajim.idlequeue.remove_timeout(self.fd)
- gajim.idlequeue.unplug_idle(self.fd)
- self.fd = -1
- self.started = False
- try:
- self._serv.close()
- except socket.error:
- pass
- self.conn_holder.kill_all_connections()
-
- def accept_conn(self):
- """
- Accept a new incoming connection
- """
- _sock = self._serv.accept()
- _sock[0].setblocking(False)
- return _sock
-
-class P2PClient(IdleObject):
- def __init__(self, _sock, addresses, conn_holder, stanzaqueue, to=None,
- on_ok=None, on_not_ok=None):
- self._owner = self
- self.Namespace = 'jabber:client'
- self.protocol_type = 'XMPP'
- self.defaultNamespace = self.Namespace
- self._component = 0
- self._registered_name = None
- self._caller = conn_holder.caller
- self.conn_holder = conn_holder
- self.stanzaqueue = stanzaqueue
- self.to = to
- #self.Server = addresses[0]['host']
- self.on_ok = on_ok
- self.on_not_ok = on_not_ok
- self.Connection = None
- self.sock_hash = None
- if _sock:
- self.sock_type = TYPE_SERVER
- else:
- self.sock_type = TYPE_CLIENT
- self.fd = -1
- conn = P2PConnection('', _sock, addresses, self._caller,
- self.on_connect, self)
- self.Server = conn.host # set Server to the last host name / address tried
- if not self.conn_holder:
- # An error occured, disconnect() has been called
- if on_not_ok:
- on_not_ok('Connection to host could not be established.')
- return
- self.sock_hash = conn._sock.__hash__
- self.fd = conn.fd
- self.conn_holder.add_connection(self, self.Server, conn.port, self.to)
- # count messages in queue
- for val in self.stanzaqueue:
- stanza, is_message = val
- if is_message:
- if self.fd == -1:
- if on_not_ok:
- on_not_ok(
- 'Connection to host could not be established.')
- return
- thread_id = stanza.getThread()
- id_ = stanza.getID()
- if not id_:
- id_ = self.Dispatcher.getAnID()
- if self.fd in self.conn_holder.ids_of_awaiting_messages:
- self.conn_holder.ids_of_awaiting_messages[self.fd].append((
- id_, thread_id))
- else:
- self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
- thread_id)]
-
- self.on_responses = {}
-
- def add_stanza(self, stanza, is_message=False):
- if self.Connection:
- if self.Connection.state == -1:
- return False
- self.send(stanza, is_message)
- else:
- self.stanzaqueue.append((stanza, is_message))
-
- if is_message:
- thread_id = stanza.getThread()
- id_ = stanza.getID()
- if not id_:
- id_ = self.Dispatcher.getAnID()
- if self.fd in self.conn_holder.ids_of_awaiting_messages:
- self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_,
- thread_id))
- else:
- self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
- thread_id)]
-
- return True
-
- def on_message_sent(self, connection_id):
- id_, thread_id = \
- self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0)
- if self.on_ok:
- self.on_ok(id_)
- # use on_ok only on first message. For others it's called in
- # ClientZeroconf
- self.on_ok = None
-
- def on_connect(self, conn):
- self.Connection = conn
- self.Connection.PlugIn(self)
- dispatcher_nb.Dispatcher().PlugIn(self)
- self._register_handlers()
-
- def StreamInit(self):
- """
- Send an initial stream header
- """
- self.Dispatcher.Stream = simplexml.NodeBuilder()
- self.Dispatcher.Stream._dispatch_depth = 2
- self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
- self.Dispatcher.Stream.stream_header_received = self._check_stream_start
- self.Dispatcher.Stream.features = None
- if self.sock_type == TYPE_CLIENT:
- self.send_stream_header()
-
- def send_stream_header(self):
- self.Dispatcher._metastream = Node('stream:stream')
- self.Dispatcher._metastream.setNamespace(self.Namespace)
- self.Dispatcher._metastream.setAttr('version', '1.0')
- self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS)
- self.Dispatcher._metastream.setAttr('from',
- self.conn_holder.zeroconf.name)
- if self.to:
- self.Dispatcher._metastream.setAttr('to', self.to)
- self.Dispatcher.send("<?xml version='1.0'?>%s>" % str(
- self.Dispatcher._metastream)[:-2])
-
- def _check_stream_start(self, ns, tag, attrs):
- if ns != NS_STREAMS or tag != 'stream':
- log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag,
- ns), 'error')
- self.Connection.disconnect()
- if self.on_not_ok:
- self.on_not_ok('Connection to host could not be established: '
- 'Incorrect answer from server.')
- return
- if self.sock_type == TYPE_SERVER:
- if 'from' in attrs:
- self.to = attrs['from']
- self.send_stream_header()
- if 'version' in attrs and attrs['version'] == '1.0':
- # other part supports stream features
- features = Node('stream:features')
- self.Dispatcher.send(features)
- while self.stanzaqueue:
- stanza, is_message = self.stanzaqueue.pop(0)
- self.send(stanza, is_message)
- elif self.sock_type == TYPE_CLIENT:
- while self.stanzaqueue:
- stanza, is_message = self.stanzaqueue.pop(0)
- self.send(stanza, is_message)
-
- def on_disconnect(self):
- if self.conn_holder:
- if self.fd in self.conn_holder.ids_of_awaiting_messages:
- del self.conn_holder.ids_of_awaiting_messages[self.fd]
- self.conn_holder.remove_connection(self.sock_hash)
- if 'Dispatcher' in self.__dict__:
- self.Dispatcher.PlugOut()
- if 'P2PConnection' in self.__dict__:
- self.P2PConnection.PlugOut()
- self.Connection = None
- self._caller = None
- self.conn_holder = None
-
- def force_disconnect(self):
- if self.Connection:
- self.disconnect()
- else:
- self.on_disconnect()
-
- def _on_receive_document_attrs(self, data):
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not hasattr(self, 'Dispatcher') or \
- self.Dispatcher.Stream._document_attrs is None:
- return
- self.onreceive(None)
- if 'version' in self.Dispatcher.Stream._document_attrs and \
- self.Dispatcher.Stream._document_attrs['version'] == '1.0':
- #~ self.onreceive(self._on_receive_stream_features)
- #XXX continue with TLS
- return
- self.onreceive(None)
- return True
-
- def remove_timeout(self):
- pass
-
- def _register_handlers(self):
- self._caller.peerhost = self.Connection._sock.getsockname()
- self.RegisterHandler('message', lambda conn,
- data:self._caller._messageCB(self.Server, conn, data))
- self.RegisterHandler('iq', self._caller._siSetCB, 'set', nbxmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._siErrorCB, 'error',
- nbxmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._siResultCB, 'result',
- nbxmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set',
- nbxmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result',
- nbxmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error',
- nbxmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get',
- nbxmpp.NS_DISCO_ITEMS)
- self.RegisterHandler('iq', self._caller._JingleCB, 'result')
- self.RegisterHandler('iq', self._caller._JingleCB, 'error')
- self.RegisterHandler('iq', self._caller._JingleCB, 'set',
- nbxmpp.NS_JINGLE)
-
-class P2PConnection(IdleObject, PlugIn):
- def __init__(self, sock_hash, _sock, addresses=None, caller=None,
- on_connect=None, client=None):
- IdleObject.__init__(self)
- self._owner = client
- PlugIn.__init__(self)
- self.sendqueue = []
- self.sendbuff = None
- self.buff_is_message = False
- self._sock = _sock
- self.sock_hash = None
- self.addresses = addresses
- self.on_connect = on_connect
- self.client = client
- self.writable = False
- self.readable = False
- self._exported_methods = [self.send, self.disconnect, self.onreceive]
- self.on_receive = None
- if _sock:
- self.host = addresses[0]['host']
- self.port = addresses[0]['port']
- self._sock = _sock
- self.state = 1
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.on_connect(self)
- else:
- self.state = 0
- self.addresses_ = self.addresses
- self.get_next_addrinfo()
-
- def get_next_addrinfo(self):
- address = self.addresses_.pop(0)
- self.host = address['host']
- self.port = address['port']
- try:
- self.ais = socket.getaddrinfo(address['host'], address['port'], socket.AF_UNSPEC,
- socket.SOCK_STREAM)
- except socket.gaierror as e:
- log.info('Lookup failure for %s: %s[%s]', host, e[1],
- repr(e[0]), exc_info=True)
- if len(self.addresses_) > 0: return self.get_next_addrinfo()
- else:
- self.connect_to_next_ip()
-
- def connect_to_next_ip(self):
- if len(self.ais) == 0:
- log.error('Connection failure to %s', str(self.host), exc_info=True)
- if len(self.addresses_) > 0: return self.get_next_addrinfo()
- self.disconnect()
- return
- ai = self.ais.pop(0)
- log.info('Trying to connect to %s through %s:%s', str(self.host),
- ai[4][0], ai[4][1], exc_info=True)
- try:
- self._sock = socket.socket(*ai[:3])
- self._sock.setblocking(False)
- self._server = ai[4]
- except socket.error:
- if sys.exc_value[0] != errno.EINPROGRESS:
- # for all errors, we try other addresses
- self.connect_to_next_ip()
- return
- self.fd = self._sock.fileno()
- gajim.idlequeue.plug_idle(self, True, False)
- self.set_timeout(CONNECT_TIMEOUT_SECONDS)
- self.do_connect()
-
- def set_timeout(self, timeout):
- gajim.idlequeue.remove_timeout(self.fd)
- if self.state >= 0:
- gajim.idlequeue.set_read_timeout(self.fd, timeout)
-
- def plugin(self, owner):
- self.onreceive(owner._on_receive_document_attrs)
- self._plug_idle()
- return True
-
- def plugout(self):
- """
- Disconnect from the remote server and unregister self.disconnected
- method from the owner's dispatcher
- """
- self.disconnect()
- self._owner = None
-
- def onreceive(self, recv_handler):
- if not recv_handler:
- if hasattr(self._owner, 'Dispatcher'):
- self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
- else:
- self.on_receive = None
- return
- _tmp = self.on_receive
- # make sure this cb is not overriden by recursive calls
- if not recv_handler(None) and _tmp == self.on_receive:
- self.on_receive = recv_handler
-
- def send(self, packet, is_message=False, now=False):
- """
- Append stanza to the queue of messages to be send if now is False, else
- send it instantly
- """
- if self.state <= 0:
- return
-
- r = packet
-
- if now:
- self.sendqueue.insert(0, (r, is_message))
- self._do_send()
- else:
- self.sendqueue.append((r, is_message))
- self._plug_idle()
-
- def read_timeout(self):
- ids = self.client.conn_holder.ids_of_awaiting_messages
- if self.fd in ids and len(ids[self.fd]) > 0:
- for (id_, thread_id) in ids[self.fd]:
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_ERROR, (
- self.client.to, thread_id))
- else:
- self._owner.on_not_ok('connection timeout')
- ids[self.fd] = []
- self.pollend()
-
- def do_connect(self):
- errnum = 0
- try:
- self._sock.connect(self._server[:2])
- self._sock.setblocking(False)
- except Exception as ee:
- errnum = ee.errno
- errstr = ee.strerror
- errors = (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK)
- if 'WSAEINVAL' in errno.__dict__:
- errors += (errno.WSAEINVAL,)
- if errnum in errors:
- return
- # win32 needs this
- elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0:
- log.error('Could not connect to %s: %s [%s]', str(self.host),
- errnum, errstr)
- self.connect_to_next_ip()
- return
- else: # socket is already connected
- self._sock.setblocking(False)
- self.state = 1 # connected
- # we are connected
- self.on_connect(self)
-
- def pollout(self):
- if self.state == 0:
- self.do_connect()
- return
- gajim.idlequeue.remove_timeout(self.fd)
- self._do_send()
-
- def pollend(self):
- if self.state == 0: # error in connect()?
- #self.disconnect()
- self.connect_to_next_ip()
- else:
- self.state = -1
- self.disconnect()
-
- def pollin(self):
- """
- Reads all pending incoming data. Call owner's disconnected() method if
- appropriate
- """
- received = ''
- errnum = 0
- try:
- # get as many bites, as possible, but not more than RECV_BUFSIZE
- received = self._sock.recv(MAX_BUFF_LEN)
- except Exception as e:
- errnum = e.errno
- # "received" will be empty anyhow
- if errnum == ssl.SSL_ERROR_WANT_READ:
- pass
- elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
- self.pollend()
- # don't proccess result, cas it will raise error
- return
- elif not received :
- if errnum != ssl.SSL_ERROR_EOF:
- # 8 EOF occurred in violation of protocol
- self.pollend()
- if self.state >= 0:
- self.disconnect()
- return
-
- if self.state < 0:
- return
- if self.on_receive:
- if self._owner.sock_type == TYPE_CLIENT:
- self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
- if received.strip():
- log.debug('received: %s', received)
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
- self.on_receive(received)
- else:
- # This should never happed, so we need the debug
- log.error('Unhandled data received: %s' % received)
- self.disconnect()
- return True
-
- def disconnect(self, message=''):
- """
- Close the socket
- """
- gajim.idlequeue.remove_timeout(self.fd)
- gajim.idlequeue.unplug_idle(self.fd)
- try:
- self._sock.shutdown(socket.SHUT_RDWR)
- self._sock.close()
- except socket.error:
- # socket is already closed
- pass
- self.fd = -1
- self.state = -1
- if self._owner:
- self._owner.on_disconnect()
-
- def _do_send(self):
- if not self.sendbuff:
- if not self.sendqueue:
- return None # nothing to send
- self.sendbuff, self.buff_is_message = self.sendqueue.pop(0)
- self.sent_data = self.sendbuff
- try:
- send_count = self._sock.send(self.sendbuff)
- if send_count:
- self.sendbuff = self.sendbuff[send_count:]
- if not self.sendbuff and not self.sendqueue:
- if self.state < 0:
- gajim.idlequeue.unplug_idle(self.fd)
- self._on_send()
- self.disconnect()
- return
- # we are not waiting for write
- self._plug_idle()
- self._on_send()
-
- except socket.error as e:
- if e.errno == ssl.SSL_ERROR_WANT_WRITE:
- return True
- if self.state < 0:
- self.disconnect()
- return
- self._on_send_failure()
- return
- if self._owner.sock_type == TYPE_CLIENT:
- self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
- return True
-
- def _plug_idle(self):
- readable = self.state != 0
- if self.sendqueue or self.sendbuff:
- writable = True
- else:
- writable = False
- if self.writable != writable or self.readable != readable:
- gajim.idlequeue.plug_idle(self, writable, readable)
-
-
- def _on_send(self):
- if self.sent_data and self.sent_data.strip():
- log.debug('sent: %s' % self.sent_data)
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
- self.sent_data = None
- if self.buff_is_message:
- self._owner.on_message_sent(self.fd)
- self.buff_is_message = False
-
- def _on_send_failure(self):
- log.error('Socket error while sending data')
- self._owner.on_disconnect()
- self.sent_data = None
-
-class ClientZeroconf:
- def __init__(self, caller):
- self.caller = caller
- self.zeroconf = None
- self.roster = None
- self.last_msg = ''
- self.connections = {}
- self.recipient_to_hash = {}
- self.ip_to_hash = {}
- self.hash_to_port = {}
- self.listener = None
- self.ids_of_awaiting_messages = {}
- self.disconnect_handlers = []
- self.disconnecting = False
-
- def connect(self, show, msg):
- self.port = self.start_listener(self.caller.port)
- if not self.port:
- return False
- self.zeroconf_init(show, msg)
- if not self.zeroconf.connect():
- self.disconnect()
- return None
- self.roster = roster_zeroconf.Roster(self.zeroconf)
- return True
-
- def remove_announce(self):
- if self.zeroconf:
- return self.zeroconf.remove_announce()
-
- def announce(self):
- if self.zeroconf:
- return self.zeroconf.announce()
-
- def set_show_msg(self, show, msg):
- if self.zeroconf:
- self.zeroconf.txt['msg'] = msg
- self.last_msg = msg
- return self.zeroconf.update_txt(show)
-
- def resolve_all(self):
- if self.zeroconf:
- self.zeroconf.resolve_all()
-
- def reannounce(self, txt):
- self.remove_announce()
- self.zeroconf.txt = txt
- self.zeroconf.port = self.port
- self.zeroconf.username = self.caller.username
- return self.announce()
-
- def zeroconf_init(self, show, msg):
- self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service,
- self.caller._on_remove_service, self.caller._on_name_conflictCB,
- self.caller._on_disconnected, self.caller._on_error,
- self.caller.username, self.caller.host, self.port)
- self.zeroconf.txt['msg'] = msg
- self.zeroconf.txt['status'] = show
- self.zeroconf.txt['1st'] = self.caller.first
- self.zeroconf.txt['last'] = self.caller.last
- self.zeroconf.txt['jid'] = self.caller.jabber_id
- self.zeroconf.txt['email'] = self.caller.email
- self.zeroconf.username = self.caller.username
- self.zeroconf.host = self.caller.host
- self.zeroconf.port = self.port
- self.last_msg = msg
-
- def disconnect(self):
- # to avoid recursive calls
- if self.disconnecting:
- return
- if self.listener:
- self.listener.disconnect()
- self.listener = None
- if self.zeroconf:
- self.zeroconf.disconnect()
- self.zeroconf = None
- if self.roster:
- self.roster.zeroconf = None
- self.roster._data = None
- self.roster = None
- self.disconnecting = True
- for i in reversed(self.disconnect_handlers):
- log.debug('Calling disconnect handler %s' % i)
- i()
- self.disconnecting = False
-
- def start_disconnect(self):
- self.disconnect()
-
- def kill_all_connections(self):
- for connection in self.connections.values():
- connection.force_disconnect()
-
- def add_connection(self, connection, ip, port, recipient):
- sock_hash=connection.sock_hash
- if sock_hash not in self.connections:
- self.connections[sock_hash] = connection
- self.ip_to_hash[ip] = sock_hash
- self.hash_to_port[sock_hash] = port
- if recipient:
- self.recipient_to_hash[recipient] = sock_hash
-
- def remove_connection(self, sock_hash):
- if sock_hash in self.connections:
- del self.connections[sock_hash]
- for i in self.recipient_to_hash:
- if self.recipient_to_hash[i] == sock_hash:
- del self.recipient_to_hash[i]
- break
- for i in self.ip_to_hash:
- if self.ip_to_hash[i] == sock_hash:
- del self.ip_to_hash[i]
- break
- if sock_hash in self.hash_to_port:
- del self.hash_to_port[sock_hash]
-
- def start_listener(self, port):
- for p in range(port, port + 5):
- self.listener = ZeroconfListener(p, self)
- self.listener.bind()
- if self.listener.started:
- return p
- self.listener = None
- return False
-
- def getRoster(self):
- if self.roster:
- return self.roster.getRoster()
- return {}
-
- def send(self, stanza, is_message=False, now=False, on_ok=None,
- on_not_ok=None):
- to = stanza.getTo()
- if to is None:
- # Can’t send undirected stanza over Zeroconf.
- return -1
- to = to.getStripped()
- stanza.setFrom(self.roster.zeroconf.name)
-
- try:
- item = self.roster[to]
- except KeyError:
- # Contact offline
- return -1
-
- # look for hashed connections
- if to in self.recipient_to_hash:
- conn = self.connections[self.recipient_to_hash[to]]
- id_ = stanza.getID() or ''
- if conn.add_stanza(stanza, is_message):
- if on_ok:
- on_ok(id_)
- return
-
- the_address = None
- for address in item['addresses']:
- if address['address'] in self.ip_to_hash:
- the_address = address
- if the_address and the_address['address'] in self.ip_to_hash:
- hash_ = self.ip_to_hash[the_address['address']]
- if self.hash_to_port[hash_] == the_address['port']:
- conn = self.connections[hash_]
- id_ = stanza.getID() or ''
- if conn.add_stanza(stanza, is_message):
- if on_ok:
- on_ok(id_)
- return
-
- # otherwise open new connection
- if not stanza.getID():
- stanza.setID('zero')
- addresses_ = []
- for address in item['addresses']:
- addresses_ += [{'host': address['address'], 'address': address['address'], 'port': address['port']}]
- P2PClient(None, addresses_, self,
- [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok)
-
- def getAnID(self):
- """
- Generate a random id
- """
- return ''.join(Random().sample(string.ascii_letters + string.digits, 6))
-
- def RegisterDisconnectHandler(self, handler):
- """
- Register handler that will be called on disconnect
- """
- self.disconnect_handlers.append(handler)
-
- def UnregisterDisconnectHandler(self, handler):
- """
- Unregister handler that is called on disconnect
- """
- self.disconnect_handlers.remove(handler)
-
- def SendAndWaitForResponse(self, stanza, timeout=None, func=None,
- args=None):
- """
- Send stanza and wait for recipient's response to it. Will call
- transports on_timeout callback if response is not retrieved in time
-
- Be aware: Only timeout of latest call of SendAndWait is active.
- """
-# if timeout is None:
-# timeout = DEFAULT_TIMEOUT_SECONDS
- def on_ok(_waitid):
-# if timeout:
-# self._owner.set_timeout(timeout)
- to = stanza.getTo()
- to = gajim.get_jid_without_resource(to)
-
- try:
- item = self.roster[to]
- except KeyError:
- # Contact offline
- item = None
-
- conn = None
- if to in self.recipient_to_hash:
- conn = self.connections[self.recipient_to_hash[to]]
- elif item:
- the_address = None
- for address in item['addresses']:
- if address['address'] in self.ip_to_hash:
- the_address = address
- if the_address and the_address['address'] in self.ip_to_hash:
- hash_ = self.ip_to_hash[the_address['address']]
- if self.hash_to_port[hash_] == the_address['port']:
- conn = self.connections[hash_]
- if func:
- conn.Dispatcher.on_responses[_waitid] = (func, args)
- conn.onreceive(conn.Dispatcher._WaitForData)
- conn.Dispatcher._expected[_waitid] = None
- self.send(stanza, on_ok=on_ok)
-
- def SendAndCallForResponse(self, stanza, func=None, args=None):
- """
- Put stanza on the wire and call back when recipient replies. Additional
- callback arguments can be specified in args.
- """
- self.SendAndWaitForResponse(stanza, 0, func, args)
diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py
deleted file mode 100644
index ddacc0e37..000000000
--- a/src/common/zeroconf/connection_handlers_zeroconf.py
+++ /dev/null
@@ -1,114 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## Contributors for this file:
-## - Yann Leboulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <nkour@jabber.org>
-## - Dimitur Kirov <dkirov@gmail.com>
-## - Travis Shirk <travis@pobox.com>
-## - Stefan Bethge <stefan@lanpartei.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/>.
-##
-
-import nbxmpp
-
-from common import gajim
-from common.commands import ConnectionCommands
-from common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
-from common.connection_handlers_events import ZeroconfMessageReceivedEvent
-
-import logging
-log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
-
-STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible']
-# kind of events we can wait for an answer
-VCARD_PUBLISHED = 'vcard_published'
-VCARD_ARRIVED = 'vcard_arrived'
-AGENT_REMOVED = 'agent_removed'
-HAS_IDLE = True
-try:
- import idle
-except Exception:
- log.debug(_('Unable to load idle module'))
- HAS_IDLE = False
-
-from common import connection_handlers
-
-class ConnectionVcard(connection_handlers.ConnectionVcard):
- def add_sha(self, p, send_caps = True):
- return p
-
- def add_caps(self, p):
- return p
-
- def request_vcard(self, jid = None, is_fake_jid = False):
- pass
-
- def send_vcard(self, vcard):
- pass
-
-
-class ConnectionHandlersZeroconf(ConnectionVcard,
-ConnectionSocks5BytestreamZeroconf, ConnectionCommands,
-connection_handlers.ConnectionPEP, connection_handlers.ConnectionHandlersBase,
-connection_handlers.ConnectionJingle):
- def __init__(self):
- ConnectionVcard.__init__(self)
- ConnectionSocks5BytestreamZeroconf.__init__(self)
- ConnectionCommands.__init__(self)
- connection_handlers.ConnectionJingle.__init__(self)
- connection_handlers.ConnectionHandlersBase.__init__(self)
-
- try:
- idle.init()
- except Exception:
- global HAS_IDLE
- HAS_IDLE = False
-
- def _messageCB(self, ip, con, msg):
- """
- Called when we receive a message
- """
- log.debug('Zeroconf MessageCB')
- gajim.nec.push_incoming_event(ZeroconfMessageReceivedEvent(None,
- conn=self, stanza=msg, ip=ip))
- return
-
- def store_metacontacts(self, tags):
- """
- Fake empty method
- """
- # serverside metacontacts are not supported with zeroconf
- # (there is no server)
- pass
-
- def _DiscoverItemsGetCB(self, con, iq_obj):
- log.debug('DiscoverItemsGetCB')
-
- if not self.connection or self.connected < 2:
- return
-
- if self.commandItemsQuery(con, iq_obj):
- raise nbxmpp.NodeProcessed
- node = iq_obj.getTagAttr('query', 'node')
- if node is None:
- result = iq_obj.buildReply('result')
- self.connection.send(result)
- raise nbxmpp.NodeProcessed
- if node==nbxmpp.NS_COMMANDS:
- self.commandListQuery(con, iq_obj)
- raise nbxmpp.NodeProcessed
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
deleted file mode 100644
index 2446a5e37..000000000
--- a/src/common/zeroconf/connection_zeroconf.py
+++ /dev/null
@@ -1,384 +0,0 @@
-## common/zeroconf/connection_zeroconf.py
-##
-## Contributors for this file:
-## - Yann Leboulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <nkour@jabber.org>
-## - Dimitur Kirov <dkirov@gmail.com>
-## - Travis Shirk <travis@pobox.com>
-## - Stefan Bethge <stefan@lanpartei.de>
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix@lagaule.org>
-## Copyright (C) 2003-2004 Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2006 Nikos Kouremenos <nkour@jabber.org>
-## Dimitur Kirov <dkirov@gmail.com>
-## Travis Shirk <travis@pobox.com>
-## Norman Rasmussen <norman@rasmussen.co.za>
-## Stefan Bethge <stefan@lanpartei.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/>.
-##
-
-
-import os
-import socket
-import random
-random.seed()
-
-import signal
-if os.name != 'nt':
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-import getpass
-from gi.repository import GLib
-
-from common.connection import CommonConnection
-from common import gajim
-from common import ged
-from common.zeroconf import client_zeroconf
-from common.zeroconf import zeroconf
-from common.zeroconf.connection_handlers_zeroconf import *
-from common.connection_handlers_events import *
-
-class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
- def __init__(self, name):
- ConnectionHandlersZeroconf.__init__(self)
- # system username
- self.username = None
- self.server_resource = '' # zeroconf has no resource, fake an empty one
- self.call_resolve_timeout = False
- # we don't need a password, but must be non-empty
- self.password = 'zeroconf'
- self.autoconnect = False
-
- CommonConnection.__init__(self, name)
- self.is_zeroconf = True
-
- gajim.ged.register_event_handler('stanza-message-outgoing', ged.OUT_CORE,
- self._nec_stanza_message_outgoing)
-
- def get_config_values_or_default(self):
- """
- Get name, host, port from config, or create zeroconf account with default
- values
- """
- if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'):
- gajim.log.debug('Creating zeroconf account')
- gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'autoconnect', True)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for',
- '')
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password',
- 'zeroconf')
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'sync_with_global_status', True)
-
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port', 5298)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'is_zeroconf', True)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'use_ft_proxies', False)
- self.host = socket.gethostname()
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname',
- self.host)
- self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port')
- self.autoconnect = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'autoconnect')
- self.sync_with_global_status = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
- self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_first_name')
- self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_last_name')
- self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_jabber_id')
- self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_email')
-
- if not self.username:
- self.username = getpass.getuser()
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name',
- self.username)
- else:
- self.username = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'name')
- # END __init__
-
- def check_jid(self, jid):
- return jid
-
- def reconnect(self):
- # Do not try to reco while we are already trying
- self.time_to_reconnect = None
- gajim.log.debug('reconnect')
-
- self.disconnect()
- self.change_status(self.old_show, self.status)
-
- def disable_account(self):
- self.disconnect()
-
- def _on_resolve_timeout(self):
- if self.connected:
- self.connection.resolve_all()
- diffs = self.roster.getDiffs()
- for key in diffs:
- self.roster.setItem(key)
- gajim.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
- jid=key, nickname=self.roster.getName(key), sub='both',
- ask='no', groups=self.roster.getGroups(key)))
- gajim.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
- None, conn=self, fjid=key, show=self.roster.getStatus(key),
- status=self.roster.getMessage(key)))
- #XXX open chat windows don't get refreshed (full name), add that
- return self.call_resolve_timeout
-
- # callbacks called from zeroconf
- def _on_new_service(self, jid):
- self.roster.setItem(jid)
- gajim.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
- jid=jid, nickname=self.roster.getName(jid), sub='both',
- ask='no', groups=self.roster.getGroups(jid)))
- gajim.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
- None, conn=self, fjid=jid, show=self.roster.getStatus(jid),
- status=self.roster.getMessage(jid)))
-
- def _on_remove_service(self, jid):
- self.roster.delItem(jid)
- # 'NOTIFY' (account, (jid, status, status message, resource, priority,
- # keyID, timestamp, contact_nickname))
- gajim.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
- None, conn=self, fjid=jid, show='offline', status=''))
-
- def disconnectedReconnCB(self):
- """
- Called when we are disconnected. Comes from network manager for example
- we don't try to reconnect, network manager will tell us when we can
- """
- if gajim.account_is_connected(self.name):
- # we cannot change our status to offline or connecting
- # after we auth to server
- self.old_show = STATUS_LIST[self.connected]
- self.connected = 0
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- # random number to show we wait network manager to send us a reconenct
- self.time_to_reconnect = 5
- self.on_purpose = False
-
- def _on_name_conflictCB(self, alt_name):
- self.disconnect()
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- gajim.nec.push_incoming_event(ZeroconfNameConflictEvent(None, conn=self,
- alt_name=alt_name))
-
- def _on_error(self, message):
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Avahi error'), sec_txt=_('%s\nLink-local '
- 'messaging might not work properly.') % message))
-
- def connect(self, show='online', msg=''):
- self.get_config_values_or_default()
- if not self.connection:
- self.connection = client_zeroconf.ClientZeroconf(self)
- if not zeroconf.test_zeroconf():
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- gajim.nec.push_incoming_event(ConnectionLostEvent(None,
- conn=self, title=_('Could not connect to "%s"') % self.name,
- msg=_('Please check if Avahi or Bonjour is installed.')))
- self.disconnect()
- return
- result = self.connection.connect(show, msg)
- if not result:
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- if result is False:
- gajim.nec.push_incoming_event(ConnectionLostEvent(None,
- conn=self, title=_('Could not start local service'),
- msg=_('Unable to bind to port %d.' % self.port)))
- else: # result is None
- gajim.nec.push_incoming_event(ConnectionLostEvent(None,
- conn=self, title=_('Could not start local service'),
- msg=_('Please check if avahi-daemon is running.')))
- self.disconnect()
- return
- else:
- self.connection.announce()
- self.roster = self.connection.getRoster()
- gajim.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,
- xmpp_roster=self.roster))
-
- # display contacts already detected and resolved
- for jid in self.roster.keys():
- gajim.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
- jid=jid, nickname=self.roster.getName(jid), sub='both',
- ask='no', groups=self.roster.getGroups(jid)))
- gajim.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
- None, conn=self, fjid=jid, show=self.roster.getStatus(jid),
- status=self.roster.getMessage(jid)))
-
- self.connected = STATUS_LIST.index(show)
-
- # refresh all contacts data every five seconds
- self.call_resolve_timeout = True
- GLib.timeout_add_seconds(5, self._on_resolve_timeout)
- return True
-
- def disconnect(self, on_purpose=False):
- self.connected = 0
- self.time_to_reconnect = None
- if self.connection:
- self.connection.disconnect()
- self.connection = None
- # stop calling the timeout
- self.call_resolve_timeout = False
-
- def reannounce(self):
- if self.connected:
- txt = {}
- txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_first_name')
- txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_last_name')
- txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_jabber_id')
- txt['email'] = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
- self.connection.reannounce(txt)
-
- def update_details(self):
- if self.connection:
- port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port')
- if port != self.port:
- self.port = port
- last_msg = self.connection.last_msg
- self.disconnect()
- if not self.connect(self.status, last_msg):
- return
- if self.status != 'invisible':
- self.connection.announce()
- else:
- self.reannounce()
-
- def connect_and_init(self, show, msg, sign_msg):
- # to check for errors from zeroconf
- check = True
- if not self.connect(show, msg):
- return
- if show != 'invisible':
- check = self.connection.announce()
- else:
- self.connected = STATUS_LIST.index(show)
- gajim.nec.push_incoming_event(SignedInEvent(None, conn=self))
-
- # stay offline when zeroconf does something wrong
- if check:
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=show))
- else:
- # show notification that avahi or system bus is down
- self.connected = 0
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not change status of account "%s"') % self.name,
- msg=_('Please check if avahi-daemon is running.')))
-
- def _change_to_invisible(self, msg):
- if self.connection.remove_announce():
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='invisible'))
- else:
- # show notification that avahi or system bus is down
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not change status of account "%s"') % self.name,
- msg=_('Please check if avahi-daemon is running.')))
-
- def _change_from_invisible(self):
- self.connection.announce()
-
- def _update_status(self, show, msg):
- if self.connection.set_show_msg(show, msg):
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=show))
- else:
- # show notification that avahi or system bus is down
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not change status of account "%s"') % self.name,
- msg=_('Please check if avahi-daemon is running.')))
-
- def _nec_stanza_message_outgoing(self, obj):
-
- def on_send_ok(msg_id):
- gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
- jid=obj.jid, message=obj.message, keyID=obj.keyID,
- automatic_message=obj.automatic_message, chatstate=None,
- msg_id=msg_id))
- if obj.callback:
- obj.callback(obj.msg_iq, *obj.callback_args)
-
- self.log_message(obj, obj.jid)
-
- def on_send_not_ok(reason):
- reason += ' ' + _('Your message could not be sent.')
- gajim.nec.push_incoming_event(MessageErrorEvent(None, conn=self,
- fjid=obj.jid, error_code=-1, error_msg=reason, msg=None,
- time_=None, session=obj.session))
-
- ret = self.connection.send(
- obj.msg_iq, obj.message is not None,
- on_ok=on_send_ok, on_not_ok=on_send_not_ok)
-
- if ret == -1:
- # Contact Offline
- gajim.nec.push_incoming_event(MessageErrorEvent(None, conn=self,
- fjid=obj.jid, error_code=-1, error_msg=_(
- 'Contact is offline. Your message could not be sent.'),
- msg=None, time_=None, session=obj.session))
-
- def send_stanza(self, stanza):
- # send a stanza untouched
- if not self.connection:
- return
- if not isinstance(stanza, nbxmpp.Node):
- stanza = nbxmpp.Protocol(node=stanza)
- self.connection.send(stanza)
-
- def _event_dispatcher(self, realm, event, data):
- CommonConnection._event_dispatcher(self, realm, event, data)
- if realm == '':
- if event == nbxmpp.transports_nb.DATA_ERROR:
- thread_id = data[1]
- frm = data[0]
- session = self.get_or_create_session(frm, thread_id)
- gajim.nec.push_incoming_event(MessageErrorEvent(
- None, conn=self, fjid=frm, error_code=-1, error_msg=_(
- 'Connection to host could not be established: Timeout while '
- 'sending data.'), msg=None, time_=None, session=session))
-
-# END ConnectionZeroconf
diff --git a/src/common/zeroconf/roster_zeroconf.py b/src/common/zeroconf/roster_zeroconf.py
deleted file mode 100644
index eeb5c8991..000000000
--- a/src/common/zeroconf/roster_zeroconf.py
+++ /dev/null
@@ -1,159 +0,0 @@
-## common/zeroconf/roster_zeroconf.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.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/>.
-##
-
-
-from common.zeroconf import zeroconf
-from common.zeroconf.zeroconf import Constant, ConstantRI
-
-class Roster:
- def __init__(self, zeroconf):
- self._data = None
- self.zeroconf = zeroconf # our zeroconf instance
- self.version = ''
- self.received_from_server = True
-
- def update_roster(self):
- for val in self.zeroconf.contacts.values():
- self.setItem(val[Constant.NAME])
-
- def getRoster(self):
- if self._data is None:
- self._data = {}
- self.update_roster()
- return self
-
- def getDiffs(self):
- """
- Update the roster with new data and return dict with jid -> new status
- pairs to do notifications and stuff
- """
- diffs = {}
- old_data = self._data.copy()
- self.update_roster()
- for key in old_data.keys():
- if key in self._data:
- if old_data[key] != self._data[key]:
- diffs[key] = self._data[key]['status']
- return diffs
-
- def setItem(self, jid, name='', groups=''):
- contact = self.zeroconf.get_contact(jid)
- if not contact:
- return
-
- addresses = []
- i = 0
- for ri in contact[Constant.RESOLVED_INFO]:
- addresses += [{}]
- addresses[i]['host'] = ri[ConstantRI.HOST]
- addresses[i]['address'] = ri[ConstantRI.ADDRESS]
- addresses[i]['port'] = ri[ConstantRI.PORT]
- i += 1
- txt = contact[Constant.TXT]
-
- self._data[jid]={}
- self._data[jid]['ask'] = 'none'
- self._data[jid]['subscription'] = 'both'
- self._data[jid]['groups'] = []
- self._data[jid]['resources'] = {}
- self._data[jid]['addresses'] = addresses
- txt_dict = self.zeroconf.txt_array_to_dict(txt)
- status = txt_dict.get('status', '')
- if not status:
- status = 'avail'
- nm = txt_dict.get('1st', '')
- if 'last' in txt_dict:
- if nm != '':
- nm += ' '
- nm += txt_dict['last']
- if nm:
- self._data[jid]['name'] = nm
- else:
- self._data[jid]['name'] = jid
- if status == 'avail':
- status = 'online'
- self._data[jid]['txt_dict'] = txt_dict
- if 'msg' not in self._data[jid]['txt_dict']:
- self._data[jid]['txt_dict']['msg'] = ''
- self._data[jid]['status'] = status
- self._data[jid]['show'] = status
-
- def setItemMulti(self, items):
- for i in items:
- self.setItem(jid=i['jid'], name=i['name'], groups=i['groups'])
-
- def delItem(self, jid):
- if jid in self._data:
- del self._data[jid]
-
- def getItem(self, jid):
- if jid in self._data:
- return self._data[jid]
-
- def __getitem__(self, jid):
- return self._data[jid]
-
- def getItems(self):
- # Return list of all [bare] JIDs that the roster currently tracks.
- return self._data.keys()
-
- def keys(self):
- return self._data.keys()
-
- def getRaw(self):
- return self._data
-
- def getResources(self, jid):
- return {}
-
- def getGroups(self, jid):
- return self._data[jid]['groups']
-
- def getName(self, jid):
- if jid in self._data:
- return self._data[jid]['name']
-
- def getStatus(self, jid):
- if jid in self._data:
- return self._data[jid]['status']
-
- def getMessage(self, jid):
- if jid in self._data:
- return self._data[jid]['txt_dict']['msg']
-
- def getShow(self, jid):
- return self.getStatus(jid)
-
- def getPriority(self, jid):
- return 5
-
- def getSubscription(self, jid):
- return 'both'
-
- def Subscribe(self, jid):
- pass
-
- def Unsubscribe(self, jid):
- pass
-
- def Authorize(self, jid):
- pass
-
- def Unauthorize(self, jid):
- pass
diff --git a/src/common/zeroconf/zeroconf.py b/src/common/zeroconf/zeroconf.py
deleted file mode 100644
index 58bba6d25..000000000
--- a/src/common/zeroconf/zeroconf.py
+++ /dev/null
@@ -1,63 +0,0 @@
-## common/zeroconf/zeroconf.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.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/>.
-##
-
-from enum import IntEnum, unique
-
-@unique
-class Constant(IntEnum):
- NAME = 0
- DOMAIN = 1
- RESOLVED_INFO = 2
- BARE_NAME = 3
- TXT = 4
-
-@unique
-class ConstantRI(IntEnum):
- INTERFACE = 0
- PROTOCOL = 1
- HOST = 2
- APROTOCOL = 3
- ADDRESS = 4
- PORT = 5
-
-def test_avahi():
- try:
- import avahi
- except ImportError:
- return False
- return True
-
-def test_bonjour():
- try:
- import pybonjour
- except ImportError:
- return False
- except WindowsError:
- return False
- return True
-
-def test_zeroconf():
- return test_avahi() or test_bonjour()
-
-if test_avahi():
- from common.zeroconf import zeroconf_avahi
- Zeroconf = zeroconf_avahi.Zeroconf
-elif test_bonjour():
- from common.zeroconf import zeroconf_bonjour
- Zeroconf = zeroconf_bonjour.Zeroconf
diff --git a/src/common/zeroconf/zeroconf_avahi.py b/src/common/zeroconf/zeroconf_avahi.py
deleted file mode 100644
index 51bad907e..000000000
--- a/src/common/zeroconf/zeroconf_avahi.py
+++ /dev/null
@@ -1,482 +0,0 @@
-## common/zeroconf/zeroconf.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.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/>.
-##
-
-import logging
-log = logging.getLogger('gajim.c.z.zeroconf_avahi')
-
-try:
- import dbus.exceptions
-except ImportError:
- pass
-
-from common.zeroconf.zeroconf import Constant, ConstantRI
-
-class Zeroconf:
- def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
- disconnected_CB, error_CB, name, host, port):
- self.avahi = None
- self.domain = None # specific domain to browse
- self.stype = '_presence._tcp'
- self.port = port # listening port that gets announced
- self.username = name
- self.host = host
- self.txt = {} # service data
-
- #XXX these CBs should be set to None when we destroy the object
- # (go offline), because they create a circular reference
- self.new_serviceCB = new_serviceCB
- self.remove_serviceCB = remove_serviceCB
- self.name_conflictCB = name_conflictCB
- self.disconnected_CB = disconnected_CB
- self.error_CB = error_CB
-
- self.service_browser = None
- self.domain_browser = None
- self.bus = None
- self.server = None
- self.contacts = {} # all current local contacts with data
- self.entrygroup = None
- self.connected = False
- self.announced = False
- self.invalid_self_contact = {}
-
-
- ## handlers for dbus callbacks
- def entrygroup_commit_error_CB(self, err):
- # left blank for possible later usage
- pass
-
- def error_callback1(self, err):
- log.debug('Error while resolving: ' + str(err))
-
- def error_callback(self, err):
- log.debug(str(err))
- # timeouts are non-critical
- if str(err) != 'Timeout reached':
- self.disconnect()
- self.disconnected_CB()
-
- def new_service_callback(self, interface, protocol, name, stype, domain,
- flags):
- log.debug('Found service %s in domain %s on %i.%i.' % (name, domain,
- interface, protocol))
- if not self.connected:
- return
-
- # synchronous resolving
- self.server.ResolveService( int(interface), int(protocol), name, stype,
- domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0),
- reply_handler=self.service_resolved_callback,
- error_handler=self.error_callback1)
-
- def remove_service_callback(self, interface, protocol, name, stype, domain,
- flags):
- log.debug('Service %s in domain %s on %i.%i disappeared.' % (name,
- domain, interface, protocol))
- if not self.connected:
- return
- if name != self.name:
- for key in self.contacts.keys():
- val = self.contacts[key]
- if val[Constant.BARE_NAME] == name:
- # try to reduce instead of delete first
- resolved_info = val[Constant.RESOLVED_INFO]
- if len(resolved_info) > 1:
- for i in range(len(resolved_info)):
- if resolved_info[i][ConstantRI.INTERFACE] == interface and resolved_info[i][ConstantRI.PROTOCOL] == protocol:
- del self.contacts[key][Constant.RESOLVED_INFO][i]
- # if still something left, don't remove
- if len(self.contacts[key][Constant.RESOLVED_INFO]) > 1: return
- del self.contacts[key]
- self.remove_serviceCB(key)
- return
-
- def new_service_type(self, interface, protocol, stype, domain, flags):
- # Are we already browsing this domain for this type?
- if self.service_browser:
- return
-
- object_path = self.server.ServiceBrowserNew(interface, protocol, \
- stype, domain, dbus.UInt32(0))
-
- self.service_browser = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, object_path),
- self.avahi.DBUS_INTERFACE_SERVICE_BROWSER)
- self.service_browser.connect_to_signal('ItemNew',
- self.new_service_callback)
- self.service_browser.connect_to_signal('ItemRemove',
- self.remove_service_callback)
- self.service_browser.connect_to_signal('Failure', self.error_callback)
-
- def new_domain_callback(self, interface, protocol, domain, flags):
- if domain != 'local':
- self.browse_domain(interface, protocol, domain)
-
- def txt_array_to_dict(self, txt_array):
- txt_dict = {}
- for els in txt_array:
- key, val = '', None
- for c in els:
- c = chr(c)
- if val is None:
- if c == '=':
- val = ''
- else:
- key += c
- else:
- val += c
- if val is None: # missing '='
- val = ''
- txt_dict[key] = val
- return txt_dict
-
- def service_resolved_callback(self, interface, protocol, name, stype, domain,
- host, aprotocol, address, port, txt, flags):
- log.debug('Service data for service %s in domain %s on %i.%i:'
- % (name, domain, interface, protocol))
- log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address,
- port, self.txt_array_to_dict(txt)))
- if not self.connected:
- return
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- resolved_info = [(interface, protocol, host, aprotocol, address, port)]
- if name in self.contacts:
- # Decide whether to try to merge with existing resolved info:
- old_name, old_domain, old_resolved_info, old_bare_name, old_txt = self.contacts[name]
- if name == old_name and domain == old_domain and bare_name == old_bare_name:
- # Seems similar enough, try to merge resolved info:
- for i in range(len(old_resolved_info)):
- # for now, keep a single record for each (interface, protocol) pair
- #
- # Note that, theoretically, we could both get IPv4 and
- # IPv6 aprotocol responses via the same protocol,
- # so this probably needs to be revised again.
- if old_resolved_info[i][0:2] == (interface, protocol):
- log.debug('Deleting resolved info for interface %i, protocol %i, host %s, aprotocol %i, address %s, port %i' % old_resolved_info[i])
- del old_resolved_info[i]
- break
- resolved_info = resolved_info + old_resolved_info
- log.debug('Collected resolved info is now: %s' % (resolved_info,))
- self.contacts[name] = (name, domain, resolved_info, bare_name, txt)
- self.new_serviceCB(name)
- else:
- # remember data
- # In case this is not our own record but of another
- # gajim instance on the same machine,
- # it will be used when we get a new name.
- self.invalid_self_contact[name] = (name, domain,
- (interface, protocol, host, aprotocol, address, port),
- bare_name, txt)
-
-
- # different handler when resolving all contacts
- def service_resolved_all_callback(self, interface, protocol, name, stype,
- domain, host, aprotocol, address, port, txt, flags):
- if not self.connected:
- return
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
- # update TXT data only, as intended according to resolve_all comment
- old_contact = self.contacts[name]
- self.contacts[name] = old_contact[0:Constant.TXT] + (txt,) + old_contact[Constant.TXT+1:]
-
- def service_added_callback(self):
- log.debug('Service successfully added')
-
- def service_committed_callback(self):
- log.debug('Service successfully committed')
-
- def service_updated_callback(self):
- log.debug('Service successfully updated')
-
- def service_add_fail_callback(self, err):
- log.debug('Error while adding service. %s' % str(err))
- if 'Local name collision' in str(err):
- alternative_name = self.server.GetAlternativeServiceName(self.username)
- self.name_conflictCB(alternative_name)
- return
- self.error_CB(_('Error while adding service. %s') % str(err))
- self.disconnect()
-
- def server_state_changed_callback(self, state, error):
- log.debug('server state changed to %s' % state)
- if state == self.avahi.SERVER_RUNNING:
- self.create_service()
- elif state in (self.avahi.SERVER_COLLISION,
- self.avahi.SERVER_REGISTERING):
- self.disconnect()
- self.entrygroup.Reset()
- elif state == self.avahi.CLIENT_FAILURE:
- # does it ever go here?
- log.debug('CLIENT FAILURE')
-
- def entrygroup_state_changed_callback(self, state, error):
- # the name is already present, so recreate
- if state == self.avahi.ENTRY_GROUP_COLLISION:
- log.debug('zeroconf.py: local name collision')
- self.service_add_fail_callback('Local name collision')
- elif state == self.avahi.ENTRY_GROUP_FAILURE:
- self.disconnect()
- self.entrygroup.Reset()
- log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that'
- ' should not happen)')
-
- # make zeroconf-valid names
- def replace_show(self, show):
- if show in ['chat', 'online', '']:
- return 'avail'
- elif show == 'xa':
- return 'away'
- return show
-
- def avahi_txt(self):
- return self.avahi.dict_to_txt_array(self.txt)
-
- def create_service(self):
- try:
- if not self.entrygroup:
- # create an EntryGroup for publishing
- self.entrygroup = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, self.server.EntryGroupNew()),
- self.avahi.DBUS_INTERFACE_ENTRY_GROUP)
- self.entrygroup.connect_to_signal('StateChanged',
- self.entrygroup_state_changed_callback)
-
- txt = {}
-
- # remove empty keys
- for key, val in self.txt.items():
- if val:
- txt[key] = val
-
- txt['port.p2pj'] = self.port
- txt['version'] = 1
- txt['txtvers'] = 1
-
- # replace gajim's show messages with compatible ones
- if 'status' in self.txt:
- txt['status'] = self.replace_show(self.txt['status'])
- else:
- txt['status'] = 'avail'
-
- self.txt = txt
- log.debug('Publishing service %s of type %s' % (self.name,
- self.stype))
- self.entrygroup.AddService(self.avahi.IF_UNSPEC,
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '',
- '', dbus.UInt16(self.port), self.avahi_txt(),
- reply_handler=self.service_added_callback,
- error_handler=self.service_add_fail_callback)
-
- self.entrygroup.Commit(reply_handler=self.service_committed_callback,
- error_handler=self.entrygroup_commit_error_CB)
-
- return True
-
- except dbus.DBusException as e:
- log.debug(str(e))
- return False
-
- def announce(self):
- if not self.connected:
- return False
-
- state = self.server.GetState()
- if state == self.avahi.SERVER_RUNNING:
- if self.create_service():
- self.announced = True
- return True
- return False
-
- def remove_announce(self):
- if self.announced == False:
- return False
- try:
- if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE:
- self.entrygroup.Reset()
- self.entrygroup.Free()
- # .Free() has mem leaks
- self.entrygroup._obj._bus = None
- self.entrygroup._obj = None
- self.entrygroup = None
- self.announced = False
-
- return True
- else:
- return False
- except dbus.DBusException:
- log.debug("Can't remove service. That should not happen")
-
- def browse_domain(self, interface, protocol, domain):
- self.new_service_type(interface, protocol, self.stype, domain, '')
-
- def avahi_dbus_connect_cb(self, a, connect, disconnect):
- if connect != "":
- log.debug('Lost connection to avahi-daemon')
- self.disconnect()
- if self.disconnected_CB:
- self.disconnected_CB()
- else:
- log.debug('We are connected to avahi-daemon')
-
- # connect to dbus
- def connect_dbus(self):
- try:
- import dbus
- except ImportError:
- log.debug('Error: python-dbus needs to be installed. No '
- 'zeroconf support.')
- return False
- if self.bus:
- return True
- try:
- self.bus = dbus.SystemBus()
- self.bus.add_signal_receiver(self.avahi_dbus_connect_cb,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='org.freedesktop.Avahi')
- except Exception as e:
- # System bus is not present
- self.bus = None
- log.debug(str(e))
- return False
- else:
- return True
-
- # connect to avahi
- def connect_avahi(self):
- if not self.connect_dbus():
- return False
- try:
- import avahi
- self.avahi = avahi
- except ImportError:
- log.debug('Error: python-avahi needs to be installed. No '
- 'zeroconf support.')
- return False
-
- if self.server:
- return True
- try:
- self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME,
- self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER)
- self.server.connect_to_signal('StateChanged',
- self.server_state_changed_callback)
- except Exception as e:
- # Avahi service is not present
- self.server = None
- log.debug(str(e))
- return False
- else:
- return True
-
- def connect(self):
- self.name = self.username + '@' + self.host # service name
- if not self.connect_avahi():
- return False
-
- self.connected = True
- # start browsing
- if self.domain is None:
- # Explicitly browse .local
- self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC,
- 'local')
-
- # Browse for other browsable domains
- self.domain_browser = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, self.server.DomainBrowserNew(
- self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, '',
- self.avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))),
- self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
- self.domain_browser.connect_to_signal('ItemNew',
- self.new_domain_callback)
- self.domain_browser.connect_to_signal('Failure', self.error_callback)
- else:
- self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC,
- self.domain)
-
- return True
-
- def disconnect(self):
- if self.connected:
- self.connected = False
- if self.service_browser:
- try:
- self.service_browser.Free()
- except dbus.DBusException as e:
- log.debug(str(e))
- self.service_browser._obj._bus = None
- self.service_browser._obj = None
- if self.domain_browser:
- try:
- self.domain_browser.Free()
- except dbus.DBusException as e:
- log.debug(str(e))
- self.domain_browser._obj._bus = None
- self.domain_browser._obj = None
- self.remove_announce()
- self.server._obj._bus = None
- self.server._obj = None
- self.server = None
- self.service_browser = None
- self.domain_browser = None
-
- # refresh txt data of all contacts manually (no callback available)
- def resolve_all(self):
- if not self.connected:
- return
- for val in self.contacts.values():
- # get txt data from last recorded resolved info
- # TODO: Better try to get it from last IPv6 mDNS, then last IPv4?
- ri = val[Constant.RESOLVED_INFO][0]
- self.server.ResolveService(int(ri[ConstantRI.INTERFACE]), int(ri[ConstantRI.PROTOCOL]),
- val[Constant.BARE_NAME], self.stype, val[Constant.DOMAIN],
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0),
- reply_handler=self.service_resolved_all_callback,
- error_handler=self.error_callback)
-
- def get_contacts(self):
- return self.contacts
-
- def get_contact(self, jid):
- if not jid in self.contacts:
- return None
- return self.contacts[jid]
-
- def update_txt(self, show = None):
- if show:
- self.txt['status'] = self.replace_show(show)
-
- txt = self.avahi_txt()
- if self.connected and self.entrygroup:
- self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC,
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '',
- txt, reply_handler=self.service_updated_callback,
- error_handler=self.error_callback)
- return True
- else:
- return False
-
-
-# END Zeroconf
diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py
deleted file mode 100644
index f68d412dd..000000000
--- a/src/common/zeroconf/zeroconf_bonjour.py
+++ /dev/null
@@ -1,335 +0,0 @@
-## common/zeroconf/zeroconf_bonjour.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.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/>.
-##
-
-from common import gajim
-import select
-import re
-from common.zeroconf.zeroconf import Constant
-
-try:
- import pybonjour
-except ImportError:
- pass
-
-
-resolve_timeout = 1
-
-class Zeroconf:
- def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
- disconnected_CB, error_CB, name, host, port):
- self.domain = None # specific domain to browse
- self.stype = '_presence._tcp'
- self.port = port # listening port that gets announced
- self.username = name
- self.host = host
- self.txt = pybonjour.TXTRecord() # service data
-
- # XXX these CBs should be set to None when we destroy the object
- # (go offline), because they create a circular reference
- self.new_serviceCB = new_serviceCB
- self.remove_serviceCB = remove_serviceCB
- self.name_conflictCB = name_conflictCB
- self.disconnected_CB = disconnected_CB
- self.error_CB = error_CB
-
- self.contacts = {} # all current local contacts with data
- self.connected = False
- self.announced = False
- self.invalid_self_contact = {}
- self.resolved = []
-
-
- def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain):
- gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype))
- if not self.connected:
- return
- if errorCode != pybonjour.kDNSServiceErr_NoError:
- return
- if not (flags & pybonjour.kDNSServiceFlagsAdd):
- self.remove_service_callback(serviceName)
- return
-
- # asynchronous resolving
- resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback)
-
- try:
- while not self.resolved:
- ready = select.select([resolve_sdRef], [], [], resolve_timeout)
- if resolve_sdRef not in ready[0]:
- gajim.log.debug('Resolve timed out')
- break
- pybonjour.DNSServiceProcessResult(resolve_sdRef)
- else:
- self.resolved.pop()
- finally:
- resolve_sdRef.close()
-
- def remove_service_callback(self, name):
- gajim.log.debug('Service %s disappeared.' % name)
- if not self.connected:
- return
- if name != self.name:
- for key in self.contacts.keys():
- if self.contacts[key][Constant.BARE_NAME] == name:
- del self.contacts[key]
- self.remove_serviceCB(key)
- return
-
- def new_domain_callback(self, interface, protocol, domain, flags):
- if domain != "local":
- self.browse_domain(interface, protocol, domain)
-
- # takes a TXTRecord instance
- def txt_array_to_dict(self, txt):
- items = pybonjour.TXTRecord.parse(txt)._items
- return dict((v[0], v[1]) for v in items.values())
-
- def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname,
- hosttarget, port, txtRecord):
-
- # TODO: do proper decoding...
- escaping= {
- r'\.': '.',
- r'\032': ' ',
- r'\064': '@',
- }
-
- # Split on '.' but do not split on '\.'
- result = re.split('(?<!\\\\)\.', fullname)
- name = result[0]
- protocol, domain = result[2:4]
-
- # Replace the escaped values
- for src, trg in escaping.items():
- name = name.replace(src, trg)
-
- txt = pybonjour.TXTRecord.parse(txtRecord)
-
- gajim.log.debug('Service data for service %s on %i:' % (fullname, interfaceIndex))
- gajim.log.debug('Host %s, port %i, TXT data: %s' % (hosttarget, port, txt._items))
-
- if not self.connected:
- return
-
- bare_name = name
- if '@' not in name:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- resolved_info = [(interfaceIndex, protocol, hosttarget, -1, port)]
- self.contacts[name] = (name, domain, resolved_info, bare_name, txtRecord)
-
- self.new_serviceCB(name)
- else:
- # remember data
- # In case this is not our own record but of another
- # gajim instance on the same machine,
- # it will be used when we get a new name.
- self.invalid_self_contact[name] = (name, domain, (interfaceIndex, protocol, hosttarget, -1, port), bare_name, txtRecord)
- # count services
- self.resolved.append(True)
-
- # different handler when resolving all contacts
- def service_resolved_all_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord):
- if not self.connected:
- return
-
- escaping= {
- r'\.': '.',
- r'\032': ' ',
- r'\064': '@',
- }
-
- name, stype, protocol, domain, dummy = fullname.split('.')
-
- # Replace the escaped values
- for src, trg in escaping.items():
- name = name.replace(src, trg)
-
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- # update TXT data only, as intended according to resolve_all comment
- old_contact = self.contacts[name]
- self.contacts[name] = old_contact[0:Constant.TXT] + (self.txt,) + old_contact[Constant.TXT+1:]
-
-
- def service_added_callback(self, sdRef, flags, errorCode, name, regtype, domain):
- if errorCode == pybonjour.kDNSServiceErr_NoError:
- gajim.log.debug('Service successfully added')
-
- def service_add_fail_callback(self, err):
- if err[0][0] == pybonjour.kDNSServiceErr_NameConflict:
- gajim.log.debug('Error while adding service. %s' % str(err))
- parts = self.username.split(' ')
-
- #check if last part is a number and if, increment it
- try:
- stripped = str(int(parts[-1]))
- except Exception:
- stripped = 1
- alternative_name = self.username + str(stripped+1)
- self.name_conflictCB(alternative_name)
- return
- self.error_CB(_('Error while adding service. %s') % str(err))
- self.disconnect()
-
- # make zeroconf-valid names
- def replace_show(self, show):
- if show in ['chat', 'online', '']:
- return 'avail'
- elif show == 'xa':
- return 'away'
- return show
-
- def create_service(self):
- txt = {}
-
- #remove empty keys
- for key, val in self.txt:
- if val:
- txt[key] = val
-
- txt['port.p2pj'] = self.port
- txt['version'] = 1
- txt['txtvers'] = 1
-
- # replace gajim's show messages with compatible ones
- if 'status' in self.txt:
- txt['status'] = self.replace_show(self.txt['status'])
- else:
- txt['status'] = 'avail'
-
- self.txt = pybonjour.TXTRecord(txt, strict=True)
-
- try:
- sdRef = pybonjour.DNSServiceRegister(name = self.name,
- regtype = self.stype, port = self.port, txtRecord = self.txt,
- callBack = self.service_added_callback)
- self.service_sdRef = sdRef
- except pybonjour.BonjourError as e:
- self.service_add_fail_callback(e)
- else:
- gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype))
-
- ready = select.select([sdRef], [], [], resolve_timeout)
- if sdRef in ready[0]:
- pybonjour.DNSServiceProcessResult(sdRef)
-
- def announce(self):
- if not self.connected:
- return False
-
- self.create_service()
- self.announced = True
- return True
-
- def remove_announce(self):
- if not self.announced:
- return False
- try:
- self.service_sdRef.close()
- self.announced = False
- return True
- except pybonjour.BonjourError as e:
- gajim.log.debug(e)
- return False
-
-
- def connect(self):
- self.name = self.username + '@' + self.host # service name
-
- self.connected = True
-
- # start browsing
- if self.domain is None:
- # Explicitly browse .local
- self.browse_domain()
-
- # Browse for other browsable domains
- #self.domain_sdRef = pybonjour.DNSServiceEnumerateDomains(flags, interfaceIndex=0, callBack=self.new_domain_callback)
-
- else:
- self.browse_domain(self.domain)
-
- return True
-
- def disconnect(self):
- if self.connected:
- self.connected = False
- if hasattr(self, 'browse_sdRef'):
- self.browse_sdRef.close()
- self.remove_announce()
-
- def browse_domain(self, domain=None):
- gajim.log.debug('starting to browse')
- try:
- self.browse_sdRef = pybonjour.DNSServiceBrowse(regtype=self.stype, domain=domain, callBack=self.browse_callback)
- except pybonjour.BonjourError as e:
- self.error_CB("Error while browsing: %s" % str(e))
-
- def browse_loop(self):
- ready = select.select([self.browse_sdRef], [], [], 0)
- if self.browse_sdRef in ready[0]:
- pybonjour.DNSServiceProcessResult(self.browse_sdRef)
-
- # refresh txt data of all contacts manually (no callback available)
- def resolve_all(self):
- if not self.connected:
- return
-
- # for now put here as this is synchronous
- self.browse_loop()
-
- for val in self.contacts.values():
- resolve_sdRef = pybonjour.DNSServiceResolve(0,
- pybonjour.kDNSServiceInterfaceIndexAny, val[Constant.BARE_NAME],
- self.stype + '.', val[Constant.DOMAIN] + '.',
- self.service_resolved_all_callback)
-
- try:
- ready = select.select([resolve_sdRef], [], [], resolve_timeout)
- if resolve_sdRef not in ready[0]:
- gajim.log.debug('Resolve timed out (in resolve_all)')
- break
- pybonjour.DNSServiceProcessResult(resolve_sdRef)
- finally:
- resolve_sdRef.close()
-
- def get_contacts(self):
- return self.contacts
-
- def get_contact(self, jid):
- if not jid in self.contacts:
- return None
- return self.contacts[jid]
-
- def update_txt(self, show = None):
- if show:
- self.txt['status'] = self.replace_show(show)
-
- try:
- pybonjour.DNSServiceUpdateRecord(self.service_sdRef, None, 0, self.txt)
- except pybonjour.BonjourError:
- return False
- return True