http://captchas.net | |
This module can be downloaded here
#---------------------------------------------------------------------
# Python module for easy utilization of http://captchas.net
#
# For documentation look at http://captchas.net/sample/python/
#
# Written by Sebastian Wilhelmi <seppi@seppi.de> and
# Felix Holderied <felix@holderied.de>
# This file is in the public domain.
#
# ChangeLog:
#
# 2006-09-08: Add new optional parameters alphabet, letters
# height an width. Add audio_url.
#
# 2006-03-01: Only delete the random string from the repository in
# case of a successful verification.
#
# 2006-02-14: Add new image() method returning an HTML/JavaScript
# snippet providing a fault tolerant service.
#
# 2005-06-02: Initial version.
#
#---------------------------------------------------------------------
import os
import md5
import random
import time
class CaptchasDotNet:
def __init__ (self, client, secret,
alphabet = 'abcdefghkmnopqrstuvwxyz',
letters = 6,
width = 240,
height = 80,
random_repository = '/tmp/captchasnet-random-strings',
cleanup_time = 3600
):
self.__client = client
self.__secret = secret
self.__alphabet = alphabet
self.__letters = letters
self.__width = width
self.__height = height
self.__random_repository = random_repository
self.__cleanup_time = cleanup_time
self.__time_stamp_file = os.path.join (random_repository,
'__time_stamp__')
# Return a random string
def __random_string (self):
# The random string shall consist of small letters, big letters
# and digits.
letters = "abcdefghijklmnopqrstuvwxyz"
letters += letters.upper () + "0123456789"
# The random starts out empty, then 40 random possible characters
# are appended.
random_string = ''
for i in range (40):
random_string += random.choice (letters)
# Return the random string.
return random_string
# Create a new random string and register it.
def random (self):
# If the repository directory is does not yet exist, create it.
if not os.path.isdir (self.__random_repository):
os.makedirs (self.__random_repository)
# If the time stamp file does not yet exist, create it.
if not os.path.isfile (self.__time_stamp_file):
os.close (os.open (self.__time_stamp_file, os.O_CREAT, 0700))
# Get the current time.
now = time.time ()
# Determine the time, before which to remove random strings.
cleanup_time = now - self.__cleanup_time
# If the last cleanup is older than specified, cleanup the
# directory.
if os.stat (self.__time_stamp_file).st_mtime < cleanup_time:
os.utime (self.__time_stamp_file, (now, now))
for file_name in os.listdir (self.__random_repository):
file_name = os.path.join (self.__random_repository, file_name)
if os.stat (file_name).st_mtime < cleanup_time:
os.unlink (file_name)
# loop until a valid random string has been found and registered.
while True:
# generate a new random string.
random = self.__random_string ()
# open a file with the corresponding name in the repository
# directory in such a way, that the creation fails, when the
# file already exists. That should be near to impossible with
# good seeding of the random number generator, but it's better
# to play safe.
try:
os.close (os.open (os.path.join (self.__random_repository,
random),
os.O_EXCL | os.O_CREAT, 0700))
except EnvironmentError, error:
# if the file already existed, rerun the loop to try the
# next string.
if error.errno == errno.EEXIST:
continue
else:
# other errors will certainly persist for other random
# strings, so raise the exception.
raise
# return the successfully registered random string.
self.__random = random
return random
def image_url (self, random = None, base = 'http://image.captchas.net/'):
if not random:
random = self.__random
url = base
url += '?client=%s&random=%s' % (self.__client, random)
if self.__alphabet != "abcdefghijklmnopqrstuvwxyz":
url += '&alphabet=%s' % self.__alphabet
if self.__letters != 6:
url += '&letters=%s' % self.__letters
if self.__width != 240:
url += '&width=%s' % self.__width
if self.__height != 80:
url += '&height=%s' % self.__height
return url
def audio_url (self, random = None, base = 'http://audio.captchas.net/'):
if not random:
random = self.__random
url = base
url += '?client=%s&random=%s' % (self.__client, random)
if self.__alphabet != "abcdefghijklmnopqrstuvwxyz":
url += '&alphabet=%s' % self.__alphabet
if self.__letters != 6:
url += '&letters=%s' % self.__letters
return url
def image (self, random = None, id = 'captchas.net'):
return '''
<a href="http://captchas.net"><img
style="border: none; vertical-align: bottom"
id="%s" src="%s" width="%d" height="%d"
alt="The CAPTCHA image" /></a>
<script type="text/javascript">
<!--
function captchas_image_error (image)
{
if (!image.timeout) return true;
image.src = image.src.replace (/^http:\/\/image\.captchas\.net/,
'http://image.backup.captchas.net');
return captchas_image_loaded (image);
}
function captchas_image_loaded (image)
{
if (!image.timeout) return true;
window.clearTimeout (image.timeout);
image.timeout = false;
return true;
}
var image = document.getElementById ('%s');
image.onerror = function() {return captchas_image_error (image);};
image.onload = function() {return captchas_image_loaded (image);};
image.timeout
= window.setTimeout(
"captchas_image_error (document.getElementById ('%s'))",
10000);
image.src = image.src;
//-->
</script>''' % (id, self.image_url (random), self.__width, self.__height, id, id)
def validate (self, random):
self.__random = random
file_name = os.path.join (self.__random_repository, random)
# Find out, whether the file exists
result = os.path.isfile (file_name)
# if the file exists, remember it.
if result:
self.__random_file = file_name
# the random string was valid, if and only if the
# corresponding file existed.
return result
def verify (self, input, random = None):
if not random:
random = self.__random
# The format of the password.
password_alphabet = self.__alphabet
password_length = self.__letters
# If the user input has the wrong lenght, it can't be correct.
if len (input) != password_length:
return False
# Calculate the MD5 digest of the concatenation of secret key and
# random string.
encryption_base = self.__secret + random
if (password_alphabet != "abcdefghijklmnopqrstuvwxyz") or (password_length != 6):
encryption_base += ":" + password_alphabet + ":" + str(password_length)
digest = md5.new (encryption_base).digest ()
# Compute password
correct_password = ''
for pos in range (password_length):
letter_num = ord (digest[pos]) % len (password_alphabet)
correct_password += password_alphabet[letter_num]
# Check password
if input != correct_password:
return False
# Remove the correspondig random file, if it exists.
try:
os.unlink (self.__random_file)
del self.__random_file
except:
pass
# The user input was correct.
return True
XHTML 1.1 compliant | A service of Felix Holderied and Sebastian Wilhelmi (Contact) | mail@captchas.net |