Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gajim/gajim-plugins
  • lovetox/gajim-plugins
  • ag/gajim-plugins
  • FlorianMuenchbach/gajim-plugins
  • rom1dep/gajim-plugins
  • pitchum/gajim-plugins
  • wurstsalat/gajim-plugins
  • Dicson/gajim-plugins
  • andre/gajim-plugins
  • link2xt/gajim-plugins
  • marmistrz/gajim-plugins
  • Jens/gajim-plugins
  • muelli/gajim-plugins
  • asterix/gajim-plugins
  • orhideous/gajim-plugins
  • ngvelprz/gajim-plugins
  • appleorange1/gajim-plugins
  • Martin/gajim-plugins
  • maltel/gajim-plugins
  • Seve/gajim-plugins
  • evert-mouw/gajim-plugins
  • Yuki/gajim-plugins
  • mxre/gajim-plugins
  • ValdikSS/gajim-plugins
  • SaltyBones/gajim-plugins
  • comradekingu/gajim-plugins
  • ritzmann/gajim-plugins
  • genofire/gajim-plugins
  • jjrh/gajim-plugins
  • yarmak/gajim-plugins
  • PapaTutuWawa/gajim-plugins
  • weblate/gajim-plugins
  • XutaxKamay/gajim-plugins
  • nekk/gajim-plugins
  • principis/gajim-plugins
  • cbix/gajim-plugins
  • bodqhrohro/gajim-plugins
  • airtower-luna/gajim-plugins
  • toms/gajim-plugins
  • mesonium/gajim-plugins
  • lissine/gajim-plugins
  • anviar/gajim-plugins
