|
1 """Simple captcha library, based on PIL. Monkey patch functions in this module |
|
2 if you want something better... |
|
3 |
|
4 :organization: Logilab |
|
5 :copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
|
6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
7 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
|
8 """ |
|
9 __docformat__ = "restructuredtext en" |
|
10 |
|
11 from random import randint, choice |
|
12 from cStringIO import StringIO |
|
13 |
|
14 import Image, ImageFont, ImageDraw, ImageFilter |
|
15 |
|
16 |
|
17 from time import time |
|
18 |
|
19 from cubicweb import tags |
|
20 from cubicweb.view import StartupView |
|
21 from cubicweb.web import httpcache, formwidgets as fw, formfields as ff |
|
22 |
|
23 |
|
24 def pil_captcha(text, fontfile, fontsize): |
|
25 """Generate a captcha image. Return a PIL image object. |
|
26 |
|
27 adapted from http://code.activestate.com/recipes/440588/ |
|
28 """ |
|
29 # randomly select the foreground color |
|
30 fgcolor = randint(0, 0xffff00) |
|
31 # make the background color the opposite of fgcolor |
|
32 bgcolor = fgcolor ^ 0xffffff |
|
33 # create a font object |
|
34 font = ImageFont.truetype(fontfile, fontsize) |
|
35 # determine dimensions of the text |
|
36 dim = font.getsize(text) |
|
37 # create a new image slightly larger that the text |
|
38 img = Image.new('RGB', (dim[0]+5, dim[1]+5), bgcolor) |
|
39 draw = ImageDraw.Draw(img) |
|
40 # draw 100 random colored boxes on the background |
|
41 x, y = img.size |
|
42 for num in range(100): |
|
43 draw.rectangle((randint(0, x), randint(0, y), |
|
44 randint(0, x), randint(0, y)), |
|
45 fill=randint(0, 0xffffff)) |
|
46 # add the text to the image |
|
47 draw.text((3, 3), text, font=font, fill=fgcolor) |
|
48 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) |
|
49 return img |
|
50 |
|
51 |
|
52 def captcha(fontfile, fontsize, size=5, format='JPEG'): |
|
53 """Generate an arbitrary text, return it together with a buffer containing |
|
54 the captcha image for the text |
|
55 """ |
|
56 text = u''.join(choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(size)) |
|
57 img = pil_captcha(text, fontfile, fontsize) |
|
58 out = StringIO() |
|
59 img.save(out, format) |
|
60 out.seek(0) |
|
61 return text, out |
|
62 |
|
63 |
|
64 class CaptchaView(StartupView): |
|
65 __regid__ = 'captcha' |
|
66 |
|
67 http_cache_manager = httpcache.NoHTTPCacheManager |
|
68 binary = True |
|
69 templatable = False |
|
70 content_type = 'image/jpg' |
|
71 |
|
72 def call(self): |
|
73 text, data = captcha.captcha(self._cw.vreg.config['captcha-font-file'], |
|
74 self._cw.vreg.config['captcha-font-size']) |
|
75 self._cw.set_session_data('captcha', text) |
|
76 self.w(data.read()) |
|
77 |
|
78 |
|
79 class CaptchaWidget(fw.TextInput): |
|
80 def render(self, form, field, renderer=None): |
|
81 # t=int(time()*100) to make sure img is not cached |
|
82 src = form._cw.build_url('view', vid='captcha', t=int(time()*100)) |
|
83 img = tags.img(src=src, alt=u'captcha') |
|
84 img = u'<div class="captcha">%s</div>' % img |
|
85 return img + super(CaptchaWidget, self).render(form, field, renderer) |