# -*- coding: utf-8 -*-
# This file is part of emesene.
#
# Emesene is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# emesene is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with emesene; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
'''this module includes all MSNSLP related stuff'''
import random
class SLPMessage(object):
'''Represents a MSNSLP/1.0 message
No session initiation here, it's just a higher level interface
to build/parse SLP messages'''
TEMPLATE = '%s\r\n' \
'To: <msnmsgr:%s>\r\n' \
'From: <msnmsgr:%s>\r\n' \
'Via: MSNSLP/1.0/TLP ;branch={%s}\r\n' \
'CSeq: %s \r\n' \
'Call-ID: {%s}\r\n' \
'Max-Forwards: %s\r\n' \
'Content-Type: %s\r\n' \
'Content-Length: %s\r\n' \
'\r\n' \
'%s'
INVITE_METHOD = 'INVITE MSNMSGR:%s MSNSLP/1.0'
BYE_METHOD = 'BYE MSNMSGR:%s MSNSLP/1.0'
ACK_METHOD = 'ACK MSNMSGR:%s MSNSLP/1.0'
OK_STATUS = 'MSNSLP/1.0 200 OK'
DECLINE_STATUS = 'MSNSLP/1.0 603 Decline'
CONTENT_SESSION_REQ = 'application/x-msnmsgr-sessionreqbody'
CONTENT_SESSION_CLOSE = 'application/x-msnmsgr-sessionclosebody'
CONTENT_TRANS_RESP = 'application/x-msnmsgr-transrespbody'
CONTENT_TRANS_REQ = 'application/x-msnmsgr-transreqbody'
EUFGUID_DISPLAY = '{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}'
EUFGUID_FILE = '{5D3E02AB-6190-11D3-BBBB-00C04F795683}'
# do you want to see my webcam?
EUFGUID_WEBCAM = '{4BD96FC0-AB17-4425-A14A-439185962DC8}'
# can i see your webcam?
EUFGUID_WEBCAM_ASK = '{1C9AA97E-9C05-4583-A3BD-908A196F1E92}'
#EUFGUID_GAMES = '{6A13AF9C-5308-4F35-923A-67E8DDA40C2F}'
def __init__(self, message=None, session=None):
'''initializes most attributes to empty'''
self.method = ''
self.to_ = ''
self.from_ = ''
self.via = ''
self.cseq = '0'
self.call_id = ''
self.max_forwards = '0'
self.content_type = ''
self.body = {}
if session:
self.to_ = session.to_
self.from_ = session.from_
self.via = session.via
self.call_id = session.call_id
elif message:
self.parse(message)
def parse(self, message):
'''parses a message and sets the attributes to rebuild it
the param must be without binary headers
SLPErrors should me handled replying 500 internal server error'''
try:
header, body = message.split('\r\n\r\n', 1)
except ValueError:
raise SLPError("The message/body separator is missing")
header = header.split("\r\n")
body = body.split("\r\n")
# body is easy, ok?
self.body = to_dict(body)
try:
self.method = header.pop(0)
except IndexError:
raise SLPError("SLP Message header is empty "
"(or starts with \\r\\n\\r\\n)")
headerparams = to_dict(header)
try:
self.to_ = between(headerparams['To'], "<msnmsgr:", ">")
self.from_ = between(headerparams['From'], "<msnmsgr:", ">")
self.via = between(headerparams['Via'], ";branch={", "}")
self.cseq = headerparams['CSeq'].strip()
self.call_id = between(headerparams['Call-ID'], "{", "}")
self.max_forwards = headerparams['Max-Forwards'].strip()
self.content_type = headerparams['Content-Type'].strip()
except KeyError:
raise SLPError("Some of the required MSNSLP fields are missing")
def __str__(self):
'''return the message as a string'''
# i take context apart to have it at the end
context = None
if 'Context' in self.body:
context = self.body.pop('Context') # take it out
items = self.body.items()
items.sort()
if context: # add it back
items.append(('Context', context))
self.body['Context'] = context
lines = []
for key, value in items:
lines.append(str(key) + ': ' + str(value))
body = '\r\n'.join(lines) + '\r\n\r\n\0'
return SLPMessage.TEMPLATE % (self.method, self.to_, self.from_,
self.via, self.cseq, self.call_id, self.max_forwards,
self.content_type, len(body), body)
# raised on SLP message parse error
class SLPError(Exception):
'''A simple, named, easy to catch exception for all SLP parsing errors'''
pass
### helper functions
def random_id(): #XXX
'''return a random id according to msnp2p rules'''
guid = []
guid.append('%04X' % random.randint( 0x1111 , 0xAAFF ))
guid.append('%04X' % random.randint( 0x1111 , 0xAAFF ) + "-")
guid.append('%04X' % random.randint( 0x1111 , 0xAAFF ) + "-")
guid.append('%04X' % random.randint( 0x1111 , 0xAAFF ) + "-")
guid.append('%04X' % random.randint( 0x1111 , 0xAAFF ) + "-")
guid.append('%04X' % random.randint( 0x1111 , 0xAAFF ))
guid.append('%04X' % random.randint( 0x1111 , 0xAAFF ))
guid.append('%04X' % random.randint( 0x1111 , 0xAAFF ))
return ''.join(guid)
def contains(string, substring):
'''return True if string contains substring'''
return (string.find(substring) != -1)
def between(string, start, end):
'''Returns the string between start and end
I wouldn't recommend using this outside this module...'''
try:
return string.split(start)[1].split(end)[0]
except IndexError:
raise SLPError("Couldn't parse field " + repr(string))
def to_dict(lines):
'''Splits the list "lines" by ": " and returns a dict'''
parameters = {}
for line in lines:
try:
key, value = line.split(': ')
parameters[key] = value
except ValueError:
continue
return parameters
### messages
# this stuff could be moved to classmethods,
# but i think we need py2.3 somewhere
# also, the old classmethod syntax sucks
def invite(session, context, eufguid=SLPMessage.EUFGUID_DISPLAY):
message = SLPMessage(session=session)
message.method = SLPMessage.INVITE_METHOD % message.to_
message.content_type = SLPMessage.CONTENT_SESSION_REQ
message.body['EUF-GUID'] = eufguid
message.body['SessionID'] = session.session_id
message.body['AppID'] = session.app_id
message.body['Context'] = context
return message
def ok(session):
message = SLPMessage(session=session)
message.method = SLPMessage.OK_STATUS
message.content_type = SLPMessage.CONTENT_SESSION_REQ
message.cseq = '1'
message.body['SessionID'] = session.session_id
return message
# TODO: move this
def webcam_invite(session):
message = SLPMessage(session=session)
message.method = SLPMessage.INVITE_METHOD % message.to_
message.content_type = SLPMessage.CONTENT_TRANS_REQ
message.cseq = '0'
message.body['Bridges'] = 'TRUDPv1 TCPv1'
message.body['NetID'] = '-1280904111'
message.body['Conn-Type'] = 'Firewall'
message.body['UPnPNat'] = 'false'
message.body['ICF'] = 'false'
return message
def decline(session):
message = SLPMessage(session=session)
message.method = SLPMessage.DECLINE_STATUS
message.content_type = SLPMessage.CONTENT_SESSION_CLOSE
message.cseq = '1'
message.body['SessionID'] = session.session_id
return message
def bye(session):
message = SLPMessage(session=session)
message.method = SLPMessage.BYE_METHOD % message.to_
message.content_type = SLPMessage.CONTENT_SESSION_CLOSE
return message
|