diff options
author | mrDoctorWho <mrdoctorwho@gmail.com> | 2014-01-15 18:44:28 +0400 |
---|---|---|
committer | mrDoctorWho <mrdoctorwho@gmail.com> | 2014-01-15 18:44:28 +0400 |
commit | fc84166a006edd62f5d0267bf5e4622816b9bfb5 (patch) | |
tree | 8e1140984d5623dce5fb2361c34b9849d6404650 | |
parent | e69216f422ac4828d5193aebc5bbf1914cefe728 (diff) |
Fixed #44, added option SLICE_STEP into config, added handling errno 101 (when vk.com is down), xmpppy fixes
-rw-r--r-- | Config_example.txt | 3 | ||||
-rw-r--r-- | gateway.py | 44 | ||||
-rw-r--r-- | handlers/Presence.py | 46 | ||||
-rw-r--r-- | library/vkApi.py | 23 | ||||
-rw-r--r-- | library/webtools.py | 4 | ||||
-rw-r--r-- | library/xmpp/auth.py | 25 | ||||
-rw-r--r-- | library/xmpp/browser.py | 2 | ||||
-rw-r--r-- | library/xmpp/commands.py | 34 | ||||
-rw-r--r-- | library/xmpp/debug.py | 4 | ||||
-rw-r--r-- | library/xmpp/dispatcher.py | 10 | ||||
-rw-r--r-- | library/xmpp/protocol.py | 49 | ||||
-rw-r--r-- | library/xmpp/simplexml.py | 48 | ||||
-rw-r--r-- | library/xmpp/transports.py | 97 | ||||
-rw-r--r-- | locales/locale.pl | 2 | ||||
-rw-r--r-- | locales/locale.ru | 2 |
15 files changed, 226 insertions, 167 deletions
diff --git a/Config_example.txt b/Config_example.txt index 5a4166e..75f2574 100644 --- a/Config_example.txt +++ b/Config_example.txt @@ -69,6 +69,9 @@ ROSTER_UPDATE_TIMEOUT = 4 ## Maximum forwarded messages depth. MAXIMUM_FORWARD_DEPTH = 5 +## Users per thread. +SLICE_STEP = 8 + ## Image that will be used if transport can't recieve image from VK. URL_VCARD_NO_IMAGE = "http://simpleapps.ru/vk4xmpp.png" @@ -168,6 +168,8 @@ class VKLogin(object): self.jidFrom = jidFrom logger.debug("VKLogin.__init__ with number:%s from jid:%s" % (number, jidFrom)) + getToken = lambda self: self.engine.token + def auth(self, token = None): logger.debug("VKLogin.auth %s token" % ("with" if token else "without")) try: @@ -229,6 +231,9 @@ class VKLogin(object): msgSend(Component, self.jidFrom, _(e.message + " Please, register again"), TransportID) self.Online = False logger.error("VKLogin: apiError %s for user %s" % (e.message, self.jidFrom)) + except api.NetworkNotFound: + logger.critical("VKLogin: network unavailable. Is vk down?") + self.Online = False return result def captchaChallenge(self): @@ -273,9 +278,6 @@ class VKLogin(object): self.method("account.setOffline") self.Online = False - def getToken(self): - return self.engine.token - def getFriends(self, fields = None): fields = fields or ["screen_name"] friendsRaw = self.method("friends.get", {"fields": ",".join(fields)}) or {} # friends.getOnline @@ -316,7 +318,6 @@ class tUser(object): self.lastMsgID = None self.rosterSet = None self.existsInDB = None - self.lastStatus = None self.last_activity = time.time() self.last_udate = time.time() self.jidFrom = source @@ -336,6 +337,11 @@ class tUser(object): logger.debug("tUser: %s exists in db. Will be deleted." % self.jidFrom) threadRun(self.deleteUser) + def __eq__(self, user): + if isinstance(user, tUser): + return user.jidFrom == self.jidFrom + return self.jidFrom == user + def deleteUser(self, roster = False): logger.debug("tUser: deleting user %s from db." % self.jidFrom) with Database(DatabaseFile) as db: @@ -351,14 +357,14 @@ class tUser(object): self.vk.Online = False if self.jidFrom in Transport: del Transport[self.jidFrom] - try: - updateTransportsList(self, False) - except NameError: - pass + try: + updateTransportsList(self, False) + except NameError: + pass def msg(self, body, uID, mType = "user_id"): + self.last_activity = time.time() try: - self.last_activity = time.time() Message = self.vk.method("messages.send", {mType: uID, "message": body, "type": 0}) except: crashLog("messages.send") @@ -396,10 +402,10 @@ class tUser(object): with Database(DatabaseFile, Semaphore) as db: db("update users set token=? where jid=?", (self.vk.getToken(), self.jidFrom)) try: - _ = self.vk.method("users.get") - self.UserID = _[0]["uid"] + json = self.vk.method("users.get") + self.UserID = json[0]["uid"] except (KeyError, TypeError): - logger.error("tUser: could not recieve user id. JSON: %s" % str(_)) + logger.error("tUser: could not recieve user id. JSON: %s" % str(json)) self.UserID = 0 jidToID[self.UserID] = self.jidFrom @@ -504,7 +510,7 @@ class tUser(object): except: crashLog("tryAgain") -msgSort = lambda Br, Ba: Br["date"] - Ba["date"] +msgSort = lambda msgOne, msgTwo: msgOne["date"] - msgTwo["date"] def Sender(cl, stanza): try: @@ -543,17 +549,21 @@ def vk2xmpp(id): id = u"%s@%s" % (id, TransportID) return id -DESC = _("© simpleApps, 2013." +DESC = _("© simpleApps, 2013 — 2014." "\nYou can support developing of any project" " via donation by WebMoney:" "\nZ405564701378 | R330257574689.") def updateTransportsList(user, add=True): global lengthOfTransportsList - if add and user not in TransportsList: - TransportsList.append(user) - elif user in TransportsList: + if user in TransportsList: + if add: + return TransportsList.remove(user) + elif add: + TransportsList.append(user) + else: + return length = len(TransportsList) if length > lengthOfTransportsList: start = lengthOfTransportsList diff --git a/handlers/Presence.py b/handlers/Presence.py index 58a7eea..74b9d67 100644 --- a/handlers/Presence.py +++ b/handlers/Presence.py @@ -9,33 +9,30 @@ def prsHandler(cl, prs): jidFromStr = jidFrom.getStripped() jidToStr = jidTo.getStripped() if jidFromStr in Transport: - Class = Transport[jidFromStr] + user = Transport[jidFromStr] Resource = jidFrom.getResource() if pType in ("available", "probe", None): - if jidTo == TransportID and Resource not in Class.resources: + if jidTo == TransportID and Resource not in user.resources: logger.debug("%s from user %s, will send sendInitPresence" % (pType, jidFromStr)) - Class.resources.append(Resource) - if Class.lastStatus == "unavailable" and len(Class.resources) == 1: - if not Class.vk.Online: - Class.vk.Online = True - Class.sendInitPresence() + user.resources.append(Resource) + user.sendInitPresence() elif pType == "unavailable": - if jidTo == TransportID and Resource in Class.resources: - Class.resources.remove(Resource) - if Class.resources: - Class.sendOutPresence(jidFrom) - if not Class.resources: + if jidTo == TransportID and Resource in user.resources: + user.resources.remove(Resource) + if user.resources: + user.sendOutPresence(jidFrom) + if not user.resources: Sender(cl, xmpp.Presence(jidFrom, "unavailable", frm = TransportID)) - Class.vk.disconnect() + user.vk.disconnect() if jidFromStr in Transport: del Transport[jidFromStr] - updateTransportsList(jidFromStr, False) + updateTransportsList(user, False) elif pType == "error": eCode = prs.getErrorCode() if eCode == "404": - Class.vk.disconnect() + user.vk.disconnect() elif pType == "subscribe": if jidToStr == TransportID: @@ -43,27 +40,26 @@ def prsHandler(cl, prs): Sender(cl, xmpp.Presence(jidFrom, frm = TransportID)) else: Sender(cl, xmpp.Presence(jidFromStr, "subscribed", frm = jidTo)) - if Class.friends: + if user.friends: id = vk2xmpp(jidToStr) - if id in Class.friends: - if Class.friends[id]["online"]: + if id in user.friends: + if user.friends[id]["online"]: Sender(cl, xmpp.Presence(jidFrom, frm = jidTo)) + elif pType == "unsubscribe": if jidFromStr in Transport and jidToStr == TransportID: - Class.deleteUser(True) + user.deleteUser(True) WatcherMsg(_("User removed registration: %s") % jidFromStr) - if jidToStr == TransportID: - Class.lastStatus = pType elif pType in ("available", None): logger.debug("User %s not in transport but want to be in" % jidFromStr) with Database(DatabaseFile) as db: - db("select * from users where jid=?", (jidFromStr,)) - user = db.fetchone() - if user: + db("select jid,username from users where jid=?", (jidFromStr,)) + data = db.fetchone() + if data: logger.debug("User %s found in db" % jidFromStr) - jid, phone = user[:2] + jid, phone = data Transport[jid] = user = tUser((phone, None), jid) try: if user.connect(): diff --git a/library/vkApi.py b/library/vkApi.py index ebdc217..8eb31df 100644 --- a/library/vkApi.py +++ b/library/vkApi.py @@ -1,7 +1,6 @@ # /* coding: utf-8 */ -# © simpleApps CodingTeam, 2013. -# Warning: Code in this module is ugly, -# but we can't do better. +# © simpleApps CodingTeam, 2013 — 2014. + import time, ssl, urllib, urllib2, cookielib import logging, json, webtools @@ -27,12 +26,14 @@ def attemptTo(maxRetries, resultType, *errors): while retries < maxRetries: try: data = func(*args, **kwargs) - except errors as exc: + except errors, exc: retries += 1 time.sleep(0.2) else: break else: + if str(exc.reason) == "[Errno 101] Network is unreachable": + raise NetworkNotFound() data = resultType() logger.debug("Error %s occured on executing %s" % (exc, func)) return data @@ -172,7 +173,7 @@ class APIBinding: else: postTarget = webtools.getTagArg("form method=\"post\"", "action", body, "form") if postTarget: - body, response = self.RIP.post(PostTarget) + body, response = self.RIP.post(postTarget) token = response.url.split("=")[1].split("&")[0] else: raise AuthError("Couldn't execute confirmThisApp()!") @@ -195,7 +196,7 @@ class APIBinding: if (self.last.pop() - self.last.pop(0)) < 1.1: time.sleep(0.3) # warn: it was 0.4 // does it matter? - response = self.RIP.post(url, values) + response = self.RIP.post(url, values) # Next func should handle NetworkNotFound if response: body, response = response if body: @@ -226,13 +227,13 @@ class APIBinding: elif eCode == 5: # auth failed raise VkApiError("Logged out") if eCode == 7: - raise NotAllowed + raise NotAllowed() elif eCode == 9: return {} if eCode == 14: # captcha if "captcha_sid" in error: self.captcha = {"sid": error["captcha_sid"], "img": error["captcha_img"]} - raise CaptchaNeeded + raise CaptchaNeeded() raise VkApiError(body["error"]) def retry(self): @@ -240,6 +241,12 @@ class APIBinding: return self.method(*self.lastMethod) +class NetworkNotFound(Exception): ## maybe network is unreachable or vk is down (same as 10 jan 2014) + pass + +class UserGoesOffline(Exception): + pass + class VkApiError(Exception): pass diff --git a/library/webtools.py b/library/webtools.py index 65cea0c..9c13e16 100644 --- a/library/webtools.py +++ b/library/webtools.py @@ -56,7 +56,7 @@ def regexp(reg, string, findall = 1): def getTagData(tag, data, close_tag = 0): if not close_tag: close_tag = tag - pattern = re.compile("<%(tag)s.*?>(.*?)</%(close_tag)s>" % vars(), flags=re.S+re.IGNORECASE) + pattern = re.compile("<%(tag)s.*?>(.*?)</%(close_tag)s>" % vars(), flags=re.DOTALL | re.IGNORECASE) tagData = pattern.search(data) if tagData: tagData = tagData.group(1) @@ -65,7 +65,7 @@ def getTagData(tag, data, close_tag = 0): def getTagArg(tag, argv, data, close_tag = 0): if not close_tag: close_tag = tag - pattern = re.compile("<%(tag)s.? %(argv)s=[\"']?(.*?)[\"']?\">(.*?)</%(close_tag)s>" % vars(), flags=re.DOTALL|re.IGNORECASE) + pattern = re.compile("<%(tag)s.? %(argv)s=[\"']?(.*?)[\"']?\">(.*?)</%(close_tag)s>" % vars(), flags=re.DOTALL | re.IGNORECASE) tagData = pattern.search(data) if tagData: tagData = tagData.group(1) diff --git a/library/xmpp/auth.py b/library/xmpp/auth.py index ca21835..7042d42 100644 --- a/library/xmpp/auth.py +++ b/library/xmpp/auth.py @@ -20,20 +20,19 @@ Can be used both for client and transport authentication. """ import dispatcher -import sha +import hashlib from base64 import encodestring, decodestring -from hashlib import md5 as __md5 from plugin import PlugIn from protocol import * from random import random as _random from re import findall as re_findall def HH(some): - return __md5(some).hexdigest() + return hashlib.md5(some).hexdigest() def H(some): - return __md5(some).digest() + return hashlib.md5(some).digest() def C(some): return ":".join(some) @@ -70,7 +69,8 @@ class NonSASL(PlugIn): query.setTagData("resource", self.resource) if query.getTag("digest"): self.DEBUG("Performing digest authentication", "ok") - query.setTagData("digest", sha.new(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest()) + hash = hashlib.sha1(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest() + query.setTagData("digest", hash) if query.getTag("password"): query.delChild("password") method = "digest" @@ -78,9 +78,9 @@ class NonSASL(PlugIn): token = query.getTagData("token") seq = query.getTagData("sequence") self.DEBUG("Performing zero-k authentication", "ok") - hash = sha.new(sha.new(self.password).hexdigest() + token).hexdigest() - for foo in xrange(int(seq)): - hash = sha.new(hash).hexdigest() + hash = hashlib.sha1(hashlib.sha1(self.password).hexdigest() + token).hexdigest() + for i in xrange(int(seq)): + hash = hashlib.sha1(hash).hexdigest() query.setTagData("hash", hash) method = "0k" else: @@ -101,7 +101,8 @@ class NonSASL(PlugIn): Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. """ self.handshake = 0 - owner.send(Node(NS_COMPONENT_ACCEPT + " handshake", payload=[sha.new(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest()])) + hash = hashlib.sha1(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest() + owner.send(Node(NS_COMPONENT_ACCEPT + " handshake", payload=[hash])) owner.RegisterHandler("handshake", self.handshakeHandler, xmlns=NS_COMPONENT_ACCEPT) while not self.handshake: self.DEBUG("waiting on handshake", "notify") @@ -235,7 +236,7 @@ class SASL(PlugIn): resp["realm"] = self._owner.Server resp["nonce"] = chal["nonce"] cnonce = "" - for i in range(7): + for i in xrange(7): cnonce += hex(int(_random() * 65536 * 4096))[2:] resp["cnonce"] = cnonce resp["nc"] = ("00000001") @@ -247,8 +248,8 @@ class SASL(PlugIn): resp["response"] = response resp["charset"] = "utf-8" sasl_data = "" - for key in ["charset", "username", "realm", "nonce", "nc", "cnonce", "digest-uri", "response", "qop"]: - if key in ["nc", "qop", "response", "charset"]: + for key in ("charset", "username", "realm", "nonce", "nc", "cnonce", "digest-uri", "response", "qop"): + if key in ("nc", "qop", "response", "charset"): sasl_data += "%s=%s," % (key, resp[key]) else: sasl_data += "%s=\"%s\"," % (key, resp[key]) diff --git a/library/xmpp/browser.py b/library/xmpp/browser.py index d2ffdf0..db74131 100644 --- a/library/xmpp/browser.py +++ b/library/xmpp/browser.py @@ -122,7 +122,7 @@ class Browser(PlugIn): get returns "" or None as the key or None as the dict. Used internally. """ - if self._handlers.has_key(jid): + if jid in self._handlers: cur = self._handlers[jid] elif set: self._handlers[jid] = {} diff --git a/library/xmpp/commands.py b/library/xmpp/commands.py index 9c75649..3d4f75c 100644 --- a/library/xmpp/commands.py +++ b/library/xmpp/commands.py @@ -95,13 +95,13 @@ class Commands(PlugIn): except Exception: conn.send(Error(request, ERR_BAD_REQUEST)) raise NodeProcessed() - if self._handlers.has_key(jid): - if self._handlers[jid].has_key(node): + if jid in self._handlers: + if node in self._handlers[jid]: self._handlers[jid][node]["execute"](conn, request) else: conn.send(Error(request, ERR_ITEM_NOT_FOUND)) raise NodeProcessed() - elif self._handlers[""].has_key(node): + elif node in self._handlers[""]: self._handlers[""][node]["execute"](conn, request) else: conn.send(Error(request, ERR_ITEM_NOT_FOUND)) @@ -124,7 +124,7 @@ class Commands(PlugIn): items = [] jid = str(request.getTo()) # Get specific jid based results - if self._handlers.has_key(jid): + if jid in self._handlers: for each in self._handlers[jid].keys(): items.append((jid, each)) else: @@ -160,10 +160,10 @@ class Commands(PlugIn): # We must: # Add item into disco # Add item into command list - if not self._handlers.has_key(jid): + if jid not in self._handlers: self._handlers[jid] = {} self._browser.setDiscoHandler(self._DiscoHandler, node=NS_COMMANDS, jid=jid) - if self._handlers[jid].has_key(name): + if name in self._handlers[jid]: raise NameError("Command Exists") self._handlers[jid][name] = {"disco": cmddisco, "execute": cmdexecute} # Need to add disco stuff here @@ -177,9 +177,9 @@ class Commands(PlugIn): # We must: # Remove item from disco # Remove item from command list - if not self._handlers.has_key(jid): + if jid not in self._handlers: raise NameError("Jid not found") - if not self._handlers[jid].has_key(name): + if name not in self._handlers[jid]: raise NameError("Command not found") # Do disco removal here command = self.getCommand(name, jid)["disco"] @@ -193,9 +193,9 @@ class Commands(PlugIn): # This gets the command object with name # We must: # Return item that matches this name - if not self._handlers.has_key(jid): + if jid not in self._handlers: raise NameError("Jid not found") - if not self._handlers[jid].has_key(name): + if name not in self._handlers[jid]: raise NameError("Command not found") return self._handlers[jid][name] @@ -258,7 +258,7 @@ class Command_Handler_Prototype(PlugIn): """ Returns an id for the command session. """ - self.count = self.count + 1 + self.count += 1 return "cmd-%s-%d" % (self.name, self.count) def Execute(self, conn, request): @@ -277,10 +277,10 @@ class Command_Handler_Prototype(PlugIn): if action == None: action = "execute" # Check session is in session list - if self.sessions.has_key(session): + if session in self.sessions: if self.sessions[session]["jid"] == request.getFrom(): # Check action is vaild - if self.sessions[session]["actions"].has_key(action): + if action in self.sessions[session]["actions"]: # Execute next action self.sessions[session]["actions"][action](conn, request) else: @@ -299,15 +299,15 @@ class Command_Handler_Prototype(PlugIn): # New session self.initial[action](conn, request) - def _DiscoHandler(self, conn, request, type): + def _DiscoHandler(self, conn, request, typ): """ The handler for discovery events. """ - if type == "list": + if typ == "list": result = (request.getTo(), self.name, self.description) - elif type == "items": + elif typ == "items": result = [] - elif type == "info": + elif typ == "info": result = self.discoinfo return result diff --git a/library/xmpp/debug.py b/library/xmpp/debug.py index e84b4c1..ccccd02 100644 --- a/library/xmpp/debug.py +++ b/library/xmpp/debug.py @@ -162,13 +162,13 @@ class Debug: self._fh.write(output) except Exception: # unicode strikes again ;) - s = u"" + s = unicode() for i in xrange(len(output)): if ord(output[i]) < 128: c = output[i] else: c = "?" - s = s + c + s += c self._fh.write("%s%s%s" % (pre, s, suf)) self._fh.flush() diff --git a/library/xmpp/dispatcher.py b/library/xmpp/dispatcher.py index 62151d3..5d020d0 100644 --- a/library/xmpp/dispatcher.py +++ b/library/xmpp/dispatcher.py @@ -27,6 +27,7 @@ import time from plugin import PlugIn from protocol import * +from select import select from xml.parsers.expat import ExpatError DefaultTimeout = 25 @@ -151,9 +152,14 @@ class Dispatcher(PlugIn): if self._pendingExceptions: e = self._pendingExceptions.pop() raise e[0], e[1], e[2] - if self._owner.Connection.pending_data(timeout): + conn = self._owner.Connection + recv, send = select([conn._sock], [conn._sock], [], timeout)[:2] + if send: + while conn._send_queue: + conn.send_now(conn._send_queue.pop(0)) + if recv: try: - data = self._owner.Connection.receive() + data = conn.receive() except IOError: return None try: diff --git a/library/xmpp/protocol.py b/library/xmpp/protocol.py index f49dcba..36822c6 100644 --- a/library/xmpp/protocol.py +++ b/library/xmpp/protocol.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: protocol.py, v1.63 2013/12/06 alkorgun Exp $ +# $Id: protocol.py, v1.64 2014/01/10 alkorgun Exp $ """ Protocol module contains tools that is needed for processing of @@ -109,13 +109,15 @@ NS_MUC_ROOMS = NS_MUC + "#rooms" # XEP-0045 NS_MUC_TRAFIC = NS_MUC + "#traffic" # XEP-0045 NS_NICK = "http://jabber.org/protocol/nick" # XEP-0172 NS_OFFLINE = "http://jabber.org/protocol/offline" # XEP-0013 +NS_OOB = "jabber:x:oob" # XEP-0066 NS_PHYSLOC = "http://jabber.org/protocol/physloc" # XEP-0112 NS_PRESENCE = "presence" # Jabberd2 NS_PRIVACY = "jabber:iq:privacy" # RFC 3921 NS_PRIVATE = "jabber:iq:private" # XEP-0049 NS_PUBSUB = "http://jabber.org/protocol/pubsub" # XEP-0060 -NS_REGISTER = "jabber:iq:register" # XEP-0077 NS_RC = "http://jabber.org/protocol/rc" # XEP-0146 +NS_REGISTER = "jabber:iq:register" # XEP-0077 +NS_RECEIPTS = "urn:xmpp:receipts" # XEP-0184 NS_ROSTER = "jabber:iq:roster" # RFC 3921 NS_ROSTERX = "http://jabber.org/protocol/rosterx" # XEP-0144 NS_RPC = "jabber:iq:rpc" # XEP-0009 @@ -126,10 +128,15 @@ NS_SESSION = "urn:ietf:params:xml:ns:xmpp-session" # RFC 3921 NS_SI = "http://jabber.org/protocol/si" # XEP-0096 NS_SI_PUB = "http://jabber.org/protocol/sipub" # XEP-0137 NS_SIGNED = "jabber:x:signed" # XEP-0027 +NS_SOFTWAREINFO = "urn:xmpp:dataforms:softwareinfo" # XEP-0155 NS_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas" # RFC 3920 +NS_STATS = "http://jabber.org/protocol/stats" # XEP-0039 NS_STREAMS = "http://etherx.jabber.org/streams" # RFC 3920 NS_TIME = "jabber:iq:time" # XEP-0090 (deprecated) NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls" # RFC 3920 +NS_URN_ATTENTION = "urn:xmpp:attention:0" # XEP-0224 +NS_URN_OOB = "urn:xmpp:bob" # XEP-0158 +NS_URN_TIME = "urn:xmpp:time" # XEP-0202 NS_VACATION = "http://jabber.org/protocol/vacation" # XEP-0109 NS_VCARD = "vcard-temp" # XEP-0054 NS_VCARD_UPDATE = "vcard-temp:x:update" # XEP-0153 @@ -137,14 +144,9 @@ NS_VERSION = "jabber:iq:version" # XEP-0092 NS_WAITINGLIST = "http://jabber.org/protocol/waitinglist" # XEP-0130 NS_XHTML_IM = "http://jabber.org/protocol/xhtml-im" # XEP-0071 NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams" # RFC 3920 -NS_STATS = "http://jabber.org/protocol/stats" # XEP-0039 NS_PING = "urn:xmpp:ping" # XEP-0199 + NS_MUC_FILTER = "http://jabber.ru/muc-filter" -NS_URN_TIME = "urn:xmpp:time" # XEP-0202 -NS_RECEIPTS = "urn:xmpp:receipts" # XEP-0184 -NS_OOB = "jabber:x:oob" # XEP-0066 -NS_URN_ATTENTION = "urn:xmpp:attention:0" # XEP-0224 -NS_URN_OOB = "urn:xmpp:bob" # XEP-0158 STREAM_NOT_AUTHORIZED = NS_XMPP_STREAMS + " not-authorized" STREAM_REMOTE_CONNECTION_FAILED = NS_XMPP_STREAMS + " remote-connection-failed" @@ -234,7 +236,7 @@ ERRORS = { "urn:ietf:params:xml:ns:xmpp-streams invalid-from": ["cancel", "", "The JID or hostname provided in a \"from\" address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization."], "urn:ietf:params:xml:ns:xmpp-streams bad-format": ["", "", "The entity has sent XML that cannot be processed."], "urn:ietf:params:xml:ns:xmpp-streams resource-constraint": ["", "", "The server lacks the system resources necessary to service the stream."], - "urn:ietf:params:xml:ns:xmpp-stanzas undefined-condition": ["500", "", ""], + "urn:ietf:params:xml:ns:xmpp-stanzas undefined-condition": ["500", "", "The condition is undefined."], "urn:ietf:params:xml:ns:xmpp-stanzas redirect": ["302", "modify", "The recipient or server is redirecting requests for this information to another entity."], "urn:ietf:params:xml:ns:xmpp-streams bad-namespace-prefix": ["", "", "The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix."], "urn:ietf:params:xml:ns:xmpp-streams system-shutdown": ["", "", "The server is being shut down and all active streams are being closed."], @@ -704,7 +706,7 @@ class Message(Protocol): def buildReply(self, text=None): """ Builds and returns another message object with specified text. - The to, from and thread properties of new message are pre-set as reply to this message. + The to, from type and thread properties of new message are pre-set as reply to this message. """ msg = Message(to=self.getFrom(), frm=self.getTo(), body=text) thr = self.getThread() @@ -835,9 +837,15 @@ class Iq(Protocol): if queryNS: self.setQueryNS(queryNS) + def getQuery(self): + """ + Returns the query node. + """ + return self.getTag("query") + def getQueryNS(self): """ - Return the namespace of the "query" child element. + Returns the namespace of the "query" child element. """ tag = self.getTag("query") if tag: @@ -845,13 +853,13 @@ class Iq(Protocol): def getQuerynode(self): """ - Return the "node" attribute value of the "query" child element. + Returns the "node" attribute value of the "query" child element. """ return self.getTagAttr("query", "node") def getQueryPayload(self): """ - Return the "query" child element payload. + Returns the "query" child element payload. """ tag = self.getTag("query") if tag: @@ -859,12 +867,25 @@ class Iq(Protocol): def getQueryChildren(self): """ - Return the "query" child element child nodes. + Returns the "query" child element child nodes. """ tag = self.getTag("query") if tag: return tag.getChildren() + def setQuery(self, name=None): + """ + Changes the name of the query node, creates it if needed. + Keep the existing name if none is given (use "query" if it's a creation). + Returns the query node. + """ + query = self.getQuery() + if query is None: + query = self.addChild("query") + if name is not None: + query.setName(name) + return query + def setQueryNS(self, namespace): """ Set the namespace of the "query" child element. diff --git a/library/xmpp/simplexml.py b/library/xmpp/simplexml.py index a416d24..ab2a3b4 100644 --- a/library/xmpp/simplexml.py +++ b/library/xmpp/simplexml.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: simplexml.py, v1.35 2013/10/21 alkorgun Exp $ +# $Id: simplexml.py, v1.36 2014/01/10 alkorgun Exp $ """ Simplexml module provides xmpppy library with all needed tools to handle @@ -25,8 +25,6 @@ import xml.parsers.expat XML_ls = ( ("&", "&"), - ("\x0C", ""), - ("\x1B", ""), ("<", "<"), (">", ">"), ('"', """), @@ -108,7 +106,7 @@ class Node(object): self.nsp_cache[k] = v for attr, val in attrs.items(): if attr == "xmlns": - self.nsd[u""] = val + self.nsd[""] = val elif attr.startswith("xmlns:"): self.nsd[attr[6:]] = val self.attrs[attr] = attrs[attr] @@ -149,39 +147,39 @@ class Node(object): if self.namespace: if not self.parent or self.parent.namespace != self.namespace: if "xmlns" not in self.attrs: - s = s + " xmlns=\"%s\"" % self.namespace + s += " xmlns=\"%s\"" % self.namespace for key in self.attrs.keys(): val = ustr(self.attrs[key]) - s = s + " %s=\"%s\"" % (key, XMLescape(val)) - s = s + ">" + s += " %s=\"%s\"" % (key, XMLescape(val)) + s += ">" cnt = 0 if self.kids: if fancy: - s = s + "\n" + s += "\n" for a in self.kids: if not fancy and (len(self.data) - 1) >= cnt: - s = s + XMLescape(self.data[cnt]) + s += XMLescape(self.data[cnt]) elif (len(self.data) - 1) >= cnt: - s = s + XMLescape(self.data[cnt].strip()) + s += XMLescape(self.data[cnt].strip()) if isinstance(a, Node): - s = s + a.__str__(fancy and fancy + 1) + s += a.__str__(fancy and fancy + 1) elif a: - s = s + a.__str__() - cnt = cnt + 1 + s += a.__str__() + cnt += 1 if not fancy and (len(self.data) - 1) >= cnt: - s = s + XMLescape(self.data[cnt]) + s += XMLescape(self.data[cnt]) elif (len(self.data) - 1) >= cnt: - s = s + XMLescape(self.data[cnt].strip()) + s += XMLescape(self.data[cnt].strip()) if not self.kids and s.endswith(">"): s = s[:-1] + " />" if fancy: - s = s + "\n" + s += "\n" else: if fancy and not self.data: - s = s + (fancy - 1) * 2 * " " - s = s + "</" + self.name + ">" + s += (fancy - 1) * 2 * " " + s += "</" + self.name + ">" if fancy: - s = s + "\n" + s += "\n" return s def getCDATA(self): @@ -193,12 +191,12 @@ class Node(object): cnt = 0 if self.kids: for a in self.kids: - s = s + self.data[cnt] + s += self.data[cnt] if a: - s = s + a.getCDATA() - cnt = cnt + 1 + s += a.getCDATA() + cnt += 1 if (len(self.data) - 1) >= cnt: - s = s + self.data[cnt] + s += self.data[cnt] return s def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None): @@ -216,7 +214,7 @@ class Node(object): if namespace: newnode.setNamespace(namespace) self.kids.append(newnode) - self.data.append(u"") + self.data.append("") return newnode def addData(self, data): @@ -301,7 +299,7 @@ class Node(object): ["text1", <nodea instance>, <nodeb instance>, " text2"]. """ pl = [] - for i in range(max(len(self.data), len(self.kids))): + for i in xrange(max(len(self.data), len(self.kids))): if i < len(self.data) and self.data[i]: pl.append(self.data[i]) if i < len(self.kids) and self.kids[i]: diff --git a/library/xmpp/transports.py b/library/xmpp/transports.py index 13a2798..2d93686 100644 --- a/library/xmpp/transports.py +++ b/library/xmpp/transports.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: transports.py, v1.36 2013/11/03 alkorgun Exp $ +# $Id: transports.py, v1.36 2014/01/10 alkorgun Exp $ """ This module contains the low-level implementations of xmpppy connect methods or @@ -29,6 +29,8 @@ Also exception 'error' is defined to allow capture of this module specific excep import sys import socket +if sys.hexversion >= 0x20600F0: + import ssl import dispatcher from base64 import encodestring @@ -80,6 +82,7 @@ class TCPsocket(PlugIn): self.DBG_LINE = "socket" self._exported_methods = [self.send, self.disconnect] self._server, self.use_srv = server, use_srv + self._send_queue = [] def srv_lookup(self, server): """ @@ -92,7 +95,9 @@ class TCPsocket(PlugIn): dns__ = dns.Request() response = dns__.req(query, qtype="SRV") if response.answers: - (port, host) = response.answers[0]["data"][2:] + # Sort by priority, according to RFC 2782. + answers = sorted(response.answers, key=lambda a: a["data"][0]) + (port, host) = answers[0]["data"][2:] server = str(host), int(port) except dns.DNSError: self.DEBUG("An error occurred while looking up %s." % query, "warn") @@ -130,41 +135,50 @@ class TCPsocket(PlugIn): def connect(self, server=None): """ - Try to connect to the given host/port. Does not lookup for SRV record. + Try to connect to the given host/port. Returns non-empty string on success. """ if not server: server = self._server host, port = server - server = (host, int(port)) - if ":" in host: - sock = socket.AF_INET6 - server = server.__add__((0, 0)) - else: - sock = socket.AF_INET + socktype = socket.SOCK_STREAM try: - self._sock = socket.socket(sock, socket.SOCK_STREAM) - self._sock.connect(server) - self._send = self._sock.sendall - self._recv = self._sock.recv - except socket.error as error: + lookup = reversed(socket.getaddrinfo(host, int(port), 0, socktype)) + except Exception: + addr = (host, int(port)) + if ":" in host: + af = socket.AF_INET6 + addr = addr.__add__((0, 0)) + else: + af = socket.AF_INET + lookup = [(af, socktype, 1, 6, addr)] + for af, socktype, proto, cn, addr in lookup: try: - code, error = error + self._sock = socket.socket(af, socktype) + self._sock.connect(addr) + self._send = self._sock.sendall + self._recv = self._sock.recv + except socket.error as error: + if getattr(self, "_sock", None): + self._sock.close() + try: + code, error = error + except Exception: + code = -1 + self.DEBUG("Failed to connect to remote host %s: %s (%s)" % (repr(server), error, code), "error") except Exception: - code = -1 - self.DEBUG("Failed to connect to remote host %s: %s (%s)" % (repr(server), error, code), "error") - except Exception: - pass - else: - self.DEBUG("Successfully connected to remote host %s." % repr(server), "start") - return "ok" + pass + else: + self.DEBUG("Successfully connected to remote host %s." % repr(server), "start") + return "ok" def plugout(self): """ Disconnect from the remote server and unregister self.disconnected method from the owner's dispatcher. """ - self._sock.close() + if getattr(self, "_sock", None): + self._sock.close() if hasattr(self._owner, "Connection"): del self._owner.Connection self._owner.UnregisterDisconnectHandler(self.disconnected) @@ -206,7 +220,10 @@ class TCPsocket(PlugIn): raise IOError("Disconnected!") return data - def send(self, data, timeout=0.002): + def send(self, data): + self._send_queue.append(data) + + def send_now(self, data, timeout=0.002): """ Writes raw outgoing data. Blocks until done. If supplied data is unicode string, encodes it to utf-8 before send. @@ -215,20 +232,17 @@ class TCPsocket(PlugIn): data = data.encode("utf-8") elif not isinstance(data, str): data = ustr(data).encode("utf-8") - while not select((), [self._sock], (), 0.002)[1]: - pass + try: + self._send(data) + except Exception: + self.DEBUG("Socket error while sending data.", "error") + self._owner.disconnected() else: - try: - self._send(data) - except Exception: - self.DEBUG("Socket error while sending data.", "error") - self._owner.disconnected() - else: - if not data.strip(): - data = repr(data) - self.DEBUG(data, "sent") - if hasattr(self._owner, "Dispatcher"): - self._owner.Dispatcher.Event("", DATA_SENT, data) + if not data.strip(): + data = repr(data) + self.DEBUG(data, "sent") + if hasattr(self._owner, "Dispatcher"): + self._owner.Dispatcher.Event("", DATA_SENT, data) def pending_data(self, timeout=0): """ @@ -382,9 +396,12 @@ class TLS(PlugIn): def _startSSL(self): tcpsock = self._owner.Connection - tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) - tcpsock._sslIssuer = tcpsock._sslObj.issuer() - tcpsock._sslServer = tcpsock._sslObj.server() + if sys.hexversion >= 0x20600F0: + tcpsock._sslObj = ssl.wrap_socket(tcpsock._sock, None, None) + else: + tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) + tcpsock._sslIssuer = tcpsock._sslObj.issuer() + tcpsock._sslServer = tcpsock._sslObj.server() tcpsock._recv = tcpsock._sslObj.read tcpsock._send = tcpsock._sslObj.write tcpsock._seen_data = 1 diff --git a/locales/locale.pl b/locales/locale.pl index a28eb4d..5235152 100644 --- a/locales/locale.pl +++ b/locales/locale.pl @@ -7,7 +7,7 @@ Null password=Puste hasło lub access-token! Incorrect password or access token!=Błędne hasło lub access-token! Initialization failed.=Błąd inicjalizacji. Feature not implemented.=Funkcja nie realizowana. -© simpleApps, 2013.\LYou can support developing of any project via donation by WebMoney:\LZ405564701378 | R330257574689.=© simpleApps, 2013.\LВы можете поддержать разработку с помощью пожертвования через WebMoney:\LZ405564701378 | R330257574689. +© simpleApps, 2013 — 2014.\LYou can support developing of any project via donation by WebMoney:\LZ405564701378 | R330257574689.=© simpleApps, 2013 — 2014.\LВы можете поддержать разработку с помощью пожертвования через WebMoney:\LZ405564701378 | R330257574689. If you found any problems, please contact us:\Lhttp://github.com/mrDoctorWho/vk4xmpp • xmpp:simpleapps@conference.jabber.ru=Если вы столкнулись с какой-либо проблемой и считаете, что сделали всё верно, то, пожалуйста, свяжитесь с нами:\Lhttp://github.com/mrDoctorWho/vk4xmpp • xmpp:simpleapps@conference.jabber.ru Contact uses VK4XMPP Transport\L%s=Контакт использует транспорт VK4XMPP\L%s User is not your friend.=Użytkownik nie jest twoim przyjacielem! diff --git a/locales/locale.ru b/locales/locale.ru index caa4f80..6ef9100 100644 --- a/locales/locale.ru +++ b/locales/locale.ru @@ -7,7 +7,7 @@ Null password=Пустой пароль или access-token! Incorrect password or access token!=Неправильный пароль или access-token! Initialization failed.=Ошибка инициализации. Feature not implemented.=Возможность не реализована. -© simpleApps, 2013.\LYou can support developing of any project via donation by WebMoney:\LZ405564701378 | R330257574689.=© simpleApps, 2013.\LВы можете поддержать разработку с помощью пожертвования через WebMoney:\LZ405564701378 | R330257574689. +© simpleApps, 2013 — 2014.\LYou can support developing of any project via donation by WebMoney:\LZ405564701378 | R330257574689.=© simpleApps, 2013 — 2014.\LВы можете поддержать разработку с помощью пожертвования через WebMoney:\LZ405564701378 | R330257574689. If you found any problems, please contact us:\Lhttp://github.com/mrDoctorWho/vk4xmpp • xmpp:simpleapps@conference.jabber.ru=Если вы столкнулись с какой-либо проблемой и считаете, что сделали всё верно, то, пожалуйста, свяжитесь с нами:\Lhttp://github.com/mrDoctorWho/vk4xmpp • xmpp:simpleapps@conference.jabber.ru Contact uses VK4XMPP Transport\L%s=Контакт использует транспорт VK4XMPP\L%s User is not your friend.=Пользователь не ваш друг! |