42 results
Show changes
Showing
with 1171 additions and 239 deletions
......@@ -22,8 +22,8 @@ import logging
import struct
from potr.compatcrypto import SHA256, SHA1, HMAC, SHA1HMAC, SHA256HMAC, \
SHA256HMAC160, Counter, AESCTR, RNG, PK, generateDefaultKey
from potr.compatcrypto import SHA256, SHA1, SHA1HMAC, SHA256HMAC, \
SHA256HMAC160, Counter, AESCTR, PK, random
from potr.utils import bytes_to_long, long_to_bytes, pack_mpi, read_mpi
from potr import proto
......@@ -36,32 +36,31 @@ STATE_AWAITING_SIG = 4
STATE_V1_SETUP = 5
DH1536_MODULUS = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919
DH1536_MODULUS_2 = DH1536_MODULUS-2
DH1536_GENERATOR = 2
SM_ORDER = (DH1536_MODULUS - 1) // 2
DH_MODULUS = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919
DH_MODULUS_2 = DH_MODULUS-2
DH_GENERATOR = 2
DH_BITS = 1536
DH_MAX = 2**DH_BITS
SM_ORDER = (DH_MODULUS - 1) // 2
def check_group(n):
return 2 <= n <= DH1536_MODULUS_2
return 2 <= n <= DH_MODULUS_2
def check_exp(n):
return 1 <= n < SM_ORDER
class DH(object):
__slots__ = ['priv', 'pub']
@classmethod
def set_params(cls, prime, gen):
cls.prime = prime
cls.gen = gen
def __init__(self):
self.priv = bytes_to_long(RNG.read(40))
self.priv = random.randrange(2, 2**320)
self.pub = pow(self.gen, self.priv, self.prime)
DH.set_params(DH1536_MODULUS, DH1536_GENERATOR)
DH.set_params(DH_MODULUS, DH_GENERATOR)
class DHSession(object):
__slots__ = ['sendenc', 'sendmac', 'rcvenc', 'rcvmac', 'sendctr', 'rcvctr',
'sendmacused', 'rcvmacused']
def __init__(self, sendenc, sendmac, rcvenc, rcvmac):
self.sendenc = sendenc
self.sendmac = sendmac
......@@ -79,7 +78,7 @@ class DHSession(object):
@classmethod
def create(cls, dh, y):
s = pow(y, dh.priv, DH1536_MODULUS)
s = pow(y, dh.priv, DH_MODULUS)
sb = pack_mpi(s)
if dh.pub > y:
......@@ -96,9 +95,6 @@ class DHSession(object):
return cls(sendenc, sendmac, rcvenc, rcvmac)
class CryptEngine(object):
__slots__ = ['ctx', 'ake', 'sessionId', 'sessionIdHalf', 'theirKeyid',
'theirY', 'theirOldY', 'ourOldDHKey', 'ourDHKey', 'ourKeyid',
'sessionkeys', 'theirPubkey', 'savedMacKeys', 'smp']
def __init__(self, ctx):
self.ctx = ctx
self.ake = None
......@@ -118,6 +114,7 @@ class CryptEngine(object):
self.savedMacKeys = []
self.smp = None
self.extraKey = None
def revealMacs(self, ours=True):
if ours:
......@@ -174,7 +171,7 @@ class CryptEngine(object):
if msg.mac != SHA1HMAC(sesskey.rcvmac, msg.getMacedData()):
logger.error('HMACs don\'t match')
raise InvalidParameterError
sesskey.rcvmacused = 1
sesskey.rcvmacused = True
newCtrPrefix = bytes_to_long(msg.ctr)
if newCtrPrefix <= sesskey.rcvctr.prefix:
......@@ -223,11 +220,14 @@ class CryptEngine(object):
self.smp = SMPHandler(self)
self.smp.abort(appdata=appdata)
def createDataMessage(self, message, flags=0, tlvs=[]):
def createDataMessage(self, message, flags=0, tlvs=None):
# check MSGSTATE
if self.theirKeyid == 0:
raise InvalidParameterError
if tlvs is None:
tlvs = []
sess = self.sessionkeys[1][0]
sess.sendctr.inc()
......@@ -303,13 +303,16 @@ class CryptEngine(object):
self.ourKeyid = ake.ourKeyid
self.theirY = ake.gy
self.theirOldY = None
self.extraKey = ake.extraKey
if self.ourKeyid != ake.ourKeyid + 1 or self.ourOldDHKey != ake.dh.pub:
# XXX is this really ok?
self.ourDHKey = ake.dh
self.sessionkeys[0][0] = DHSession.create(self.ourDHKey, self.theirY)
self.rotateDHKeys()
# we don't need the AKE anymore, free the reference
self.ake = None
self.ctx._wentEncrypted()
logger.info('went encrypted with {0}'.format(self.theirPubkey))
......@@ -317,10 +320,6 @@ class CryptEngine(object):
self.smp = None
class AuthKeyExchange(object):
__slots__ = ['privkey', 'state', 'r', 'encgx', 'hashgx', 'ourKeyid',
'theirPubkey', 'theirKeyid', 'enc_c', 'enc_cp', 'mac_m1',
'mac_m1p', 'mac_m2', 'mac_m2p', 'sessionId', 'dh', 'onSuccess',
'gy', 'lastmsg', 'sessionIdHalf']
def __init__(self, privkey, onSuccess):
self.privkey = privkey
self.state = STATE_NONE
......@@ -341,9 +340,11 @@ class AuthKeyExchange(object):
self.dh = DH()
self.onSuccess = onSuccess
self.gy = None
self.extraKey = None
self.lastmsg = None
def startAKE(self):
self.r = RNG.read(16)
self.r = long_to_bytes(random.getrandbits(128))
gxmpi = pack_mpi(self.dh.pub)
......@@ -444,15 +445,17 @@ class AuthKeyExchange(object):
self.state = STATE_NONE
def createAuthKeys(self):
s = pow(self.gy, self.dh.priv, DH1536_MODULUS)
s = pow(self.gy, self.dh.priv, DH_MODULUS)
sbyte = pack_mpi(s)
self.sessionId = SHA256(b'\0' + sbyte)[:8]
enc = SHA256(b'\1' + sbyte)
self.enc_c, self.enc_cp = enc[:16], enc[16:]
self.mac_m1 = SHA256(b'\2' + sbyte)
self.mac_m2 = SHA256(b'\3' + sbyte)
self.mac_m1p = SHA256(b'\4' + sbyte)
self.mac_m2p = SHA256(b'\5' + sbyte)
self.sessionId = SHA256(b'\x00' + sbyte)[:8]
enc = SHA256(b'\x01' + sbyte)
self.enc_c = enc[:16]
self.enc_cp = enc[16:]
self.mac_m1 = SHA256(b'\x02' + sbyte)
self.mac_m2 = SHA256(b'\x03' + sbyte)
self.mac_m1p = SHA256(b'\x04' + sbyte)
self.mac_m2p = SHA256(b'\x05' + sbyte)
self.extraKey = SHA256(b'\xff' + sbyte)
def calculatePubkeyAuth(self, key, mackey):
pubkey = self.privkey.serializePublicKey()
......@@ -490,14 +493,15 @@ SMPPROG_FAILED = -1
SMPPROG_SUCCEEDED = 1
class SMPHandler:
__slots__ = ['crypto', 'questionReceived', 'prog', 'state', 'g1', 'g3o',
'x2', 'x3', 'g2', 'g3', 'pab', 'qab', 'secret', 'p', 'q']
def __init__(self, crypto):
self.crypto = crypto
self.state = 1
self.g1 = DH1536_GENERATOR
self.g1 = DH_GENERATOR
self.g2 = None
self.g3 = None
self.g3o = None
self.x2 = None
self.x3 = None
self.prog = SMPPROG_OK
self.pab = None
self.qab = None
......@@ -539,11 +543,11 @@ class SMPHandler:
self.g3o = msg[3]
self.x2 = bytes_to_long(RNG.read(192))
self.x3 = bytes_to_long(RNG.read(192))
self.x2 = random.randrange(2, DH_MAX)
self.x3 = random.randrange(2, DH_MAX)
self.g2 = pow(msg[0], self.x2, DH1536_MODULUS)
self.g3 = pow(msg[3], self.x3, DH1536_MODULUS)
self.g2 = pow(msg[0], self.x2, DH_MODULUS)
self.g3 = pow(msg[3], self.x3, DH_MODULUS)
self.prog = SMPPROG_OK
self.state = 0
......@@ -568,29 +572,29 @@ class SMPHandler:
return
self.g3o = msg[3]
self.g2 = pow(msg[0], self.x2, DH1536_MODULUS)
self.g3 = pow(msg[3], self.x3, DH1536_MODULUS)
self.g2 = pow(msg[0], self.x2, DH_MODULUS)
self.g3 = pow(msg[3], self.x3, DH_MODULUS)
if not self.check_equal_coords(msg[6:11], 5):
logger.error('invalid SMP2TLV received')
self.abort(appdata=appdata)
return
r = bytes_to_long(RNG.read(192))
self.p = pow(self.g3, r, DH1536_MODULUS)
r = random.randrange(2, DH_MAX)
self.p = pow(self.g3, r, DH_MODULUS)
msg = [self.p]
qa1 = pow(self.g1, r, DH1536_MODULUS)
qa2 = pow(self.g2, self.secret, DH1536_MODULUS)
self.q = qa1*qa2 % DH1536_MODULUS
qa1 = pow(self.g1, r, DH_MODULUS)
qa2 = pow(self.g2, self.secret, DH_MODULUS)
self.q = qa1*qa2 % DH_MODULUS
msg.append(self.q)
msg += self.proof_equal_coords(r, 6)
inv = invMod(mp)
self.pab = self.p * inv % DH1536_MODULUS
self.pab = self.p * inv % DH_MODULUS
inv = invMod(mq)
self.qab = self.q * inv % DH1536_MODULUS
self.qab = self.q * inv % DH_MODULUS
msg.append(pow(self.qab, self.x3, DH1536_MODULUS))
msg.append(pow(self.qab, self.x3, DH_MODULUS))
msg += self.proof_equal_logs(7)
self.state = 4
......@@ -613,9 +617,9 @@ class SMPHandler:
return
inv = invMod(self.p)
self.pab = msg[0] * inv % DH1536_MODULUS
self.pab = msg[0] * inv % DH_MODULUS
inv = invMod(self.q)
self.qab = msg[1] * inv % DH1536_MODULUS
self.qab = msg[1] * inv % DH_MODULUS
if not self.check_equal_logs(msg[5:8], 7):
logger.error('invalid SMP3TLV received')
......@@ -623,10 +627,10 @@ class SMPHandler:
return
md = msg[5]
msg = [pow(self.qab, self.x3, DH1536_MODULUS)]
msg = [pow(self.qab, self.x3, DH_MODULUS)]
msg += self.proof_equal_logs(8)
rab = pow(md, self.x3, DH1536_MODULUS)
rab = pow(md, self.x3, DH_MODULUS)
self.prog = SMPPROG_SUCCEEDED if self.pab == rab else SMPPROG_FAILED
if self.prog != SMPPROG_SUCCEEDED:
......@@ -654,7 +658,7 @@ class SMPHandler:
self.abort(appdata=appdata)
return
rab = pow(msg[0], self.x3, DH1536_MODULUS)
rab = pow(msg[0], self.x3, DH_MODULUS)
self.prog = SMPPROG_SUCCEEDED if self.pab == rab else SMPPROG_FAILED
......@@ -679,12 +683,12 @@ class SMPHandler:
self.secret = bytes_to_long(combSecret)
self.x2 = bytes_to_long(RNG.read(192))
self.x3 = bytes_to_long(RNG.read(192))
self.x2 = random.randrange(2, DH_MAX)
self.x3 = random.randrange(2, DH_MAX)
msg = [pow(self.g1, self.x2, DH1536_MODULUS)]
msg = [pow(self.g1, self.x2, DH_MODULUS)]
msg += proof_known_log(self.g1, self.x2, 1)
msg.append(pow(self.g1, self.x3, DH1536_MODULUS))
msg.append(pow(self.g1, self.x3, DH_MODULUS))
msg += proof_known_log(self.g1, self.x3, 2)
self.prog = SMPPROG_OK
......@@ -700,19 +704,19 @@ class SMPHandler:
self.secret = bytes_to_long(combSecret)
msg = [pow(self.g1, self.x2, DH1536_MODULUS)]
msg = [pow(self.g1, self.x2, DH_MODULUS)]
msg += proof_known_log(self.g1, self.x2, 3)
msg.append(pow(self.g1, self.x3, DH1536_MODULUS))
msg.append(pow(self.g1, self.x3, DH_MODULUS))
msg += proof_known_log(self.g1, self.x3, 4)
r = bytes_to_long(RNG.read(192))
r = random.randrange(2, DH_MAX)
self.p = pow(self.g3, r, DH1536_MODULUS)
self.p = pow(self.g3, r, DH_MODULUS)
msg.append(self.p)
qb1 = pow(self.g1, r, DH1536_MODULUS)
qb2 = pow(self.g2, self.secret, DH1536_MODULUS)
self.q = qb1 * qb2 % DH1536_MODULUS
qb1 = pow(self.g1, r, DH_MODULUS)
qb2 = pow(self.g2, self.secret, DH_MODULUS)
self.q = qb1 * qb2 % DH_MODULUS
msg.append(self.q)
msg += self.proof_equal_coords(r, 5)
......@@ -721,11 +725,11 @@ class SMPHandler:
self.sendTLV(proto.SMP2TLV(msg), appdata=appdata)
def proof_equal_coords(self, r, v):
r1 = bytes_to_long(RNG.read(192))
r2 = bytes_to_long(RNG.read(192))
temp2 = pow(self.g1, r1, DH1536_MODULUS) \
* pow(self.g2, r2, DH1536_MODULUS) % DH1536_MODULUS
temp1 = pow(self.g3, r1, DH1536_MODULUS)
r1 = random.randrange(2, DH_MAX)
r2 = random.randrange(2, DH_MAX)
temp2 = pow(self.g1, r1, DH_MODULUS) \
* pow(self.g2, r2, DH_MODULUS) % DH_MODULUS
temp1 = pow(self.g3, r1, DH_MODULUS)
cb = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2))
c = bytes_to_long(cb)
......@@ -739,21 +743,21 @@ class SMPHandler:
def check_equal_coords(self, coords, v):
(p, q, c, d1, d2) = coords
temp1 = pow(self.g3, d1, DH1536_MODULUS) * pow(p, c, DH1536_MODULUS) \
% DH1536_MODULUS
temp1 = pow(self.g3, d1, DH_MODULUS) * pow(p, c, DH_MODULUS) \
% DH_MODULUS
temp2 = pow(self.g1, d1, DH1536_MODULUS) \
* pow(self.g2, d2, DH1536_MODULUS) \
* pow(q, c, DH1536_MODULUS) % DH1536_MODULUS
temp2 = pow(self.g1, d1, DH_MODULUS) \
* pow(self.g2, d2, DH_MODULUS) \
* pow(q, c, DH_MODULUS) % DH_MODULUS
cprime = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2))
return long_to_bytes(c) == cprime
return long_to_bytes(c, 32) == cprime
def proof_equal_logs(self, v):
r = bytes_to_long(RNG.read(192))
temp1 = pow(self.g1, r, DH1536_MODULUS)
temp2 = pow(self.qab, r, DH1536_MODULUS)
r = random.randrange(2, DH_MAX)
temp1 = pow(self.g1, r, DH_MODULUS)
temp2 = pow(self.qab, r, DH_MODULUS)
cb = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2))
c = bytes_to_long(cb)
......@@ -763,29 +767,29 @@ class SMPHandler:
def check_equal_logs(self, logs, v):
(r, c, d) = logs
temp1 = pow(self.g1, d, DH1536_MODULUS) \
* pow(self.g3o, c, DH1536_MODULUS) % DH1536_MODULUS
temp1 = pow(self.g1, d, DH_MODULUS) \
* pow(self.g3o, c, DH_MODULUS) % DH_MODULUS
temp2 = pow(self.qab, d, DH1536_MODULUS) \
* pow(r, c, DH1536_MODULUS) % DH1536_MODULUS
temp2 = pow(self.qab, d, DH_MODULUS) \
* pow(r, c, DH_MODULUS) % DH_MODULUS
cprime = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2))
return long_to_bytes(c) == cprime
return long_to_bytes(c, 32) == cprime
def proof_known_log(g, x, v):
r = bytes_to_long(RNG.read(192))
c = bytes_to_long(SHA256(struct.pack(b'B', v) + pack_mpi(pow(g, r, DH1536_MODULUS))))
r = random.randrange(2, DH_MAX)
c = bytes_to_long(SHA256(struct.pack(b'B', v) + pack_mpi(pow(g, r, DH_MODULUS))))
temp = x * c % SM_ORDER
return c, (r-temp) % SM_ORDER
def check_known_log(c, d, g, x, v):
gd = pow(g, d, DH1536_MODULUS)
xc = pow(x, c, DH1536_MODULUS)
gdxc = gd * xc % DH1536_MODULUS
return SHA256(struct.pack(b'B', v) + pack_mpi(gdxc)) == long_to_bytes(c)
gd = pow(g, d, DH_MODULUS)
xc = pow(x, c, DH_MODULUS)
gdxc = gd * xc % DH_MODULUS
return SHA256(struct.pack(b'B', v) + pack_mpi(gdxc)) == long_to_bytes(c, 32)
def invMod(n):
return pow(n, DH1536_MODULUS_2, DH1536_MODULUS)
return pow(n, DH_MODULUS_2, DH_MODULUS)
class InvalidParameterError(RuntimeError):
pass
......@@ -19,14 +19,16 @@
from __future__ import unicode_literals
import base64
import logging
import struct
from potr.utils import pack_mpi, read_mpi, pack_data, read_data, unpack
OTRTAG = b'?OTR'
MESSAGE_TAG_BASE = b' \t \t\t\t\t \t \t \t '
MESSAGE_TAG_V1 = b' \t \t \t '
MESSAGE_TAG_V2 = b' \t\t \t '
MESSAGE_TAGS = {
1:b' \t \t \t ',
2:b' \t\t \t ',
3:b' \t\t \t\t',
}
MSGTYPE_NOTOTR = 0
MSGTYPE_TAGGEDPLAINTEXT = 1
......@@ -62,6 +64,8 @@ def registermessage(cls):
def registertlv(cls):
if not hasattr(cls, 'parsePayload'):
raise TypeError('registered tlv types need parsePayload()')
if cls.typ is None:
raise TypeError('registered tlv type needs type ID')
tlvClasses[cls.typ] = cls
return cls
......@@ -84,16 +88,6 @@ class OTRMessage(object):
__slots__ = ['payload']
version = 0x0002
msgtype = 0
def __init__(self, payload):
self.payload = payload
def getPayload(self):
return self.payload
def __bytes__(self):
data = struct.pack(b'!HB', self.version, self.msgtype) \
+ self.getPayload()
return b'?OTR:' + base64.b64encode(data) + b'.'
def __eq__(self, other):
if not isinstance(other, self.__class__):
......@@ -110,6 +104,7 @@ class OTRMessage(object):
class Error(OTRMessage):
__slots__ = ['error']
def __init__(self, error):
super(Error, self).__init__()
self.error = error
def __repr__(self):
......@@ -119,56 +114,58 @@ class Error(OTRMessage):
return b'?OTR Error:' + self.error
class Query(OTRMessage):
__slots__ = ['v1', 'v2']
def __init__(self, v1, v2):
self.v1 = v1
self.v2 = v2
__slots__ = ['versions']
def __init__(self, versions=set()):
super(Query, self).__init__()
self.versions = versions
@classmethod
def parse(cls, data):
v2 = False
v1 = False
if len(data) > 0 and data[0:1] == b'?':
data = data[1:]
v1 = True
if len(data) > 0 and data[0:1] == b'v':
for c in data[1:]:
if c == b'2'[0]:
v2 = True
return cls(v1, v2)
if not isinstance(data, bytes):
raise TypeError('can only parse bytes')
udata = data.decode('ascii', errors='replace')
versions = set()
if len(udata) > 0 and udata[0] == '?':
udata = udata[1:]
versions.add(1)
if len(udata) > 0 and udata[0] == 'v':
versions.update(( int(c) for c in udata if c.isdigit() ))
return cls(versions)
def __repr__(self):
return '<proto.Query(v1=%r,v2=%r)>'%(self.v1,self.v2)
return '<proto.Query(versions=%r)>' % (self.versions)
def __bytes__(self):
d = b'?OTR'
if self.v1:
if 1 in self.versions:
d += b'?'
d += b'v'
if self.v2:
d += b'2'
# in python3 there is only int->unicode conversion
# so I convert to unicode and encode it to a byte string
versions = [ '%d' % v for v in self.versions if v != 1 ]
d += ''.join(versions).encode('ascii')
d += b'?'
return d
class TaggedPlaintext(Query):
__slots__ = ['msg']
def __init__(self, msg, v1, v2):
def __init__(self, msg, versions):
super(TaggedPlaintext, self).__init__(versions)
self.msg = msg
self.v1 = v1
self.v2 = v2
def __bytes__(self):
data = self.msg + MESSAGE_TAG_BASE
if self.v1:
data += MESSAGE_TAG_V1
if self.v2:
data += MESSAGE_TAG_V2
for v in self.versions:
data += MESSAGE_TAGS[v]
return data
def __repr__(self):
return '<proto.TaggedPlaintext(v1={v1!r},v2={v2!r},msg={msg!r})>' \
.format(v1=self.v1, v2=self.v2, msg=self.msg)
return '<proto.TaggedPlaintext(versions={versions!r},msg={msg!r})>' \
.format(versions=self.versions, msg=self.msg)
@classmethod
def parse(cls, data):
......@@ -177,21 +174,18 @@ class TaggedPlaintext(Query):
raise TypeError(
'this is not a tagged plaintext ({0!r:.20})'.format(data))
v1 = False
v2 = False
tags = [ data[i:i+8] for i in range(tagPos, len(data), 8) ]
for tag in tags:
if not tag.isspace():
break
v1 |= tag == MESSAGE_TAG_V1
v2 |= tag == MESSAGE_TAG_V2
versions = set([ version for version, tag in MESSAGE_TAGS.items() if tag
in tags ])
return TaggedPlaintext(data[:tagPos], v1, v2)
return TaggedPlaintext(data[:tagPos], versions)
class GenericOTRMessage(OTRMessage):
__slots__ = ['data']
fields = []
def __init__(self, *args):
super(GenericOTRMessage, self).__init__()
if len(args) != len(self.fields):
raise TypeError('%s needs %d arguments, got %d' %
(self.__class__.__name__, len(self.fields), len(args)))
......@@ -213,6 +207,11 @@ class GenericOTRMessage(OTRMessage):
self.__getattr__(attr) # existence check
self.data[attr] = val
def __bytes__(self):
data = struct.pack(b'!HB', self.version, self.msgtype) \
+ self.getPayload()
return b'?OTR:' + base64.b64encode(data) + b'.'
def __repr__(self):
name = self.__class__.__name__
data = ''
......@@ -224,11 +223,10 @@ class GenericOTRMessage(OTRMessage):
def parsePayload(cls, data):
data = base64.b64decode(data)
args = []
for k, ftype in cls.fields:
for _, ftype in cls.fields:
if ftype == 'data':
value, data = read_data(data)
elif isinstance(ftype, bytes):
size = int(struct.calcsize(ftype))
value, data = unpack(ftype, data)
elif isinstance(ftype, int):
value, data = data[:ftype], data[ftype:]
......@@ -251,26 +249,24 @@ class GenericOTRMessage(OTRMessage):
class AKEMessage(GenericOTRMessage):
__slots__ = []
pass
@registermessage
class DHCommit(AKEMessage):
__slots__ = []
msgtype = 0x02
fields = [('encgx','data'), ('hashgx','data'), ]
fields = [('encgx', 'data'), ('hashgx', 'data'), ]
@registermessage
class DHKey(AKEMessage):
__slots__ = []
msgtype = 0x0a
fields = [('gy','data'), ]
fields = [('gy', 'data'), ]
@registermessage
class RevealSig(AKEMessage):
__slots__ = []
msgtype = 0x11
fields = [('rkey','data'), ('encsig','data'), ('mac',20),]
fields = [('rkey', 'data'), ('encsig', 'data'), ('mac', 20),]
def getMacedData(self):
p = self.encsig
......@@ -280,7 +276,7 @@ class RevealSig(AKEMessage):
class Signature(AKEMessage):
__slots__ = []
msgtype = 0x12
fields = [('encsig','data'), ('mac',20)]
fields = [('encsig', 'data'), ('mac', 20)]
def getMacedData(self):
p = self.encsig
......@@ -290,8 +286,9 @@ class Signature(AKEMessage):
class DataMessage(GenericOTRMessage):
__slots__ = []
msgtype = 0x03
fields = [('flags',b'!B'), ('skeyid',b'!I'), ('rkeyid',b'!I'), ('dhy','data'),
('ctr',8), ('encmsg','data'), ('mac',20), ('oldmacs','data'), ]
fields = [('flags', b'!B'), ('skeyid', b'!I'), ('rkeyid', b'!I'),
('dhy', 'data'), ('ctr', 8), ('encmsg', 'data'), ('mac', 20),
('oldmacs', 'data'), ]
def getMacedData(self):
return struct.pack(b'!HB', self.version, self.msgtype) + \
......@@ -300,6 +297,10 @@ class DataMessage(GenericOTRMessage):
@bytesAndStrings
class TLV(object):
__slots__ = []
typ = None
def getPayload(self):
raise NotImplementedError
def __repr__(self):
val = self.getPayload()
......@@ -330,11 +331,28 @@ class TLV(object):
def __neq__(self, other):
return not self.__eq__(other)
@registertlv
class PaddingTLV(TLV):
typ = 0
__slots__ = ['padding']
def __init__(self, padding):
super(PaddingTLV, self).__init__()
self.padding = padding
def getPayload(self):
return self.padding
@classmethod
def parsePayload(cls, data):
return cls(data)
@registertlv
class DisconnectTLV(TLV):
typ = 1
def __init__(self):
pass
super(DisconnectTLV, self).__init__()
def getPayload(self):
return b''
......@@ -348,8 +366,14 @@ class DisconnectTLV(TLV):
class SMPTLV(TLV):
__slots__ = ['mpis']
def __init__(self, mpis=[]):
dlen = None
def __init__(self, mpis=None):
super(SMPTLV, self).__init__()
if mpis is None:
mpis = []
if self.dlen is None:
raise TypeError('no amount of mpis specified in dlen')
if len(mpis) != self.dlen:
raise TypeError('expected {0} mpis, got {1}'
.format(self.dlen, len(mpis)))
......@@ -366,7 +390,7 @@ class SMPTLV(TLV):
mpis = []
if cls.dlen > 0:
count, data = unpack(b'!I', data)
for i in range(count):
for _ in range(count):
n, data = read_mpi(data)
mpis.append(n)
if len(data) > 0:
......@@ -419,3 +443,23 @@ class SMPABORTTLV(SMPTLV):
def getPayload(self):
return b''
@registertlv
class ExtraKeyTLV(TLV):
typ = 8
__slots__ = ['appid', 'appdata']
def __init__(self, appid, appdata):
super(ExtraKeyTLV, self).__init__()
self.appid = appid
self.appdata = appdata
if appdata is None:
self.appdata = b''
def getPayload(self):
return self.appid + self.appdata
@classmethod
def parsePayload(cls, data):
return cls(data[:4], data[4:])
......@@ -43,11 +43,12 @@ def bytes_to_long(b):
s += byte_to_long(b[i:i+1]) << 8*(l-i-1)
return s
def long_to_bytes(l):
def long_to_bytes(l, n=0):
b = b''
while l != 0:
while l != 0 or n > 0:
b = long_to_byte(l & 0xff) + b
l >>= 8
n -= 1
return b
def byte_to_long(b):
......
from gui_for_me import GuiForMe
gui_for_me/gui_for_me.png

