'''
Various forms of addresses such as URI and SIP address.
'''
import re, socket, struct
def isIPv4(data):
'''Check if the data is a dotted decimal IPv4 address or not?
>>> isIPv4('10.2.3.4') == True
True
>>> False == isIPv4('10.2.3.a') == isIPv4('10.2.3.a.5') == isIPv4('10.2.3.-2') == isIPv4('10.2.3.403')
True
'''
try:
m = socket.inet_aton(data)
return True
except:
return False
def isMulticast(data):
'''Check if the data is a dotted decimal multicast address or not?
>>> isMulticast('224.0.1.2') == True
True
>>> False == isMulticast('10.2.3.4')
True
'''
try:
m, = struct.unpack('>I', socket.inet_aton(data))
return ((m & 0xF0000000) == 0xE0000000)
except:
return False
def isLocal(data):
'''Check if the data is a dotted decimal local interface IP address?
>>> isLocal('127.0.0.1') == True
True
>>> False == isLocal('192.1.2.3')
True
'''
return data == '127.0.0.1' or data == '::1'
def isPrivate(data):
'''Check if the data is a dotted decimal private IP address behind a NAT?
>>> isPrivate('10.1.2.3') == True
True
>>> False == isPrivate('192.1.2.3')
True
'''
try:
a, b, c, d = struct.unpack('>BBBB', socket.inet_aton(data))
return a == 10 or a == 172 and 16 <= b < 32 or a == 192 and b == 168
except:
return False
class URI(object):
'''A URI object with dynamic properties.
Attributes and items such as scheme, user, password, host, port,
param[name], header[index], give various parts of the URI.
>>> print URI('sip:kundan@example.net')
sip:kundan@example.net
>>> print URI('sip:kundan:passwd@example.net:5060;transport=udp;lr?name=value&another=another')
sip:kundan:passwd@example.net:5060;lr;transport=udp?name=value&another=another
>>> print URI('sip:192.1.2.3:5060')
sip:192.1.2.3:5060
>>> print URI("sip:kundan@example.net") == URI("sip:Kundan@Example.NET")
True
>>> print 'empty=', URI()
empty=
>>> print URI('tel:+1-212-9397063')
tel:+1-212-9397063
>>> print URI('sip:kundan@192.1.2.3:5060').hostPort
('192.1.2.3', 5060)
'''
_syntax = re.compile('^(?P<scheme>[a-zA-Z][a-zA-Z0-9\+\-\.]*):'
+ '(?:(?:(?P<user>[a-zA-Z0-9\-\_\.\!\~\*\'\(\)&=\+\$,;\?\/\%]+)'
+ '(?::(?P<password>[^:@;\?]+))?)@)?'
+ '(?:(?:(?P<host>[^;\?:]*)(?::(?P<port>[\d]+))?))'
+ '(?:;(?P<params>[^\?]*))?'
+ '(?:\?(?P<headers>.*))?$')
_syntax_urn = re.compile(r'^(?P<scheme>urn):(?P<host>[^;\?>]+)$')
_schemes = ['http', 'sip']
def __init__(self, value=''):
'''Construct from a string representation of a URI, or empty'''
if value:
m = URI._syntax.match(value)
if m:
self.scheme, self.user, self.password, self.host, self.port, params, headers = m.groups()
elif URI._syntax_urn.match(value):
m = URI._syntax_urn.match(value)
self.scheme, self.host = m.groups()
self.user = self.password = self.port = params = headers = None
else:
raise ValueError, 'Invalid URI(' + value + ')'
if self.scheme == 'tel' and self.user is None:
self.user, self.host = self.host, None
self.port = self.port and int(self.port) or None
self.param = dict(map(lambda k: (k[0], k[2] if k[2] else None), map(lambda n: n.partition('='), params.split(';')))) if params else {}
self.header = [nv for nv in headers.split('&')] if headers else []
else:
self.scheme = self.user = self.password = self.host = self.port = None
self.param = {}; self.header = []
def __repr__(self):
'''Return a string representation of the URI'''
user,host = (self.user,self.host) if self.scheme != 'tel' else (None, self.user)
return (self.scheme + ':' + ((user + \
((':'+self.password) if self.password else '') + '@') if user else '') + \
(((host if host else '') + ((':'+str(self.port)) if self.port else '')) if host else '') + \
((';'+';'.join([(n+'='+v if v is not None else n) for n,v in sorted(self.param.items())])) if len(self.param)>0 else '') + \
(('?'+'&'.join(self.header)) if len(self.header)>0 else '')) if self.scheme and host else '';
def dup(self):
'''Duplicate this object.'''
return URI(self.__repr__())
def __hash__(self):
'''Hash is derived from lower-case string, hence causes case insensitive match'''
return hash(str(self).lower())
def __cmp__(self, other):
'''Compare two URI objects by comparing their hash values'''
return cmp(str(self).lower(), str(other).lower())
@property
def hostPort(self):
'''Read-only tuple (host, port) for this uri.'''
return (self.host, self.port)
def _ssecure(self, value):
if value and self.scheme in URI._schemes: self.scheme += 's'
def _gsecure(self):
return True if self.scheme and self.scheme[-1] == 's' and self.scheme[:-1] in URI._schemes else False
secure = property(fget=_gsecure, fset=_ssecure)
class Address(object):
'''An address object has displayName (str) and uri (URI) attributes.
The mustQuote property indicates whether the uri portion must
be quoted when using a string representation or not.
>>> a1 = Address('"Kundan Singh" <sip:kundan@example.net>')
>>> a2 = Address('Kundan Singh <sip:kundan@example.net>')
>>> a3 = Address('"Kundan Singh" <sip:kundan@example.net> ')
>>> a4 = Address('<sip:kundan@example.net>')
>>> a5 = Address('sip:kundan@example.net')
>>> print str(a1) == str(a2) and str(a1) == str(a3) and str(a1.uri) == str(a4.uri) and str(a1.uri) == str(a5.uri)
True
>>> print a1
"Kundan Singh" <sip:kundan@example.net>
>>> print a1.displayable
Kundan Singh
'''
_syntax = [re.compile('^(?P<name>[a-zA-Z0-9\-\.\_\+\~\ \t]*)<(?P<uri>[^>]+)>'),
re.compile('^(?:"(?P<name>[^"]+)")[\ \t]*<(?P<uri>[^>]+)>'),
re.compile('^[\ \t]*(?P<name>)(?P<uri>[^;]+)')]
def __init__(self, value=None):
'''Construct an address from the string representation'''
self.displayName = self.uri = None
self.wildcard = self.mustQuote = False
if value: self.parse(value)
def parse(self, value):
'''Parse a string representation to an address. Returns number of
characters parsed.'''
if str(value).startswith('*'):
self.wildcard = True
return 1;
else:
for s in Address._syntax:
m = s.match(value)
if m:
self.displayName = m.groups()[0].strip()
self.uri = URI(m.groups()[1].strip())
return m.end()
def __repr__(self):
'''Return a string representation of the address'''
return (('"' + self.displayName + '"' + (' ' if self.uri else '')) if self.displayName else '') \
+ ((('<' if self.mustQuote or self.displayName else '') \
+ repr(self.uri) \
+ ('>' if self.mustQuote or self.displayName else '')) if self.uri else '')
def dup(self):
'''Duplicate this object.'''
return Address(self.__repr__())
@property
def displayable(self):
'''Read-only displayable string representation'''
return self.getDisplayable(limit=25)
def getDisplayable(self, limit):
name = self.displayName or self.uri and self.uri.user or self.uri and self.uri.host or ''
return name if len(name)<limit else (name[0:limit-3] + '...')
if __name__ == '__main__':
import doctest
doctest.testmod()