1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """Simple captcha library, based on PIL. Monkey patch functions in this module |
|
19 if you want something better... |
|
20 """ |
|
21 |
|
22 __docformat__ = "restructuredtext en" |
|
23 |
|
24 from random import randint, choice |
|
25 from io import BytesIO |
|
26 |
|
27 from six.moves import range |
|
28 |
|
29 from PIL import Image, ImageFont, ImageDraw, ImageFilter |
|
30 |
|
31 |
|
32 from time import time |
|
33 |
|
34 from cubicweb import tags |
|
35 from cubicweb.web import ProcessFormError, formwidgets as fw |
|
36 |
|
37 |
|
38 def pil_captcha(text, fontfile, fontsize): |
|
39 """Generate a captcha image. Return a PIL image object. |
|
40 |
|
41 adapted from http://code.activestate.com/recipes/440588/ |
|
42 """ |
|
43 # randomly select the foreground color |
|
44 fgcolor = randint(0, 0xffff00) |
|
45 # make the background color the opposite of fgcolor |
|
46 bgcolor = fgcolor ^ 0xffffff |
|
47 # create a font object |
|
48 font = ImageFont.truetype(fontfile, fontsize) |
|
49 # determine dimensions of the text |
|
50 dim = font.getsize(text) |
|
51 # create a new image slightly larger that the text |
|
52 img = Image.new('RGB', (dim[0]+5, dim[1]+5), bgcolor) |
|
53 draw = ImageDraw.Draw(img) |
|
54 # draw 100 random colored boxes on the background |
|
55 x, y = img.size |
|
56 for num in range(100): |
|
57 draw.rectangle((randint(0, x), randint(0, y), |
|
58 randint(0, x), randint(0, y)), |
|
59 fill=randint(0, 0xffffff)) |
|
60 # add the text to the image |
|
61 draw.text((3, 3), text, font=font, fill=fgcolor) |
|
62 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) |
|
63 return img |
|
64 |
|
65 |
|
66 def captcha(fontfile, fontsize, size=5, format='JPEG'): |
|
67 """Generate an arbitrary text, return it together with a buffer containing |
|
68 the captcha image for the text |
|
69 """ |
|
70 text = u''.join(choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(size)) |
|
71 img = pil_captcha(text, fontfile, fontsize) |
|
72 out = BytesIO() |
|
73 img.save(out, format) |
|
74 out.seek(0) |
|
75 return text, out |
|
76 |
|
77 |
|
78 class CaptchaWidget(fw.TextInput): |
|
79 def render(self, form, field, renderer=None): |
|
80 # t=int(time()*100) to make sure img is not cached |
|
81 src = form._cw.build_url('view', vid='captcha', t=int(time()*100), |
|
82 captchakey=field.input_name(form)) |
|
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) |
|
86 |
|
87 def process_field_data(self, form, field): |
|
88 captcha = form._cw.session.data.pop(field.input_name(form), None) |
|
89 val = super(CaptchaWidget, self).process_field_data(form, field) |
|
90 if val is None: |
|
91 return val # required will be checked by field |
|
92 if captcha is None: |
|
93 msg = form._cw._('unable to check captcha, please try again') |
|
94 raise ProcessFormError(msg) |
|
95 elif val.lower() != captcha.lower(): |
|
96 msg = form._cw._('incorrect captcha value') |
|
97 raise ProcessFormError(msg) |
|
98 return val |
|