818 B

# -*- coding: utf-8 -*-
import gtk
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log_calls
class GuiForMe(GajimPlugin):
@log_calls('GuiForMePlugin')
def init(self):
self.config_dialog = None # GuiForMePluginConfigDialog(self)
self.gui_extension_points = {
'chat_control_base': (self.connect_with_chat_control,
self.disconnect_from_chat_control),
'chat_control_base_update_toolbar': (self.update_button_state,
None)}
self.controls = []
@log_calls('GuiForMePlugin')
def connect_with_chat_control(self, chat_control):
if chat_control.widget_name != 'groupchat_control':
return
self.chat_control = chat_control
control = Base(self, self.chat_control)
self.controls.append(control)
@log_calls('GuiForMePlugin')
def disconnect_from_chat_control(self, chat_control):
for control in self.controls:
control.disconnect_from_chat_control()
self.controls = []
@log_calls('GuiForMePlugin')
def update_button_state(self, chat_control):
for base in self.controls:
if base.chat_control != chat_control:
continue
base.button.set_sensitive(chat_control.contact.show != 'offline' \
and gajim.connections[chat_control.account].connected > 0)
class Base(object):
def __init__(self, plugin, chat_control):
self.chat_control = chat_control
self.plugin = plugin
self.textview = self.chat_control.conv_textview
self.change_cursor = False
self.create_buttons()
def create_buttons(self):
# create /me button
actions_hbox = self.chat_control.xml.get_object('actions_hbox')
self.button = gtk.Button(label=None, stock=None, use_underline=True)
self.button.set_property('relief', gtk.RELIEF_NONE)
self.button.set_property('can-focus', False)
img = gtk.Image()
img_path = self.plugin.local_file_path('gui_for_me.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory = gtk.IconFactory()
factory.add('gui_for_me', iconset)
factory.add_default()
img.set_from_stock('gui_for_me', gtk.ICON_SIZE_MENU)
self.button.set_image(img)
self.button.set_tooltip_text(_('Insert /me to conversation input box,'
' at cursor position'))
send_button = self.chat_control.xml.get_object('send_button')
send_button_pos = actions_hbox.child_get_property(send_button,
'position')
actions_hbox.add_with_properties(self.button, 'position',
send_button_pos - 1, 'expand', False)
id_ = self.button.connect('clicked', self.on_me_button_clicked)
self.chat_control.handlers[id_] = self.button
self.button.show()
def on_me_button_clicked(self, widget):
"""
Insert /me to conversation input box, at cursor position
"""
message_buffer = self.chat_control.msg_textview.get_buffer()
message_buffer.insert_at_cursor('/me ')
self.chat_control.msg_textview.grab_focus()
def disconnect_from_chat_control(self):
actions_hbox = self.chat_control.xml.get_object('actions_hbox')
actions_hbox.remove(self.button)
[info]
name: GUI For Me
short_name: gui_for_me
version: 0.2
description: Gui for the '/me' command.
authors: BomberMan
copper
Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/GUIForMePlugin
max_gajim_version: 0.16.9
......@@ -3,10 +3,11 @@
import dbus
import datetime as dt
import gobject
import os
from common import gajim
from common import ged
from common import dbus_support
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from common.pep import ACTIVITIES
......@@ -24,11 +25,11 @@ class HamsterIntegrationPlugin(GajimPlugin):
@log_calls('HamsterIntegrationPlugin')
def init(self):
self.description = _('Integration with project hamster\n'
'see https://trac.gajim.org/ticket/6993\n'
'and http://projecthamster.wordpress.com/about/')
self.config_dialog = None
self.events_handlers = {}
if os.name == 'nt':
self.available_text = _('Plugin can\'t be run under Windows.')
self.activatable = False
@log_calls('HamsterIntegrationPlugin')
def activate(self):
......
[info]
name: Hamster integration
short_name: hamster_integration
version: 0.1.1
description: Integration with project hamster
see https://trac.gajim.org/ticket/6993
and http://projecthamster.wordpress.com/about/
version: 0.1.3
description: Integration with project hamster<br/>
see see <a href="https://trac.gajim.org/ticket/6993">ticket #6993</a>
and <a href="http://projecthamster.wordpress.com/about/">hamster project</a>
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/
max_gajim_version: 0.16.9
# simple redirect
from httpupload import HttpuploadPlugin
httpupload/httpupload.png

780 B

# -*- coding: utf-8 -*-
##
from common import demandimport
demandimport.enable()
demandimport.ignore += ['builtins', '__builtin__', 'PIL','_imp']
import gtk
import gobject
import os
import time
import base64
import tempfile
import urllib2
import mimetypes # better use the magic packet, but that's not a standard lib
import gtkgui_helpers
from Queue import Queue
try:
from PIL import Image
pil_available = True
except:
pil_available = False
from io import BytesIO
import base64
import binascii
from common import gajim
from common import ged
import chat_control
from plugins import GajimPlugin
from plugins.helpers import log_calls
import logging
from dialogs import FileChooserDialog, ImageChooserDialog, ErrorDialog
import nbxmpp
log = logging.getLogger('gajim.plugin_system.httpupload')
if os.name != 'nt':
try:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers.modes import GCM
encryption_available = True
except Exception as e:
log.debug(e)
encryption_available = False
else:
encryption_available = False
log.info('Cryptography not available on Windows for now')
# XEP-0363 (http://xmpp.org/extensions/xep-0363.html)
NS_HTTPUPLOAD = 'urn:xmpp:http:upload'
TAGSIZE = 16
jid_to_servers = {}
iq_ids_to_callbacks = {}
last_info_query = {}
max_thumbnail_size = 2048
max_thumbnail_dimension = 160
class HttpuploadPlugin(GajimPlugin):
@log_calls('HttpuploadPlugin')
def init(self):
self.config_dialog = None # HttpuploadPluginConfigDialog(self)
self.controls = []
self.events_handlers = {}
self.events_handlers['agent-info-received'] = (ged.PRECORE,
self.handle_agent_info_received)
self.events_handlers['raw-iq-received'] = (ged.PRECORE,
self.handle_iq_received)
self.gui_extension_points = {
'chat_control_base': (self.connect_with_chat_control,
self.disconnect_from_chat_control),
'chat_control_base_update_toolbar': (self.update_button_state,
None)}
self.first_run = True
def handle_iq_received(self, event):
global iq_ids_to_callbacks
id_ = event.stanza.getAttr("id")
if str(id_) in iq_ids_to_callbacks:
try:
iq_ids_to_callbacks[str(id_)](event.stanza)
except:
raise
finally:
del iq_ids_to_callbacks[str(id_)]
def handle_agent_info_received(self, event):
global jid_to_servers
if NS_HTTPUPLOAD in event.features and gajim.jid_is_transport(event.jid):
own_jid = gajim.get_jid_without_resource(str(event.stanza.getTo()))
jid_to_servers[own_jid] = event.jid # map own jid to upload component's jid
log.info(own_jid + " can do http uploads via component " + event.jid)
# update all buttons
for base in self.controls:
self.update_button_state(base.chat_control)
@log_calls('HttpuploadPlugin')
def connect_with_chat_control(self, control):
self.chat_control = control
base = Base(self, self.chat_control)
self.controls.append(base)
if self.first_run:
# ALT + U
gtk.binding_entry_add_signal(control.msg_textview,
gtk.keysyms.u, gtk.gdk.MOD1_MASK, 'mykeypress',
int, gtk.keysyms.u, gtk.gdk.ModifierType, gtk.gdk.MOD1_MASK)
self.first_run = False
self.update_button_state(self.chat_control)
@log_calls('HttpuploadPlugin')
def disconnect_from_chat_control(self, chat_control):
for control in self.controls:
control.disconnect_from_chat_control()
self.controls = []
@log_calls('HttpuploadPlugin')
def update_button_state(self, chat_control):
global jid_to_servers
global iq_ids_to_callbacks
global last_info_query
if gajim.connections[chat_control.account].connection == None and \
gajim.get_jid_from_account(chat_control.account) in jid_to_servers:
# maybe don't delete this and detect vanished upload components when actually trying to upload something
log.info("Deleting %s from jid_to_servers (disconnected)" % gajim.get_jid_from_account(chat_control.account))
del jid_to_servers[gajim.get_jid_from_account(chat_control.account)]
#pass
# query info at most every 60 seconds in case something goes wrong
if (not chat_control.account in last_info_query or \
last_info_query[chat_control.account] + 60 < time.time()) and \
not gajim.get_jid_from_account(chat_control.account) in jid_to_servers and \
gajim.account_is_connected(chat_control.account):
log.info("Account %s: Using dicovery to find jid of httpupload component" % chat_control.account)
id_ = gajim.get_an_id()
iq = nbxmpp.Iq(
typ='get',
to=gajim.get_server_from_jid(gajim.get_jid_from_account(chat_control.account)),
queryNS="http://jabber.org/protocol/disco#items"
)
iq.setID(id_)
def query_info(stanza):
global last_info_query
for item in stanza.getTag("query").getTags("item"):
id_ = gajim.get_an_id()
iq = nbxmpp.Iq(
typ='get',
to=item.getAttr("jid"),
queryNS="http://jabber.org/protocol/disco#info"
)
iq.setID(id_)
last_info_query[chat_control.account] = time.time()
gajim.connections[chat_control.account].connection.send(iq)
iq_ids_to_callbacks[str(id_)] = query_info
gajim.connections[chat_control.account].connection.send(iq)
#send disco query to main server jid
id_ = gajim.get_an_id()
iq = nbxmpp.Iq(
typ='get',
to=gajim.get_server_from_jid(gajim.get_jid_from_account(chat_control.account)),
queryNS="http://jabber.org/protocol/disco#info"
)
iq.setID(id_)
last_info_query[chat_control.account] = time.time()
gajim.connections[chat_control.account].connection.send(iq)
for base in self.controls:
if base.chat_control == chat_control:
is_supported = gajim.get_jid_from_account(chat_control.account) in jid_to_servers and \
gajim.connections[chat_control.account].connection != None
log.info("Account %s: httpupload is_supported: %s" % (str(chat_control.account), str(is_supported)))
if not is_supported:
text = _('Your server does not support http uploads')
image_text = text
else:
text = _('Send file via http upload')
image_text = _('Send image via http upload')
base.button.set_sensitive(is_supported)
base.button.set_tooltip_text(text)
base.image_button.set_sensitive(is_supported)
base.image_button.set_tooltip_text(image_text)
class Base(object):
def __init__(self, plugin, chat_control):
self.dlg = None
self.dialog_type = 'file'
self.keypress_id = chat_control.msg_textview.connect('mykeypress',
self.on_key_press)
self.plugin = plugin
self.encrypted_upload = False
self.chat_control = chat_control
actions_hbox = chat_control.xml.get_object('actions_hbox')
self.button = gtk.Button(label=None, stock=None, use_underline=True)
self.button.set_property('relief', gtk.RELIEF_NONE)
self.button.set_property('can-focus', False)
self.button.set_sensitive(False)
img = gtk.Image()
img.set_from_file(self.plugin.local_file_path('httpupload.png'))
self.button.set_image(img)
self.button.set_tooltip_text(_('Your server does not support http uploads'))
self.image_button = gtk.Button(label=None, stock=None, use_underline=True)
self.image_button.set_property('relief', gtk.RELIEF_NONE)
self.image_button.set_property('can-focus', False)
self.image_button.set_sensitive(False)
img = gtk.Image()
img.set_from_file(self.plugin.local_file_path('image.png'))
self.image_button.set_image(img)
self.image_button.set_tooltip_text(_('Your server does not support http uploads'))
send_button = chat_control.xml.get_object('send_button')
send_button_pos = actions_hbox.child_get_property(send_button,
'position')
actions_hbox.add_with_properties(self.button, 'position',
send_button_pos - 2, 'expand', False)
actions_hbox.add_with_properties(self.image_button, 'position',
send_button_pos - 1, 'expand', False)
file_id = self.button.connect('clicked', self.on_file_button_clicked)
image_id = self.image_button.connect('clicked', self.on_image_button_clicked)
chat_control.handlers[file_id] = self.button
chat_control.handlers[image_id] = self.image_button
chat_control.handlers[self.keypress_id] = chat_control.msg_textview
self.button.show()
self.image_button.show()
def on_key_press(self, widget, event_keyval, event_keymod):
# construct event instance from binding
event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
event.keyval = event_keyval
event.state = event_keymod
event.time = 0 # assign current time
if event.keyval != gtk.keysyms.u:
return
if event.state != gtk.gdk.MOD1_MASK: # ALT+u
return
is_supported = gajim.get_jid_from_account(self.chat_control.account) in jid_to_servers and \
gajim.connections[self.chat_control.account].connection != None
if not is_supported:
from dialogs import WarningDialog
WarningDialog('Warning', _('Your server does not support http uploads'),
transient_for=self.chat_control.parent_win.window)
return
self.on_file_button_clicked(widget)
def disconnect_from_chat_control(self):
actions_hbox = self.chat_control.xml.get_object('actions_hbox')
actions_hbox.remove(self.button)
actions_hbox.remove(self.image_button)
if self.keypress_id in self.chat_control.handlers and \
self.chat_control.handlers[self.keypress_id].handler_is_connected(self.keypress_id):
self.chat_control.handlers[self.keypress_id].disconnect(self.keypress_id)
del self.chat_control.handlers[self.keypress_id]
def encryption_activated(self):
if not encryption_available:
return False
jid = self.chat_control.contact.jid
account = self.chat_control.account
for plugin in gajim.plugin_manager.active_plugins:
if type(plugin).__name__ == 'OmemoPlugin':
omemo = plugin
break
if omemo:
state = omemo.get_omemo_state(account)
log.info('Encryption is: ' +
str(state.encryption.is_active(jid)))
return state.encryption.is_active(jid)
log.info('Encryption is: False / OMEMO not found')
return False
def on_file_dialog_ok(self, widget, path_to_file=None):
global jid_to_servers
try:
self.encrypted_upload = self.encryption_activated()
except Exception as e:
log.debug(e)
self.encrypted_upload = False
if not path_to_file:
path_to_file = self.dlg.get_filename()
if not path_to_file:
self.dlg.destroy()
return
path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
(path_to_file,))[0]
self.dlg.destroy()
if not os.path.exists(path_to_file):
return
if self.encrypted_upload:
filesize = os.path.getsize(path_to_file) + TAGSIZE # in bytes
else:
filesize = os.path.getsize(path_to_file)
invalid_file = False
msg = ''
if os.path.isfile(path_to_file):
stat = os.stat(path_to_file)
if stat[6] == 0:
invalid_file = True
msg = _('File is empty')
else:
invalid_file = True
msg = _('File does not exist')
if invalid_file:
ErrorDialog(_('Could not open file'), msg, transient_for=self.chat_control.parent_win.window)
return
mime_type = mimetypes.MimeTypes().guess_type(path_to_file)[0]
if not mime_type:
mime_type = 'application/octet-stream' # fallback mime type
log.info("Detected MIME Type of file: " + str(mime_type))
progress_messages = Queue(8)
progress_window = ProgressWindow(_('HTTP Upload'), _('Requesting HTTP Upload Slot...'), progress_messages, self.plugin)
def upload_file(stanza):
slot = stanza.getTag("slot")
if not slot:
progress_window.close_dialog()
log.error("got unexpected stanza: "+str(stanza))
error = stanza.getTag("error")
if error and error.getTag("text"):
ErrorDialog(_('Could not request upload slot'),
_('Got unexpected response from server: %s') % str(error.getTagData("text")),
transient_for=self.chat_control.parent_win.window)
else:
ErrorDialog(_('Could not request upload slot'),
_('Got unexpected response from server (protocol mismatch??)'),
transient_for=self.chat_control.parent_win.window)
return
try:
if self.encrypted_upload:
key = os.urandom(32)
iv = os.urandom(16)
data = StreamFileWithProgress(path_to_file,
"rb",
progress_window.update_progress,
self.encrypted_upload, key, iv)
else:
data = StreamFileWithProgress(path_to_file,
"rb",
progress_window.update_progress)
except:
progress_window.close_dialog()
ErrorDialog(_('Could not open file'),
_('Exception raised while opening file (see error log for more information)'),
transient_for=self.chat_control.parent_win.window)
raise # fill error log with useful information
put = slot.getTag("put")
get = slot.getTag("get")
if not put or not get:
progress_window.close_dialog()
log.error("got unexpected stanza: " + str(stanza))
ErrorDialog(_('Could not request upload slot'),
_('Got unexpected response from server (protocol mismatch??)'),
transient_for=self.chat_control.parent_win.window)
return
def upload_complete(response_code):
if response_code == 0:
return # Upload was aborted
if response_code >= 200 and response_code < 300:
log.info("Upload completed successfully")
xhtml = None
is_image = mime_type.split('/', 1)[0] == 'image'
if (not isinstance(self.chat_control, chat_control.ChatControl) or not self.chat_control.gpg_is_active) and \
self.dialog_type == 'image' and is_image and not self.encrypted_upload:
progress_messages.put(_('Calculating (possible) image thumbnail...'))
thumb = None
quality_steps = (100, 80, 60, 50, 40, 35, 30, 25, 23, 20, 18, 15, 13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
with open(path_to_file, 'rb') as content_file:
thumb = urllib2.quote(base64.standard_b64encode(content_file.read()), '')
if thumb and len(thumb) < max_thumbnail_size:
quality = 100
log.info("Image small enough (%d bytes), not resampling" % len(thumb))
elif pil_available:
log.info("PIL available, using it for image downsampling")
try:
for quality in quality_steps:
thumb = Image.open(path_to_file)
thumb.thumbnail((max_thumbnail_dimension, max_thumbnail_dimension), Image.ANTIALIAS)
output = BytesIO()
thumb.save(output, format='JPEG', quality=quality, optimize=True)
thumb = output.getvalue()
output.close()
thumb = urllib2.quote(base64.standard_b64encode(thumb), '')
log.debug("pil thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb)))
if len(thumb) < max_thumbnail_size:
break
except:
thumb = None
else:
thumb = None
if not thumb:
log.info("PIL not available, using GTK for image downsampling")
temp_file = None
try:
with open(path_to_file, 'rb') as content_file:
thumb = content_file.read()
loader = gtk.gdk.PixbufLoader()
loader.write(thumb)
loader.close()
pixbuf = loader.get_pixbuf()
scaled_pb = self.get_pixbuf_of_size(pixbuf, max_thumbnail_dimension)
handle, temp_file = tempfile.mkstemp(suffix='.jpeg', prefix='gajim_httpupload_scaled_tmp', dir=gajim.TMP)
log.debug("Saving temporary jpeg image to '%s'..." % temp_file)
os.close(handle)
for quality in quality_steps:
scaled_pb.save(temp_file, "jpeg", {"quality": str(quality)})
with open(temp_file, 'rb') as content_file:
thumb = content_file.read()
thumb = urllib2.quote(base64.standard_b64encode(thumb), '')
log.debug("gtk thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb)))
if len(thumb) < max_thumbnail_size:
break
except:
thumb = None
finally:
if temp_file:
os.unlink(temp_file)
if thumb:
if len(thumb) > max_thumbnail_size:
log.info("Couldn't compress image enough, not sending any thumbnail")
else:
log.info("Using thumbnail jpeg quality %d (image size: %d bytes)" % (quality, len(thumb)))
xhtml = '<body><br/><a href="%s"> <img alt="%s" src="data:image/png;base64,%s"/> </a></body>' % \
(get.getData(), get.getData(), thumb)
progress_window.close_dialog()
id_ = gajim.get_an_id()
def add_oob_tag():
pass
if self.encrypted_upload:
keyAndIv = '#' + binascii.hexlify(iv) + binascii.hexlify(key)
self.chat_control.send_message(message=get.getData() + keyAndIv, xhtml=None)
else:
self.chat_control.send_message(message=get.getData(), xhtml=xhtml)
self.chat_control.msg_textview.grab_focus()
else:
progress_window.close_dialog()
log.error("got unexpected http upload response code: " + str(response_code))
ErrorDialog(_('Could not upload file'),
_('Got unexpected http response code from server: ') + str(response_code),
transient_for=self.chat_control.parent_win.window)
def uploader():
progress_messages.put(_('Uploading file via HTTP...'))
try:
headers = {'User-Agent': 'Gajim %s' % gajim.version,
'Content-Type': mime_type}
request = urllib2.Request(put.getData().encode("utf-8"), data=data, headers=headers)
request.get_method = lambda: 'PUT'
log.debug("opening urllib2 upload request...")
transfer = urllib2.urlopen(request, timeout=30)
log.debug("urllib2 upload request done, response code: " + str(transfer.getcode()))
return transfer.getcode()
except UploadAbortedException:
log.info("Upload aborted")
except:
progress_window.close_dialog()
ErrorDialog(_('Could not upload file'),
_('Got unexpected exception while uploading file (see error log for more information)'),
transient_for=self.chat_control.parent_win.window)
raise # fill error log with useful information
return 0
log.info("Uploading file to '%s'..." % str(put.getData()))
log.info("Please download from '%s' later..." % str(get.getData()))
gajim.thread_interface(uploader, [], upload_complete)
is_supported = gajim.get_jid_from_account(self.chat_control.account) in jid_to_servers and \
gajim.connections[self.chat_control.account].connection != None
log.info("jid_to_servers of %s: %s ; connection: %s" % (gajim.get_jid_from_account(self.chat_control.account), str(jid_to_servers[gajim.get_jid_from_account(self.chat_control.account)]), str(gajim.connections[self.chat_control.account].connection)))
if not is_supported:
progress_window.close_dialog()
log.error("upload component vanished, account got disconnected??")
ErrorDialog(_('Your server does not support http uploads or you just got disconnected.\nPlease try to reconnect or reopen the chat window to fix this.'),
transient_for=self.chat_control.parent_win.window)
return
# create iq for slot request
id_ = gajim.get_an_id()
iq = nbxmpp.Iq(
typ='get',
to=jid_to_servers[gajim.get_jid_from_account(self.chat_control.account)],
queryNS=None
)
iq.setID(id_)
request = iq.addChild(
name="request",
namespace=NS_HTTPUPLOAD
)
filename = request.addChild(
name="filename",
)
filename.addData(os.path.basename(path_to_file))
size = request.addChild(
name="size",
)
size.addData(filesize)
content_type = request.addChild(
name="content-type",
)
content_type.addData(mime_type)
# send slot request and register callback
log.debug("sending httpupload slot request iq...")
iq_ids_to_callbacks[str(id_)] = upload_file
gajim.connections[self.chat_control.account].connection.send(iq)
self.chat_control.msg_textview.grab_focus()
def on_file_button_clicked(self, widget):
self.dialog_type = 'file'
self.dlg = FileChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None,
title_text = _('Choose file to send'), action = gtk.FILE_CHOOSER_ACTION_OPEN,
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK),
default_response = gtk.RESPONSE_OK,)
def on_image_button_clicked(self, widget):
self.dialog_type = 'image'
self.dlg = ImageChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None)
def get_pixbuf_of_size(self, pixbuf, size):
# Creates a pixbuf that fits in the specified square of sizexsize
# while preserving the aspect ratio
# Returns scaled_pixbuf
image_width = pixbuf.get_width()
image_height = pixbuf.get_height()
if image_width > image_height:
if image_width > size:
image_height = int(size / float(image_width) * image_height)
image_width = int(size)
else:
if image_height > size:
image_width = int(size / float(image_height) * image_width)
image_height = int(size)
crop_pixbuf = pixbuf.scale_simple(image_width, image_height,
gtk.gdk.INTERP_BILINEAR)
return crop_pixbuf
class StreamFileWithProgress(file):
def __init__(self, path, mode, callback=None,
encrypted_upload=False, key=None, iv=None, *args):
file.__init__(self, path, mode)
self.encrypted_upload = encrypted_upload
self.seek(0, os.SEEK_END)
if self.encrypted_upload:
self.encryptor = Cipher(
algorithms.AES(key),
GCM(iv),
backend=default_backend()).encryptor()
self._total = self.tell() + TAGSIZE
else:
self._total = self.tell()
self.seek(0)
self._callback = callback
self._args = args
self._seen = 0
def __len__(self):
return self._total
def read(self, size):
if self.encrypted_upload:
data = file.read(self, size)
if len(data) > 0:
data = self.encryptor.update(data)
self._seen += len(data)
if (self._seen + TAGSIZE) == self._total:
self.encryptor.finalize()
data += self.encryptor.tag
self._seen += TAGSIZE
if self._callback:
self._callback(self._seen, self._total, *self._args)
return data
else:
data = file.read(self, size)
self._seen += len(data)
if self._callback:
self._callback(self._seen, self._total, *self._args)
return data
class ProgressWindow:
def __init__(self, title_text, during_text, messages_queue, plugin):
self.plugin = plugin
self.xml = gtkgui_helpers.get_gtk_builder(self.plugin.local_file_path('upload_progress_dialog.ui'))
self.messages_queue = messages_queue
self.dialog = self.xml.get_object('progress_dialog')
self.label = self.xml.get_object('label')
self.cancel_button = self.xml.get_object('close_button')
self.label.set_markup('<big>' + during_text + '</big>')
self.progressbar = self.xml.get_object('progressbar')
self.progressbar.set_text("")
self.dialog.set_title(title_text)
self.dialog.set_geometry_hints(min_width=400, min_height=96)
self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
self.dialog.show_all()
self.xml.connect_signals(self)
self.stopped = False
self.pulse_progressbar_timeout_id = gobject.timeout_add(100, self.pulse_progressbar)
self.process_messages_queue_timeout_id = gobject.timeout_add(100, self.process_messages_queue)
def pulse_progressbar(self):
if self.dialog:
self.progressbar.pulse()
return True # loop forever
return False
def process_messages_queue(self):
if not self.messages_queue.empty():
self.label.set_markup('<big>' + self.messages_queue.get() + '</big>')
if self.dialog:
return True # loop forever
return False
def on_progress_dialog_delete_event(self, widget, event):
self.stopped = True
if self.pulse_progressbar_timeout_id:
gobject.source_remove(self.pulse_progressbar_timeout_id)
gobject.source_remove(self.process_messages_queue_timeout_id)
def on_cancel(self, widget):
self.stopped = True
if self.pulse_progressbar_timeout_id:
gobject.source_remove(self.pulse_progressbar_timeout_id)
gobject.source_remove(self.process_messages_queue_timeout_id)
self.dialog.destroy()
def update_progress(self, seen, total):
if self.stopped == True:
raise UploadAbortedException
if self.pulse_progressbar_timeout_id:
gobject.source_remove(self.pulse_progressbar_timeout_id)
self.pulse_progressbar_timeout_id = None
pct = (float(seen) / total) * 100.0
self.progressbar.set_fraction(float(seen) / total)
self.progressbar.set_text(str(int(pct)) + "%")
log.debug('upload progress: %.2f%% (%d of %d bytes)' % (pct, seen, total))
def close_dialog(self):
self.on_cancel(None)
class UploadAbortedException(Exception):
def __str__(self):
return "Upload Aborted"
httpupload/image.png

