pyLibravatar-1.6/ 0000755 0001750 0001750 00000000000 12220266705 015454 5 ustar francois francois 0000000 0000000 pyLibravatar-1.6/setup.py 0000644 0001750 0001750 00000002447 12220264436 017174 0 ustar francois francois 0000000 0000000 from distutils.core import setup
import sys
if sys.version_info[0] < 3:
requires = ['pydns']
else:
requires = ['py3dns']
setup(
name = 'pyLibravatar',
version = '1.6',
description = 'Python module for Libravatar',
author = 'Francois Marier',
author_email = 'francois@libravatar.org',
url = 'https://launchpad.net/pylibravatar',
py_modules = ['libravatar'],
license = 'MIT',
keywords = ['libravatar', 'avatars', 'autonomous', 'social', 'federated'],
requires = requires,
classifiers = [
"Programming Language :: Python",
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
],
long_description = """\
PyLibravatar is an easy way to make use of the federated `Libravatar`_
avatar hosting service from within your Python applications.
See the `project page`_ to file bugs or ask questions.
.. _Libravatar: https://www.libravatar.org/
.. _project page: https://launchpad.net/pylibravatar
"""
)
pyLibravatar-1.6/README.txt 0000644 0001750 0001750 00000003777 12220264373 017167 0 ustar francois francois 0000000 0000000 # pyLibravatar
PyLibravatar is an easy way to make use of the federated [Libravatar](http://www.libravatar.org)
avatar hosting service from within your Python applications.
See the [project page](https://launchpad.net/pylibravatar) for the bug tracker and downloads.
## Installation
To install using pip, simply do this:
$ pip install pyLibravatar
## Usage
To generate the correct avatar URL based on someone's email address, use the
following:
>>> from libravatar import libravatar_url
>>> url = libravatar_url(email = 'person@example.com')
>>> print '
'
Here are other options you can provide:
>>> url = libravatar_url(openid = 'http://example.org/id/Bob', https = True, size = 96, default = 'mm')
See the [Libravatar documentation](http://wiki.libravatar.org/api) for more
information on the special values for the "default" parameter.
## License
Copyright (C) 2011, 2013 Francois Marier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
pyLibravatar-1.6/PKG-INFO 0000644 0001750 0001750 00000002131 12220266705 016546 0 ustar francois francois 0000000 0000000 Metadata-Version: 1.1
Name: pyLibravatar
Version: 1.6
Summary: Python module for Libravatar
Home-page: https://launchpad.net/pylibravatar
Author: Francois Marier
Author-email: francois@libravatar.org
License: MIT
Description: PyLibravatar is an easy way to make use of the federated `Libravatar`_
avatar hosting service from within your Python applications.
See the `project page`_ to file bugs or ask questions.
.. _Libravatar: https://www.libravatar.org/
.. _project page: https://launchpad.net/pylibravatar
Keywords: libravatar,avatars,autonomous,social,federated
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Requires: pydns
pyLibravatar-1.6/Changelog.txt 0000644 0001750 0001750 00000001161 12220264342 020076 0 ustar francois francois 0000000 0000000 1.6 (2013-09-24)
- Add support for Python 3
1.5 (2011-10-24)
- Tolerate type errors in options
- Refine normalization of OpenIDs and domain names
- Refactor a few functions
- Add many unit tests
1.4 (2011-10-16)
- Ignore non-SRV records in lookup_avatar_server()
- Mark package as production/stable
1.3 (2011-09-03)
- Fix quoting of "default" parameter
- Add unit tests
1.2 (2011-07-06)
- Security fix: sanitization of DNS results
- Update package description
1.1 (2011-06-02)
- Add dependency on pydns
- Add a changelog
1.0 (2011-06-01)
- Initial public release
pyLibravatar-1.6/libravatar.py 0000644 0001750 0001750 00000020476 12220264157 020165 0 ustar francois francois 0000000 0000000 """
pyLibravatar Python module for Libravatar
Copyright (C) 2011, 2013 Francois Marier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
"""
from __future__ import print_function
import DNS
import hashlib
import random
import re
try:
# Python-3.x
# pylint: disable=F0401,E0611
from urllib.parse import urlsplit, urlunsplit
from urllib.parse import quote_plus
except ImportError:
# Python-2.x
from urlparse import urlsplit, urlunsplit
from urllib import quote_plus
BASE_URL = 'http://cdn.libravatar.org/avatar/'
SECURE_BASE_URL = 'https://seccdn.libravatar.org/avatar/'
SERVICE_BASE = '_avatars._tcp'
SECURE_SERVICE_BASE = '_avatars-sec._tcp'
MIN_AVATAR_SIZE = 1
MAX_AVATAR_SIZE = 512
def libravatar_url(email=None, openid=None, https=False,
default=None, size=None):
"""
Return a URL to the appropriate avatar
"""
avatar_hash, domain = parse_user_identity(email, openid)
query_string = parse_options(default, size)
delegation_server = lookup_avatar_server(domain, https)
return compose_avatar_url(delegation_server, avatar_hash,
query_string, https)
def parse_options(default, size):
"""
Turn optional parameters into a query string.
"""
query_string = ''
if default:
query_string = '?d=%s' % quote_plus(str(default))
if size:
try:
size = int(size)
except ValueError:
return query_string # invalid size, skip
if len(query_string) > 0:
query_string += '&'
else:
query_string = '?'
query_string += 's=%s' % max(MIN_AVATAR_SIZE,
min(MAX_AVATAR_SIZE, size))
return query_string
def parse_user_identity(email, openid):
"""
Generate user hash based on the email address or OpenID and return
it along with the relevant domain.
"""
hash_obj = None
if email:
lowercase_value = email.strip().lower()
domain = lowercase_value.split('@')[-1]
hash_obj = hashlib.new('md5')
elif openid:
# pylint: disable=E1103
url = urlsplit(openid.strip())
if url.username:
password = url.password or ''
netloc = url.username + ':' + password + '@' + url.hostname
else:
netloc = url.hostname
lowercase_value = urlunsplit((url.scheme.lower(), netloc,
url.path, url.query, url.fragment))
domain = url.hostname
hash_obj = hashlib.new('sha256')
if not hash_obj: # email and openid both missing
return (None, None)
hash_obj.update(lowercase_value.encode('utf-8'))
return (hash_obj.hexdigest(), domain)
def compose_avatar_url(delegation_server, avatar_hash, query_string, https):
"""
Assemble the final avatar URL based on the provided components.
"""
avatar_hash = avatar_hash or ''
query_string = query_string or ''
base_url = BASE_URL
if https:
base_url = SECURE_BASE_URL
if delegation_server:
if https:
base_url = "https://%s/avatar/" % delegation_server
else:
base_url = "http://%s/avatar/" % delegation_server
return base_url + avatar_hash + query_string
def service_name(domain, https):
"""
Return the DNS service to query for a given domain and scheme.
"""
if not domain:
return None
if https:
return "%s.%s" % (SECURE_SERVICE_BASE, domain)
else:
return "%s.%s" % (SERVICE_BASE, domain)
def lookup_avatar_server(domain, https):
"""
Extract the avatar server from an SRV record in the DNS zone
The SRV records should look like this:
_avatars._tcp.example.com. IN SRV 0 0 80 avatars.example.com
_avatars-sec._tcp.example.com. IN SRV 0 0 443 avatars.example.com
"""
DNS.DiscoverNameServers()
try:
dns_request = DNS.Request(name=service_name(domain, https),
qtype='SRV').req()
except DNS.DNSError as message:
print("DNS Error: %s" % message)
return None
if 'NXDOMAIN' == dns_request.header['status']:
# Not an error, but no point in going any further
return None
if dns_request.header['status'] != 'NOERROR':
print("DNS Error: status=%s" % dns_request.header['status'])
return None
records = []
for answer in dns_request.answers:
if (not 'data' in answer) or (not answer['data']):
continue
if (not answer['typename']) or (answer['typename'] != 'SRV'):
continue
srv_record = {'priority': int(answer['data'][0]),
'weight': int(answer['data'][1]),
'port': int(answer['data'][2]),
'target': answer['data'][3]}
records.append(srv_record)
return normalized_target(records, https)
def normalized_target(records, https):
"""
Pick the right server to use and return its normalized hostname
(i.e. only include the port number if it's necessary).
"""
target, port = sanitize_target(srv_hostname(records))
if target and ((https and port != 443) or (not https and port != 80)):
return "%s:%s" % (target, port)
return target
def sanitize_target(args):
"""
Ensure we are getting a (mostly) valid hostname and port number
from the DNS resolver.
"""
target, port = args
if not target or not port:
return (None, None)
if not re.match('^[0-9a-zA-Z\-.]+$', str(target)):
return (None, None)
try:
if int(port) < 1 or int(port) > 65535:
return (None, None)
except ValueError:
return (None, None)
return (target, port)
def srv_hostname(records):
"""
Return the right (target, port) pair from a list of SRV records.
"""
if len(records) < 1:
return (None, None)
if 1 == len(records):
srv_record = records[0]
return (srv_record['target'], srv_record['port'])
# Keep only the servers in the top priority
priority_records = []
total_weight = 0
top_priority = records[0]['priority'] # highest priority = lowest number
for srv_record in records:
if srv_record['priority'] > top_priority:
# ignore the record (srv_record has lower priority)
continue
elif srv_record['priority'] < top_priority:
# reset the array (srv_record has higher priority)
top_priority = srv_record['priority']
total_weight = 0
priority_records = []
total_weight += srv_record['weight']
if srv_record['weight'] > 0:
priority_records.append((total_weight, srv_record))
else:
# zero-weigth elements must come first
priority_records.insert(0, (0, srv_record))
if 1 == len(priority_records):
srv_record = priority_records[0][1]
return (srv_record['target'], srv_record['port'])
# Select first record according to RFC2782 weight
# ordering algorithm (page 3)
random_number = random.randint(0, total_weight)
for record in priority_records:
weighted_index, srv_record = record
if weighted_index >= random_number:
return (srv_record['target'], srv_record['port'])
print('There is something wrong with our SRV weight ordering algorithm')
return (None, None)