666 B

[info]
name: HttpUpload
short_name: httpupload
version: 0.4.0
description: This plugin is designed to send a file to a contact or muc by using httpupload.<br/>
Your server must support <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP Upload</a>.<br/>
Conversations supported this.<br/>
If the receiving side supports <a href="http://xmpp.org/extensions/xep-0071.html">XEP-0071: XHTML-IM</a>
and maintains the scheme data: URI, a thumbnail image is send along the link to the full size image.
If the receiving side doesn't support this, only a text message containing the link to the image is send.
authors: Thilo Molitor <thilo@eightysoft.de>
Anders Sandblad <runeson@gmail.com>
Philipp Hörist <philipp@hoerist.com>
homepage: https://trac-plugins.gajim.org/wiki/HttpUploadPlugin
min_gajim_version: 0.16.5
max_gajim_version: 0.16.9
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="2.16"/>
<object class="GtkDialog" id="progress_dialog">
<property name="can_focus">True</property>
<property name="resizable">False</property>
<property name="icon_name">upload-media</property>
<property name="type_hint">dialog</property>
<signal name="delete-event" handler="on_progress_dialog_delete_event" swapped="no"/>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">2</property>
<property name="bottom_padding">4</property>
<property name="left_padding">0</property>
<property name="right_padding">3</property>
<child>
<object class="GtkButton" id="close_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_cancel" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">8</property>
<property name="bottom_padding">4</property>
<property name="left_padding">8</property>
<property name="right_padding">8</property>
<child>
<object class="GtkLabel" id="label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">4</property>
<property name="bottom_padding">4</property>
<property name="left_padding">8</property>
<property name="right_padding">8</property>
<child>
<object class="GtkProgressBar" id="progressbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pulse_step">0.10000000149</property>
<property name="show_text">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
......@@ -16,11 +16,6 @@ NS_XHTML_IM = 'http://jabber.org/protocol/xhtml-im' # XEP-0071
class ImagePlugin(GajimPlugin):
@log_calls('ImagePlugin')
def init(self):
self.description = _('This plugin is designed to send '
'a small(0 - 40 kb) graphic image to your contact.\n'
'Client on the other side must support XEP-0071: XHTML-IM'
' and maintain the scheme data: URI.\n'
'Psi+ and Jabbim supported this.')
self.config_dialog = None # ImagePluginConfigDialog(self)
self.controls = []
self.gui_extension_points = {
......@@ -136,8 +131,8 @@ class Base(object):
msg = 'HTML image'
extension = os.path.splitext(os.path.split(path_to_file)[1])[1] \
.lower()[1:]
xhtml = ' <img alt="img" src="data:image/%s;base64,%s"/>' % (
extension, img)
xhtml = '<body><br/> <img alt="img" src="data:image/%s;base64,%s"/> \
</body>' % (extension, img)
self.chat_control.send_message(message=msg, xhtml=xhtml)
self.chat_control.msg_textview.grab_focus()
......
[info]
name: Image
short_name: image
version: 0.5
description: This plugin is designed to send a small(0 - 40 kb) graphic image to your contact.
Client on the other side must support XEP-0071: XHTML-IM and maintain the scheme data: URI.
version: 0.5.1
description: This plugin is designed to send a small(0 - 40 kb) graphic image to your contact.<br/>
Client on the other side must support <a href="http://xmpp.org/extensions/xep-0071.html">XEP-0071: XHTML-IM</a> and maintain the scheme data: URI.<br/>
Psi+ and Jabbim supported this.
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/ImagePlugin
max_gajim_version: 0.16.9
......@@ -6,6 +6,7 @@
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="border_width">9</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkCheckButton" id="checkbutton">
......
[info]
name: Juick
short_name: Juick
version: 0.9.3
description: Clickable Juick links , Juick nicks, preview Juick picturs.
version: 0.9.6
description: Clickable Juick links , Juick nicks, preview Juick picturs.<br/>
The key combination alt + up in the textbox allow insert the number of last message (comment or topic).
authors: Denis Fomin <fominde@gmail.com>
evgen <drujebober@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/JuickPlugin
min_gajim_version: 0.16
max_gajim_version: 0.16.9
......@@ -9,6 +9,9 @@ from string import upper
from string import rstrip
import locale
import sqlite3
import json
import urllib2
import gobject
from common import helpers
from common import gajim
......@@ -18,26 +21,17 @@ from plugins.gui import GajimPluginConfigDialog
from conversation_textview import TextViewImage
import gtkgui_helpers
nb_xmpp = False
import common.xmpp
if not dir(common.xmpp):
import nbxmpp
nb_xmpp = True
class JuickPlugin(GajimPlugin):
@log_calls('JuickPlugin')
def init(self):
self.description = _('Clickable Juick links , Juick nicks, '
'preview Juick picturs.\nThe key combination alt + up in the '
'textbox allow insert the number of last message '
'(comment or topic).')
self.config_dialog = JuickPluginConfigDialog(self)
self.gui_extension_points = {
'chat_control_base': (self.connect_with_chat_control,
self.disconnect_from_chat_control),
'print_special_text': (self.print_special_text,
self.print_special_text1),}
self.print_special_text1),
'chat_control_base_update_toolbar': (self.update_button_state,
None)}
self.config_default_values = {'SHOW_AVATARS': (False, ''),
'AVATAR_SIZE': (20, 'Avatar size(10-32)'),
'avatars_old': (2419200, 'Update avatars '
......@@ -87,19 +81,30 @@ class JuickPlugin(GajimPlugin):
if self.conn:
self.conn.close()
def print_special_text(self, tv, special_text, other_tags, graphics=True):
def print_special_text(self, tv, special_text, other_tags, graphics=True,
iter_=None):
for control in self.controls:
if control.chat_control.conv_textview != tv:
continue
control.print_special_text(special_text, other_tags, graphics=True)
control.print_special_text(special_text, other_tags, graphics,
iter_)
def print_special_text1(self, chat_control, special_text, other_tags=None,
graphics=True):
graphics=True, iter_=None):
for control in self.controls:
if control.chat_control == chat_control:
control.disconnect_from_chat_control()
self.controls.remove(control)
def update_button_state(self, chat_control):
for base in self.controls:
if base.chat_control != chat_control:
continue
state = gajim.connections[chat_control.account].connected > 0
base.button.set_sensitive(state)
base.tag_button.set_sensitive(state)
class Base(object):
def __init__(self, plugin, chat_control):
self.last_juick_num = ''
......@@ -317,20 +322,21 @@ class Base(object):
if kind == 'juick_nick':
self.on_insert(widget, 'PM %s' % word.rstrip(':'))
def print_special_text(self, special_text, other_tags, graphics=True):
def print_special_text(self, special_text, other_tags, graphics=True,
iter__=None):
if gajim.interface.sharp_slash_re.match(special_text):
# insert post num #123456//
buffer_, iter_, tag = self.get_iter_and_tag('sharp_slash')
buffer_.insert_with_tags(iter_, special_text, tag)
buffer_.insert_with_tags(iter__, special_text, tag)
self.last_juick_num = special_text
self.textview.plugin_modified = True
return
if gajim.interface.juick_nick_re.match(special_text):
# insert juick nick @nickname////
buffer_, iter_, tag = self.get_iter_and_tag('juick_nick')
mark = buffer_.create_mark(None, iter_, True)
mark = buffer_.create_mark(None, iter__, True)
nick = special_text[1:].rstrip(':')
buffer_.insert_with_tags(iter_, special_text, tag)
buffer_.insert_with_tags(iter__, special_text, tag)
# insert avatars
if not self.plugin.config['SHOW_AVATARS']:
self.textview.plugin_modified = True
......@@ -365,45 +371,36 @@ class Base(object):
if not pixbuf:
self.textview.plugin_modified = True
return
end_iter = buffer_.get_iter_at_mark(mark)
anchor = buffer_.create_child_anchor(end_iter)
img = TextViewImage(anchor, nick)
img.set_from_pixbuf(pixbuf)
img.show()
self.textview.tv.add_child_at_anchor(img, anchor)
gobject.idle_add(self.set_avatar, mark, nick, pixbuf)
self.textview.plugin_modified = True
return
else:
# nick not in the db
id_ = conn.connection.getAnID()
to = 'juick@juick.com'
if not nb_xmpp:
iq = common.xmpp.Iq('get', to=to)
else:
iq = nbxmpp.Iq('get', to=to)
a = iq.addChild(name='query',
namespace='http://juick.com/query#users')
a.addChild(name='user', namespace='http://juick.com/user',
attrs={'uname': nick})
iq.setID(id_)
conn.connection.SendAndCallForResponse(iq, self._on_response,
{'mark': mark, 'special_text': special_text})
gobject.idle_add(self.get_new_avatar, mark, nick)
self.textview.plugin_modified = True
return
if gajim.interface.juick_pic_re.match(special_text) and \
self.plugin.config['SHOW_PREVIEW']:
# show pics preview
buffer_, iter_, tag = self.get_iter_and_tag('url')
mark = buffer_.create_mark(None, iter_, True)
buffer_.insert_with_tags(iter_, special_text, tag)
mark = buffer_.create_mark(None, buffer_.get_end_iter(), True)
uid = special_text.split('/')[-1]
url = "http://i.juick.com/photos-512/%s" % uid
gajim.thread_interface(self.insert_pic_preview, [mark, special_text,
url])
url, tag])
self.textview.plugin_modified = True
return
def insert_pic_preview(self, mark, special_text, url):
def set_avatar(self, mark, nick, pixbuf):
buffer_ = mark.get_buffer()
end_iter = buffer_.get_iter_at_mark(mark)
anchor = buffer_.create_child_anchor(end_iter)
img = TextViewImage(anchor, nick)
img.set_from_pixbuf(pixbuf)
img.show()
self.textview.tv.add_child_at_anchor(img, anchor)
def insert_pic_preview(self, mark, special_text, url, tag):
pixbuf = self.get_pixbuf_from_url( url, self.plugin.config[
'PREVIEW_SIZE'])
if pixbuf:
......@@ -415,6 +412,9 @@ class Base(object):
img.set_from_pixbuf(pixbuf)
img.show()
self.textview.tv.add_child_at_anchor(img, anchor)
buffer_ = mark.get_buffer()
end_iter = buffer_.get_iter_at_mark(mark)
buffer_.insert_with_tags(end_iter, special_text, tag)
def get_iter_and_tag(self, tag_name):
buffer_ = self.textview.tv.get_buffer()
......@@ -422,33 +422,29 @@ class Base(object):
tag = ttable.lookup(tag_name)
return buffer_, buffer_.get_end_iter(), tag
def _on_response(self, a, resp, **kwargs):
# insert avatar to text mark
mark = kwargs['mark']
def get_new_avatar(self, mark, nick):
try:
req = urllib2.Request('http://api.juick.com/users?uname=%s' % nick)
response = urllib2.urlopen(req)
j = json.load(response)
_id = str(j[0]['uid'])
except urllib2.HTTPError, e:
return
pixbuf = self.get_avatar(_id, nick)
buffer_ = mark.get_buffer()
end_iter = buffer_.get_iter_at_mark(mark)
tags = resp.getTag('query')
nick = kwargs['special_text'][1:].rstrip(':')
if tags:
user = tags.getTag('user')
if not user:
return
uid = user.getAttr('uid')
pixbuf = self.get_avatar(uid, nick)
anchor = buffer_.create_child_anchor(end_iter)
img = TextViewImage(anchor, nick)
img.set_from_pixbuf(pixbuf)
img.show()
self.textview.tv.add_child_at_anchor(img, anchor)
anchor = buffer_.create_child_anchor(end_iter)
img = TextViewImage(anchor, nick)
img.set_from_pixbuf(pixbuf)
img.show()
self.textview.tv.add_child_at_anchor(img, anchor)
def get_avatar(self, uid, nick, need_check=None):
# search avatar in cache or download from juick.com
pic = uid + '.png'
pic_path = os.path.join(self.plugin.cache_path, pic)
pic_path = pic_path.decode(locale.getpreferredencoding())
url = 'http://i.juick.com/as/%s.png' % uid
url = 'http://api.juick.com/avatar?uname=%s&size=32' % nick
if need_check and os.path.isfile(pic_path):
max_old = self.plugin.config['avatars_old']
if (time.time() - os.stat(pic_path).st_mtime) < max_old:
......