pax_global_header00006660000000000000000000000064152326057020014514gustar00rootroot0000000000000052 comment=ba514948f74ac078fd63c9bd1337cc895d6e3f3e django-simple-captcha-0.7.0/000077500000000000000000000000001523260570200156525ustar00rootroot00000000000000django-simple-captcha-0.7.0/.flake8000066400000000000000000000000771523260570200170310ustar00rootroot00000000000000[flake8] ignore = E501, W503 exclude = migrations,.venv_*,docs django-simple-captcha-0.7.0/.github/000077500000000000000000000000001523260570200172125ustar00rootroot00000000000000django-simple-captcha-0.7.0/.github/workflows/000077500000000000000000000000001523260570200212475ustar00rootroot00000000000000django-simple-captcha-0.7.0/.github/workflows/test.yml000066400000000000000000000070671523260570200227630ustar00rootroot00000000000000name: Test Django Simple Captcha on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: ["3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | sudo apt-get install gettext python -m pip install --upgrade pip wheel pip install tox tox-gh-actions - name: Test with tox run: tox windows: runs-on: windows-latest strategy: matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e py313-django60 docs: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e docs coverage: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install audio dependencies run: sudo apt-get install sox flite - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e coverage env: CAPTCHA_FLITE_PATH: '/usr/bin/flite' CAPTCHA_SOX_PATH: '/usr/bin/sox' gettext: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: sudo apt-get install gettext - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e gettext flake8: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e flake8 django-simple-captcha-0.7.0/.gitignore000066400000000000000000000002441523260570200176420ustar00rootroot00000000000000dist build *.pyc *.egg-info docs/_build/ .venv* testproject/django-simple-captcha.db testproject/.coverage testproject/coverage.xml testproject/htmlcov/ .tox .idea django-simple-captcha-0.7.0/.pre-commit-config.yaml000066400000000000000000000012211523260570200221270ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: fix-encoding-pragma args: ['--remove'] - id: debug-statements - id: check-merge-conflict - id: check-ast - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/timothycrosley/isort rev: 5.12.0 hooks: - id: isort additional_dependencies: [toml] - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. rev: "v0.2.2" hooks: # Run the linter. - id: ruff args: [ --fix ] # Run the formatter. - id: ruff-format django-simple-captcha-0.7.0/.readthedocs.yaml000066400000000000000000000005161523260570200211030ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.10" # You can also specify other tool versions: # nodejs: "16" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # Dependencies required to build your docs python: install: - requirements: docs/requirements.txt django-simple-captcha-0.7.0/CHANGES000066400000000000000000000254321523260570200166530ustar00rootroot00000000000000Version History =============== Version 0.7.0 ------------- * Test against Django 6.1 * Wrap the fixed random.seed() / reset in a try..finally, to avoid accidentally polluting the global RNG in case of an exception * math_challenge: avoid using eval() * Issue a loud warning if CAPTCHA_TEST_MODE is True and DEBUG is False * Invalidate the captcha store after a single validation attempt, even when the response is wrong, to prevent brute-force attacks * Adds SECURITY.md Version 0.6.3 ------------- * Test against Django 6.0 * Support animated CAPTCHAs (AVIF or GIF) Version 0.6.2 ------------- * Make DRF dependency optional Version 0.6.1 ------------- * Fixed docs and test configuration * Delete leftover temporary audio files (#224, #225, #226 thanks @dawidratynski and @A-dead-pixel) * Test against Django 5.1 and 5.2 * Add ability to control color of each character (PR #235, thanks @mheidarian) * Add support for Django REST Framework (PR #236, thanks @michalwrona01) * Audio CAPTCHA: try to detect the sample rate of the generated WAV file prior to merge (Issue #196) Version 0.6.0 ------------- * Only Django versions 4.2 or above are now supported * Removed the old rendering methods that were deprecated in 2017. Version 0.5.20 -------------- * Still support Django 3.2 (#222, thanks @petrklus) Version 0.5.19 -------------- * SECURITY ISSUE: reset the random seed after an image was generated (#221, thanks @ibuler) Version 0.5.18 -------------- * Fix some typos in documentation (#210, thanks @stweil) * Test against Django 4.2 * Stopped testing Django < 3.2 * BaseCaptchaTextInput should set autocomplete=off on the hashkey HiddenInput (#201, thanks @eerotal) * Test against Django 4.2a * Fix some deprecation warnings in Pillow 9.2+ * Removed old unused conditional imports * Format code with pre-commit and black Version 0.5.17 -------------- * English translation created from Spanish ones (#209, thanks @tpazderka) Version 0.5.16 -------------- * Adds a migration missing from 0.5.15 (#208, thanks @atodorov) Version 0.5.15 -------------- * Updated test matrix, drop tests against Python3.6, test against Python3.9 * Remove dependency on six * Test against Django 4.0a1 * Test with Python 3.10 (Django 3.2 and Django 4.0) * Remove warning for django 3.2 (#206, thanks @MiStErLu) Version 0.5.14 -------------- * Generate deterministic CAPTCHA images for each Captcha instance (#194, thanks @mjnaderi) * New setting CAPTCHA_2X_IMAGE to disable the double resolution image option (#195, thanks @mjnaderi) * Test against Django 3.2a Version 0.5.13 -------------- * Fix DeprecationWarnings on Django 3 related to unicode (#173, thanks @jannh) * Applied black to the codebase and ignored W503 * tox: remove superfluous basepython statement (#172, thanks @devkral) * i18n: add Persian (fa) language translation files (#190, thanks @mavenium) * Test Django 2.2, 3.0 and 3.1 on Python 3.6, 3.7 and 3.8 (#171, thanks @devkral) * Fix deprecation warnings on Django 3.1 (#186, thanks @ jannh and @TheBuky) * Travis fixes and test on ppc64le arch (#192 and #191, thanks @kishorkunal-raj) Version 0.5.12 -------------- * Doc: Update url include line for Django (#164, thanks @jpic) * Doc: Fixed a typo (#162, thanks @RubenGarcia) * Initial fixes and test against Django 3.0 (#166, #165. Thanks @devkral) * Don't open dictionary file in binary mode (#167, thanks @grasshoppermouse) Version 0.5.11 -------------- * Fix: CAPTCHA_TEST_MODE was broken. (#163, thanks @ohlr for reporting) Version 0.5.10 -------------- * Test against Django 2.2a1 * Docs: Grammar correction (#160, thanks @DanAtShenTech) * Fix: Add '+' to text replacement for audio support (#157, thanks @geirkairam) * I18N: Added Swedish translation (#155, thanks @stefannorman) * Docs: Provide an example of custom field template (#158, thanks @TheBuky) Version 0.5.9 ------------- * Add missing Jinja2 templates in the pypi packages. Version 0.5.8 ------------- * Add support for Jinja2 templates (Issue #145, PR #146, thanks @ziima) * Cleanup, drop dependency on South (#141, #142 thanks @ziima) Version 0.5.7 ------------- * Use templates for rendering of widgets (Issue #128, #134, PR #133, #139, thanks @ziima) * Always defined audio context variable (PR #132, thanks @ziima) * Test against Django 2.1a * Updated AJAX update docs (PR #140, thanks @CNmanyue) * Fixed a typo in a variable name (PR #130, thanks @galeo) Version 0.5.6 ------------- * Updated render method to adapt for Django 2.1 (PR #120, thanks @skozan) * Improved compatibility with Django 2.0, tests against Django 2.0a1 (PR #121, thanks @Kondou-ger) * Dropped support for PIL (use Pillow instead) * Updated documentation (Fixes #122, thanks @claudep) * Test against Django 2.0b1 * Return a Ranged Response when returning WAV audio to support Safari (Fixes #123, thanks @po5i) * Optionally inject brown noise into the generated WAV audio file, to avoid rainbow-table attacks (Fixes #124, thanks @appleorange1) * Test against Django 2.0 Version 0.5.5 ------------- * I messed the 0.5.4 release, re-releasing as 0.5.5 Version 0.5.4 ------------- * Removed a couple gremlins (PR #113, thanks @Pawamoy) * Added autocapitalize="off", autocorrect="off" and spellcheck="false" to the generated field (PR #116, thanks @rdonnelly) * Test against Django 1.11 * Drop support of Django 1.7 ("it'll probably still work") Version 0.5.3 ------------- * Ability to pass a per-field challenge generator function (Fixes #109) * Added a feature to get captchas from a data pool of pre-created captchas (PR #110, thanks @skozan) * Cleanup to remove old code handling timezones for no longer supported Django versions * Fix for "Size must be a tuple" issue with Pillow 3.4.0 (Fixes #111) Version 0.5.2 ------------- * Use any multiplication uperator instead of "*". (Fixes #77 via PR #104, thanks @honsdomi and @isergey) * Test against Django 1.10 Version 0.5.1 ------------- * Fine tuning MANIFEST.in * Prevent testproject from installing into site-packages Version 0.5.0 ------------- * Adds missing includes in MANIFEST.in Version 0.4.7 ------------- * Supported Django versions are now 1.7, 1.8 and 1.9 * Trying to fix the TravisCI build errors * Use Django templates to render the individual fields, as well as the assembled Captcha Field (Issue #31) Version 0.4.6 ------------- * Fixes an UnicodeDecodeError which was apparently only triggered during testing on TravisCI (I hope) * Support for Django 2.0 urlpatterns syntax (PR #82, Thanks @R3v1L) * settings.CAPTCHA_FONT_PATH may be a list, in which case a font is picked randomly (Issue #51 fixed in PR #88, Thanks @inflrscns) Version 0.4.5 ------------- * Test with tox * Test against Django 1.8 final * Added ability to force a fixed image size (PR #76, Thanks @superqwer) Version 0.4.4 ------------- * Added id_prefix argument (fixes issue #37) Version 0.4.3 ------------- * Add null noise helper (Thanks @xrmx) * Test against Django 1.7b4 * Added Spanish translations (Thanks @dragosdobrota) * Massive cleanup (pep8, translations) * Support for transparent background color. (Thanks @curaloucura) * Support both Django 1.7 migrations and South migrations. Please note, you *must* add the following to your settings, if you are using South migrations and Django 1.6 or lower. * Make sure autocomplete="off" is only applied to the text input, not the hidden input (Issue #68, thanks @narrowfail) * Fixed some grammar in the documentation. (Thanks @rikrian) * Return an HTTP 410 GONE error code for expired captcha images, to avoid crawlers from trying to reindex them (PR #70, thanks @joshuajonah) * Fixed title markup in documentation (#74, thanks @pavlov99) * Test against Django 1.7.1 Version 0.4.2 ------------- * Added autocomplete="off" to the input (Issue #57, thanks @Vincent-Vega) * Fixed the format (msgfmt -c) of most PO and MO files distributed with the project * Added Bulgarian translations. (Thanks @vstoykov) * Added Japanese translations. (Thanks, Keisuke URAGO) * Added Ukrainian translations. (Thanks, @FuriousCoder) * Added support for Python 3.2. (Thanks, @amrhassan) Version 0.4.1 ------------- * Dropped support for Django 1.3 * Fixed support of newer versions of Pillow (2.1 and above. Pillow 2.2.2 is now required) Thanks @viaregio (Issue #50) Version 0.4.0 ------------- * Perform some tests at package installation, to check whether PIL or Pillow are already installed. (Issue #46) * Added Slovak translations. (Thanks @ciklysta) Version 0.3.9 ------------- * Run most tests both with a regular Form and a ModelForm, to avoid regressions such as Issue #40 * Handle the special case where CaptchaFields are instantiated with required=False (Issue #42, thanks @DrMeers) * Fixed a misspelled setting, we now support both spellings, but the docs suggest the correct one (Issue #36, thanks @sayadn) * Added Django 1.6b to testrunner and adapted the test cases to support Django 1.6's new test discovery * Added German translations. (Thanks @digi604) * Frozen the version of Pillow to 2.0.0, as 2.1.0 seems to be truncating the output image -- Issue #44, Thanks @andruby * Added Polish translations. (Thanks @stilzdev) Version 0.3.8 ------------- * Fixed a critical bug (Issue #40) that would generate two captcha objects, and the test would always fail. Thanks @pengqi for the heads-up. Version 0.3.7 ------------- * Improved Django 1.5 and Django HEAD (1.6) compatibility (thanks @uruz) * Python3 compatibility (requires six and Pillow >= 2.0) * Added zh_CN localization (thanks @mingchen) * Make sure the generated challenge is a string type (the math challenge was probably broken -- Issue #33, thanks @YDS19872712) * Massive cleanup and refactoring (Issue #38, thanks @tepez) * Test refactoring to test a couple generators that weren't tested by default Version 0.3.6 ------------- * Django 1.5 compatibility (only affects tests) * Italian localization (thanks @arjunadeltoso) * Russian localization (thanks @mikek) * Fixed issue #17 - Append content-length to response (thanks @shchemelevev) * Merged PR #19 - AJAX refresh of captcha (thanks @artofhuman) * Merged PR #22 - Use op.popen instead of subprocess.call to generate the audio CAPTCHA (thanks @beda42) * Fixed issue #10 - uniformize spelling of "CAPTCHA" (thanks @mikek) * Fixed issue #12 - Raise error when try to initialize CaptchaTextInput alone and/or when try to initialize CaptchaField with widget keyword argument (thanks @vstoykov) * Merged PR #15 - Allow a 'test mode' where the string 'PASSED' always validates the CAPTCHA (thanks @beda42) * Dutch translation (thanks @leonderijke) * Turkish translation (thanks @gkmngrgn) Version 0.3.5 ------------- * Fixes issue #4: Fixes id_for_label malfunction with prefixed forms (thanks @lolek09) Version 0.3.4 ------------- * Fixes issue #3: regression on Django 1.4 when USE_TZ is False Version 0.3.3 ------------- * Django 1.4 Time zones compatibility * PEP 8 love Version 0.3.2 ------------- * Added a test project to run tests * Added South migrations django-simple-captcha-0.7.0/LICENSE000066400000000000000000000020501523260570200166540ustar00rootroot00000000000000Copyright (c) 2008 - 2014 Marco Bonetti 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. django-simple-captcha-0.7.0/MANIFEST.in000066400000000000000000000007461523260570200174170ustar00rootroot00000000000000include MANIFEST.in include LICENSE include README.rst include CHANGES include tox.ini include .pep8 recursive-include captcha/fonts * recursive-include captcha/locale * recursive-include captcha/templates * recursive-include captcha/jinja2 * recursive-include testproject * recursive-include docs * exclude testproject/django-simple-captcha.db prune .tox prune docs/_build prune htmlcov global-exclude *pyc global-exclude coverage.xml global-exclude .DS_Store global-exclude .coverage django-simple-captcha-0.7.0/README.rst000066400000000000000000000025551523260570200173500ustar00rootroot00000000000000********************* Django Simple Captcha ********************* .. image:: https://github.com/mbi/django-simple-captcha/actions/workflows/test.yml/badge.svg :target: https://github.com/mbi/django-simple-captcha/actions/workflows/test.yml .. image:: https://img.shields.io/pypi/v/django-simple-captcha :target: https://pypi.org/project/django-simple-captcha/ .. image:: https://img.shields.io/pypi/l/django-simple-captcha :target: https://github.com/mbi/django-simple-captcha/blob/master/LICENSE Django Simple Captcha is an extremely simple, yet highly customizable Django application to add captcha images to any Django form. .. image:: http://django-simple-captcha.readthedocs.io/en/latest/_images/captcha3.png Features ++++++++ * Very simple to setup and deploy, yet very configurable * Can use custom challenges (e.g. random chars, simple math, dictionary word, ...) * Custom generators, noise and filter functions alter the look of the generated image * Supports text-to-speech audio output of the challenge text, for improved accessibility * Ajax refresh Requirements ++++++++++++ * Django 4.2+, Python3.8+ * A recent version of the Pillow compiled with FreeType support * Flite is required for text-to-speech (audio) output, but not mandatory Documentation +++++++++++++ Read the `documentation online `_. django-simple-captcha-0.7.0/SECURITY.md000066400000000000000000000012761523260570200174510ustar00rootroot00000000000000# Security Policy ## Supported Versions Only the latest release of django-simple-captcha receives security fixes. Please upgrade to the newest version before reporting issues. ## Reporting a Vulnerability Please report security vulnerabilities privately: - Preferred: open a [GitHub security advisory](https://github.com/mbi/django-simple-captcha/security/advisories/new) - Alternatively: email mbi@mbi.me Do **not** open a public issue for security reports. Please include a description of the vulnerability, steps to reproduce, and the affected version(s). You can expect an acknowledgement within a few days; we will work with you on a fix and coordinate disclosure once a patch is released. django-simple-captcha-0.7.0/captcha/000077500000000000000000000000001523260570200172555ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/__init__.py000066400000000000000000000002131523260570200213620ustar00rootroot00000000000000VERSION = (0, 7, 0) def get_version(): "Return the version as a human-format string." return ".".join([str(i) for i in VERSION]) django-simple-captcha-0.7.0/captcha/conf/000077500000000000000000000000001523260570200202025ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/conf/__init__.py000066400000000000000000000000001523260570200223010ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/conf/settings.py000066400000000000000000000101571523260570200224200ustar00rootroot00000000000000import logging import os import warnings from PIL.features import check as check_pil_feature from django.conf import settings from django.utils.module_loading import import_string CAPTCHA_FONT_PATH = getattr( settings, "CAPTCHA_FONT_PATH", os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "fonts/Vera.ttf")), ) CAPTCHA_FONT_SIZE = getattr(settings, "CAPTCHA_FONT_SIZE", 22) CAPTCHA_LETTER_ROTATION = getattr(settings, "CAPTCHA_LETTER_ROTATION", (-35, 35)) CAPTCHA_BACKGROUND_COLOR = getattr(settings, "CAPTCHA_BACKGROUND_COLOR", "#ffffff") CAPTCHA_FOREGROUND_COLOR = getattr(settings, "CAPTCHA_FOREGROUND_COLOR", "#001100") CAPTCHA_LETTER_COLOR_FUNCT = getattr(settings, "CAPTCHA_LETTER_COLOR_FUNCT", None) CAPTCHA_CHALLENGE_FUNCT = getattr( settings, "CAPTCHA_CHALLENGE_FUNCT", "captcha.helpers.random_char_challenge" ) CAPTCHA_NOISE_FUNCTIONS = getattr( settings, "CAPTCHA_NOISE_FUNCTIONS", ("captcha.helpers.noise_arcs", "captcha.helpers.noise_dots"), ) CAPTCHA_FILTER_FUNCTIONS = getattr( settings, "CAPTCHA_FILTER_FUNCTIONS", ("captcha.helpers.post_smooth",) ) CAPTCHA_WORDS_DICTIONARY = getattr( settings, "CAPTCHA_WORDS_DICTIONARY", "/usr/share/dict/words" ) CAPTCHA_PUNCTUATION = getattr(settings, "CAPTCHA_PUNCTUATION", """_"',.;:-""") CAPTCHA_FLITE_PATH = getattr(settings, "CAPTCHA_FLITE_PATH", None) CAPTCHA_SOX_PATH = getattr(settings, "CAPTCHA_SOX_PATH", None) CAPTCHA_TIMEOUT = getattr(settings, "CAPTCHA_TIMEOUT", 5) # Minutes CAPTCHA_LENGTH = int(getattr(settings, "CAPTCHA_LENGTH", 4)) # Chars # CAPTCHA_IMAGE_BEFORE_FIELD = getattr(settings, 'CAPTCHA_IMAGE_BEFORE_FIELD', True) CAPTCHA_DICTIONARY_MIN_LENGTH = getattr(settings, "CAPTCHA_DICTIONARY_MIN_LENGTH", 0) CAPTCHA_DICTIONARY_MAX_LENGTH = getattr(settings, "CAPTCHA_DICTIONARY_MAX_LENGTH", 99) CAPTCHA_IMAGE_SIZE = getattr(settings, "CAPTCHA_IMAGE_SIZE", None) CAPTCHA_MATH_CHALLENGE_OPERATOR = getattr( settings, "CAPTCHA_MATH_CHALLENGE_OPERATOR", "*" ) CAPTCHA_GET_FROM_POOL = getattr(settings, "CAPTCHA_GET_FROM_POOL", False) CAPTCHA_GET_FROM_POOL_TIMEOUT = getattr(settings, "CAPTCHA_GET_FROM_POOL_TIMEOUT", 5) CAPTCHA_TEST_MODE = getattr(settings, "CAPTCHA_TEST_MODE", False) CAPTCHA_2X_IMAGE = getattr(settings, "CAPTCHA_2X_IMAGE", True) CAPTCHA_ANIMATED = getattr(settings, "CAPTCHA_ANIMATED", False) CAPTCHA_ANIMATED_USE_AVIF = CAPTCHA_ANIMATED and check_pil_feature("avif") # Failsafe if CAPTCHA_DICTIONARY_MIN_LENGTH > CAPTCHA_DICTIONARY_MAX_LENGTH: CAPTCHA_DICTIONARY_MIN_LENGTH, CAPTCHA_DICTIONARY_MAX_LENGTH = ( CAPTCHA_DICTIONARY_MAX_LENGTH, CAPTCHA_DICTIONARY_MIN_LENGTH, ) # CAPTCHA_TEST_MODE disables captcha verification (any answer "passed" is # accepted). Warn loudly when it is enabled outside of DEBUG to avoid silently # shipping an unprotected deployment. def _warn_if_test_mode_enabled(test_mode=CAPTCHA_TEST_MODE, debug=None): if debug is None: debug = getattr(settings, "DEBUG", False) if test_mode and not debug: msg = ( "CAPTCHA_TEST_MODE is enabled while Django DEBUG is False. Captcha " "verification is effectively disabled: any answer is accepted. " "This must never be enabled in a production deployment." ) logging.getLogger(__name__).error(msg) warnings.warn(msg, RuntimeWarning, stacklevel=2) _warn_if_test_mode_enabled() def _callable_from_string(string_or_callable): if callable(string_or_callable): return string_or_callable elif isinstance(string_or_callable, str): return import_string(string_or_callable) def get_challenge(generator=None): return _callable_from_string(generator or CAPTCHA_CHALLENGE_FUNCT) def noise_functions(): if CAPTCHA_NOISE_FUNCTIONS: return map(_callable_from_string, CAPTCHA_NOISE_FUNCTIONS) return [] def filter_functions(): if CAPTCHA_FILTER_FUNCTIONS: return map(_callable_from_string, CAPTCHA_FILTER_FUNCTIONS) return [] def get_letter_color(index, challenge): if CAPTCHA_LETTER_COLOR_FUNCT: return _callable_from_string(CAPTCHA_LETTER_COLOR_FUNCT)(index, challenge) return CAPTCHA_FOREGROUND_COLOR django-simple-captcha-0.7.0/captcha/fields.py000066400000000000000000000144141523260570200211010ustar00rootroot00000000000000from django.core.exceptions import ImproperlyConfigured from django.forms import ValidationError from django.forms.fields import CharField, MultiValueField from django.forms.widgets import HiddenInput, MultiWidget, TextInput from django.urls import NoReverseMatch, reverse from django.utils import timezone from django.utils.translation import gettext_lazy from captcha.conf import settings from captcha.models import CaptchaStore class CaptchaHiddenInput(HiddenInput): """Hidden input for the captcha key.""" # Use *args and **kwargs because signature changed in Django 1.11 def build_attrs(self, *args, **kwargs): """Disable autocomplete to prevent problems on page reload.""" attrs = super().build_attrs(*args, **kwargs) attrs["autocomplete"] = "off" return attrs class CaptchaAnswerInput(TextInput): """Text input for captcha answer.""" # Use *args and **kwargs because signature changed in Django 1.11 def build_attrs(self, *args, **kwargs): """Disable automatic corrections and completions.""" attrs = super().build_attrs(*args, **kwargs) attrs["autocapitalize"] = "off" attrs["autocomplete"] = "off" attrs["autocorrect"] = "off" attrs["spellcheck"] = "false" return attrs class BaseCaptchaTextInput(MultiWidget): """ Base class for Captcha widgets """ def __init__(self, attrs=None): widgets = (CaptchaHiddenInput(attrs), CaptchaAnswerInput(attrs)) super().__init__(widgets, attrs) def decompress(self, value): if value: return value.split(",") return [None, None] def fetch_captcha_store(self, name, value, attrs=None, generator=None): """ Fetches a new CaptchaStore This has to be called inside render """ try: reverse("captcha-image", args=("dummy",)) except NoReverseMatch: raise ImproperlyConfigured( "Make sure you've included captcha.urls as explained in the INSTALLATION section on http://readthedocs.org/docs/django-simple-captcha/en/latest/usage.html#installation" ) if settings.CAPTCHA_GET_FROM_POOL: key = CaptchaStore.pick() else: key = CaptchaStore.generate_key(generator) # these can be used by format_output and render self._value = [key, ""] self._key = key self.id_ = self.build_attrs(attrs).get("id", None) def id_for_label(self, id_): if id_: return id_ + "_1" return id_ def image_url(self): return reverse("captcha-image", kwargs={"key": self._key}) def audio_url(self): return ( reverse("captcha-audio", kwargs={"key": self._key}) if settings.CAPTCHA_FLITE_PATH else None ) def refresh_url(self): return reverse("captcha-refresh") class CaptchaTextInput(BaseCaptchaTextInput): template_name = "captcha/widgets/captcha.html" def __init__( self, attrs=None, id_prefix=None, generator=None, # output_format=None, ): self.id_prefix = id_prefix self.generator = generator super().__init__(attrs) def build_attrs(self, *args, **kwargs): ret = super().build_attrs(*args, **kwargs) if self.id_prefix and "id" in ret: ret["id"] = "%s_%s" % (self.id_prefix, ret["id"]) return ret def id_for_label(self, id_): ret = super().id_for_label(id_) if self.id_prefix and "id" in ret: ret = "%s_%s" % (self.id_prefix, ret) return ret def get_context(self, name, value, attrs): """Add captcha specific variables to context.""" context = super().get_context(name, value, attrs) context["image"] = self.image_url() context["audio"] = self.audio_url() return context def render(self, name, value, attrs=None, renderer=None): self.fetch_captcha_store(name, value, attrs, self.generator) extra_kwargs = {} extra_kwargs["renderer"] = renderer return super().render(name, self._value, attrs=attrs, **extra_kwargs) class CaptchaField(MultiValueField): def __init__(self, *args, **kwargs): fields = (CharField(show_hidden_initial=True), CharField()) if "error_messages" not in kwargs or "invalid" not in kwargs.get( "error_messages" ): if "error_messages" not in kwargs: kwargs["error_messages"] = {} kwargs["error_messages"].update( {"invalid": gettext_lazy("Invalid CAPTCHA")} ) kwargs["widget"] = kwargs.pop( "widget", CaptchaTextInput( id_prefix=kwargs.pop("id_prefix", None), generator=kwargs.pop("generator", None), ), ) super().__init__(fields, *args, **kwargs) def compress(self, data_list): if data_list: return ",".join(data_list) return None def clean(self, value): super().clean(value) response, value[1] = (value[1] or "").strip().lower(), "" if not settings.CAPTCHA_GET_FROM_POOL: CaptchaStore.remove_expired() if settings.CAPTCHA_TEST_MODE and response.lower() == "passed": # automatically pass the test try: # try to delete the captcha based on its hash CaptchaStore.objects.get(hashkey=value[0]).delete() except CaptchaStore.DoesNotExist: # ignore errors pass elif not self.required and not response: pass else: store = CaptchaStore.objects.filter( hashkey=value[0], expiration__gt=timezone.now() ).first() # A captcha can only be attempted once: invalidate the store # even when the response is wrong, to prevent brute-force attacks. if store is not None: store.delete() if store is None or store.response != response: raise ValidationError( getattr(self, "error_messages", {}).get( "invalid", gettext_lazy("Invalid CAPTCHA") ) ) return value django-simple-captcha-0.7.0/captcha/fonts/000077500000000000000000000000001523260570200204065ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/fonts/COPYRIGHT.TXT000066400000000000000000000134761523260570200223720ustar00rootroot00000000000000Bitstream Vera Fonts Copyright The fonts have a generous copyright, allowing derivative works (as long as "Bitstream" or "Vera" are not in the names), and full redistribution (so long as they are not *sold* by themselves). They can be be bundled, redistributed and sold with any software. The fonts are distributed under the following copyright: Copyright ========= Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. Copyright FAQ ============= 1. I don't understand the resale restriction... What gives? Bitstream is giving away these fonts, but wishes to ensure its competitors can't just drop the fonts as is into a font sale system and sell them as is. It seems fair that if Bitstream can't make money from the Bitstream Vera fonts, their competitors should not be able to do so either. You can sell the fonts as part of any software package, however. 2. I want to package these fonts separately for distribution and sale as part of a larger software package or system. Can I do so? Yes. A RPM or Debian package is a "larger software package" to begin with, and you aren't selling them independently by themselves. See 1. above. 3. Are derivative works allowed? Yes! 4. Can I change or add to the font(s)? Yes, but you must change the name(s) of the font(s). 5. Under what terms are derivative works allowed? You must change the name(s) of the fonts. This is to ensure the quality of the fonts, both to protect Bitstream and Gnome. We want to ensure that if an application has opened a font specifically of these names, it gets what it expects (though of course, using fontconfig, substitutions could still could have occurred during font opening). You must include the Bitstream copyright. Additional copyrights can be added, as per copyright law. Happy Font Hacking! 6. If I have improvements for Bitstream Vera, is it possible they might get adopted in future versions? Yes. The contract between the Gnome Foundation and Bitstream has provisions for working with Bitstream to ensure quality additions to the Bitstream Vera font family. Please contact us if you have such additions. Note, that in general, we will want such additions for the entire family, not just a single font, and that you'll have to keep both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add glyphs to the font, they must be stylistically in keeping with Vera's design. Vera cannot become a "ransom note" font. Jim Lyles will be providing a document describing the design elements used in Vera, as a guide and aid for people interested in contributing to Vera. 7. I want to sell a software package that uses these fonts: Can I do so? Sure. Bundle the fonts with your software and sell your software with the fonts. That is the intent of the copyright. 8. If applications have built the names "Bitstream Vera" into them, can I override this somehow to use fonts of my choosing? This depends on exact details of the software. Most open source systems and software (e.g., Gnome, KDE, etc.) are now converting to use fontconfig (see www.fontconfig.org) to handle font configuration, selection and substitution; it has provisions for overriding font names and substituting alternatives. An example is provided by the supplied local.conf file, which chooses the family Bitstream Vera for "sans", "serif" and "monospace". Other software (e.g., the XFree86 core server) has other mechanisms for font substitution. django-simple-captcha-0.7.0/captcha/fonts/README.TXT000066400000000000000000000005011523260570200217400ustar00rootroot00000000000000Contained herin is the Bitstream Vera font family. The Copyright information is found in the COPYRIGHT.TXT file (along with being incorporated into the fonts themselves). The releases notes are found in the file "RELEASENOTES.TXT". We hope you enjoy Vera! Bitstream, Inc. The Gnome Project django-simple-captcha-0.7.0/captcha/fonts/Vera.ttf000066400000000000000000002006141523260570200220250ustar00rootroot00000000000000OS/2´_ôcëpVPCLTÑŠ^—ëÈ6cmap¤Ãè ±lXcvt ÿÓ9üüfpgmç´ñÄ&`‹gaspH glyf tAÏ&ìŠ~hdmx4ð!ìHheadÝ„¢ÐT6hheaEoëL$hmtx ÆŽ²´Ä0kernÜRÕ™½ -ŠlocaóËÒ=»„maxpG:ë, nameټȵßpost´Z/»¸ôŽprep;ñ øh::_:: dM0­l  ƒ p t › &   Y &  &   c . 5 ` õ s 0¡ & {Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.Bitstream Vera SansBitstreamVeraSans-RomanRelease 1.10Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.http://www.bitstream.comCopyright (c) 2003 by Bitstream, Inc. All Rights Reserved.Bitstream Vera SansBitstreamVeraSans-RomanRelease 1.10Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.http://www.bitstream.com5¸ËËÁªœ¦¸fqË ²…u¸Ãˉ-˦ðÓª‡ËªJ3ËÙôT´œ99N´R¸çÍ7sÍ`s3¢V¦V9Åɸßsºé3¼DßͪåªË�¤{¸o{R�ÇÍššoËÍžÓðºƒÕ˜HžÕÁËöƒT3fÓǤÍ�šsÕ þ+¤´œbœ-ÕÕÕð{T¤¸#Ӹ˦Ãì“ Ó\qÛ…#¨H�99`�Õš#fy```{œw`ªé`b{Å{´RÍf¼fwÍ;…‰�{ÍJ/œœ}oo5jo{®²-–�{öƒT7ö�œáf��öÍD)fîs¸€@ÿûþúù%ø2÷–öõþôþó%òñ–ð%ïŠAïþî–í–ìúëúêþé:èBçþæ2åäSå–äŠAäSãâ/ãúâ/áþàþß2ÞÝ–ÜþÛÚ}Ù»ØþÖŠAÖ}ÕÔGÕ}ÔGÓÒÓþÒÑþÐþÏþÎþÍ–ÌËÌþËÊ2ÉþÆ…ÆÅÄþÃþÂþÁþÀþ¿þ¾þ½þ¼þ»þº¹†%¹þ¸·»¸þ·¶]·»·€¶µ%¶]@ÿ¶@µ%´þ³–²þ±þ°þ¯þ®d­¬«%¬d«ª«%ª©ŠA©ú¨þ§þ¦þ¥¤þ£¢£2¢¡d ŠA –Ÿþž� žþ� œ›œd›š›š™ ˜þ—– —þ– •ŠA•–”“”(“’ú‘�»‘þ��]�»�€�Ž%�]�@Ž%�þŒ‹.Œþ‹.І%ŠA‰ˆ ‰ˆ ‡†%‡d†…†%…„þƒ‚ƒþ‚�þ€þþ@ÿ~}}~þ}}|d{T{%zþyþxw v uþtúsúrúqúpþoþnþl!kþjBjSiþh}gBfþeþdþcþbþa:`ú^ ]þ[þZþYX YúX WW2VþUTUBTSSRQJQþP OþNMNþMLþKJKþJIJI IH GþF–E–DþC-CúB»AK@þ?þ>=>=<=<; <@ÿ; :þ9þ878ú76765 65 43 21 2þ1 0/ 0 / .- .- ,2+*%+d*)*%)('%(A'%&% &% $þ#þ"!! dú d BþúBBþdþþþþBþ-B}dþ  þ   þ  þ-þdþ@-þ-þ¸d…�++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++¶, °%Id°@QX ÈY!-,°%Id°@QX ÈY!-,  °P° y ¸ÿÿPXY°°%°%#á °P° y ¸ÿÿPXY°°%á-,KPX °ýEDY!-,°%E`D-,KSX°%°%EDY!!-,ED-fþ–f¤@ ûû/ÄÔì1ÔìÔì0!%!!füsüåþ–øòr)5Õ @@ƒ� ü<ì2991/äüÌ0K° TX½ @ ÿÀ878Y¶ P ]%3#3#5ËËË¢þþÕýqþ›eŪéÕM@„�üüÜì1ô<ì20K°TK°T[X½@ÿÀ878Y@0 @ P ` p   ¿ ]#!#oª$ªÕýÕ+ýÕ+ž¾`@1 ‡  ‡   üÌ91/<Ô<<ü<<Ô<<Ä2ì220@   ]!! !3!!!!#!#!5!!5!þÝT%Dh$i g8þ¡R>þ›h gþÛg¡hþÅ`Tþ¾if…þ²‡þaŸþašþ²™þbžþbž™NšŸªþÓm!(/Õ@U" '&( /)/))/B" ) *!††#Љ*Љ- ) " & 0ü<ìô<ü<ôäì1/äìÄÔäì2Äîî99990KSXíí9í9íY"K° TX½0@00ÿÀ878YK° TK°T[K°T[X½0ÿÀ00@878Y#.'5.546753.'>54&´diÒjfÑoÝÉÚÌd]®SS¯\ãÖãÖdtzqá�{þÓ---´@AÈ$¬–£¼ëè¯*.þU#´œ©Ãš jXV`ÕþOnZXhqÿã)ð #'3•@6$%&%&'$'B’ ’.�’$’ �&Œ($‘4'!%   ! + 1 4üÄìôìîöî991ä2ô<äìîöîî0KSXííY"K° TK° T[K° T[K°T[K°T[K° T[X½4@44ÿÀ878Y"32654&'2#"&546"32654&%3#2#"&546ÑWccWUccUžº»� º»ü—VcbWWcd1 üZ ž¼»ŸŸ¹º‘”„‚••‚ƒ•Ü»»ÛÛ»¼Ûa•‚„””„�–ùó Û»½ÚÛ¼ºÜ�ÿãþð 0Í@–  † †  † †††  !         B  (('•+•'”$‘Œ .  .'.'!!1üìÄÔÔìÆî99999991/ÆäöæîîÆ9990KSXíí9í9í9í9í9íí9í9ííí9Y"²2]@² " ) **&:4D ^YZ UZZY0g{›š™—• “••"™-  ' (   2'') #**(/2; 49?2J LKFO2VZ Y UY\_2j i`2uy z““—•œœŸš › š �2 2°29]]3267 >73#'#"5467.54632.#"ò[UÔ _¦Iþ{ü;Bº h]ü�häƒñþΆ†02Þ¸S¥UWžDiƒ;#Q¡X’Â?@�ýøYËr„þþ~þã“YW×€ác?}<¢Å$$¶/1oX3gŪoÕB@ „�üì1ôì0K°TK°T[X½@ÿÀ878Y@ @P`p� ]#oªÕýÕ+°þò{ O@˜—  Üä2ì991üì0K°TX½@ÿÀ878YK°TX½ÿÀ@878Y#&547{†‚ƒ… –•”—æþ>ççþ;åëÆàßÄì¤þòo @˜— Ü<ôì991üì03#654¤ –••– …ƒƒìþ<ßàþ:ëåÅççÂ=JÃðN@,  ™ ™ ‘    Ô<ä2Ü<ä2991ôÔ<ì2Äì2990%#'%%73%Ãþ™g:þ°rþ°:gþ™:PrPßÂÃbËþ‡yËbÃÂcËyþ‡ËÙÛ #@ œ  Üü<ü<ì1/Ô<ü<Ä0!!#!5!®-ýÓ¨ýÓ-ýÓªýÓ-ª-žÿÃþ@ žƒüìÔÌ1üì073#ðÓ¤�Rþ¬þÀ@d߃¶œÜÌ1Ôì0!!dý僤ۮþ·ƒüì1/ì073#ÛÓÓþþÿB²Õ-@BŸ�/Ä991ôì0KSXííY"3#ªýøªÕùm‡ÿã�ð #@   ‘Œ üìôì1äôìî0"32'2#"‹œ��œ����û þ÷ûûþ÷ PþÍþÌþÍþÍ3343 þsþ†þ‡þs�yz�áZÕ K@B  �   ÔìÄüì1/ì2ôìÔì0KSXY"K°TX½ ÿÀ @878Y´]7!5%3!!þJþ™eÊJü¤ªsH¸HúÕª–Jð¥@'B¡”  ‘   üÄÔìÀÀ91/ì2ôìôì0KSXíí9Y"K°TK°T[K°T[X½@ÿÀ878Y@2UVVzzv‡tvust‚†‚‚‚¨¨]]%!!567>54&#"5>32‰ÁüLs�3aM§†_ÓxzÔXèE[þôªªªw‘:m—Iw–BCÌ12èÂ\¥pþëœÿãsð({@. † †     “  “#‘Œ£)&  )üÄÄÔìôì991ìäôäìæîîîî90K°TK°T[X½)@))ÿÀ878Y@ daa d!]!"&'532654&+532654&#"5>32?‘£þÐþè^ÇjTÈm¾Ç¹¥®¶•ž£˜S¾rsÉYæ Ž%Ä�Ýò%%Ã12–�„•¦wps{$&´ Ѳ|«d¤Õ Œ@   B  �   ÜÔ<Äì291/äÔ<ì290KSXÉÉY"K° TK° T[X½@ÿÀ878Y@* *HYiwŠ+&+6NO O Vfuz… ]] !33##!5þþ5þÕÕÉý^%üãÍü3¨þ `ÞÿãdÕu@#†  ‰  � Œ¤  üÄÔìÄî1ääôìæîþÄî90K°TK°T[X½@ÿÀ878YK°TX½ÿÀ@878Y!!>32!"&'532654&#"Ýý ,X,ú$þÔþï^ÃhZÀk­ÊÊ­Q¡TÕªþ’þîêñþõ Ë10¶œœ¶$&�ÿã–ð $X@$ †   ¥  ‰"‘Œ% " !%üììôìä1äôäüäîîî90@ËËÍÍÍËˤ²]]"32654&.#">32# !2¤ˆŸŸˆˆŸŸ L›LÈÓ;²káþðâþýþîPL›;º¢¡»»¡¢ºy¸$&þòþïW]þïëæþê�yb¥¨hÕc@B �üÌÄ991/ôì0KSXííY"K°TX½@ÿÀ878Y@X9Hg°°]]!#!¨ÀýâÓþý3ÕVú�+‹ÿã‹ð #/C@%  ' - ‘Œ'£0 $*$ !0üÄìôÄìîî991ìäôìîî990"32654&%&&54632#"$54632654&#"‹�¥¥��¦¥þ¥‚‘ÿÞßþ‘�’£þ÷÷÷þ÷¤H‘ƒ‚““‚ƒ‘Åš‡‡š›†‡šV ²€³Ðг€² "Æ�ÙèèÙ�Æat‚‚tt‚‚�ÿã‡ð$X@#†  ¥ ‰ ‘Œ%!"" %üìäôìì1äôìæþõîî90@ÄÂÀÀÀÂμé]]7532#"543 !"&2654&#"áLœKÈÓ:²làþûâþ±þåLœ>ˆŸŸˆˆŸŸ¸$& V\ëæþsþ†þŸþ[—º¢¡»»¡¢ºðÃ#@ƒ¦ƒü<ì21/ìôì073#3#ðÓÓÓÓþþ#þžÿÃ# %@ƒžƒ¦  ü<ì2ÔÌ1äüìî03#3#ðÓÓÓ¤�R#þýÙ¬þÀ@Ù^Û¦M@*œœœœB¨§$#üì291ôì90KSXííííY" 5Ûûøúþðþ‘þ“¶ѦÑÙ`Û¢@ œœ#ü<Ä21ÔìÔì0!!!!Ùúþúþ¢¨ðªÙ^Û¦O@+œœœœB¨§$#ü<ì91ôì90KSXííííY"55Ùúþð¶þ/¦þ/¶m“°ð$p@+$  †ˆ•‘ƒ   &%ÜÄüìÔìî99991/îöþôîÍ9990K° TX½%@%%ÿÀ878Y¶y z z ]%3##546?>54&#"5>32‡ËËÅ¿8ZZ93ƒlO³a^Ág¸ßHZX/'þþ‘še‚VY5^1YnFC¼98ŸL‰VV/5<4‡þœq¢ L•@2  ©©L43¬0©7¬$©7CM34( (+(I+*(I,=MÜìüìþýþ<Æî991ÔÄüìþíÔÆÅî2Äî990K° TK° T[K°T[K°T[K°T[X½MÿÀMM@878Y@ NN/N?N]32654&#"#"&5463253>54&'&$#"3267#"$'&5476$32úŽ|{��zy�!<›g¬×Ø«gœ;�’¥?@hþÕ°{â`�±smi��ùhZ}þÙ˜¹þ¸€€†ˆ~�R½Ôk{KOþÂþè�£¤ŽŒ¥¤þHMIùÈÈúKLƒý ß±k¼Pƒ‹A@fþµÁŸþêjhmWQoagƒ}}I½¶J}‡® bæ{þùþÐhÕ º@A       B•�    ÔÄ91/<äÔì90KSXííííííííY"² ]@:XvpŒ VXP ghxv|rwx‡ˆ€ ˜™–]] !3#!#¼þî%þ{å9Òˆý_ˆÕý®ú+þ�ÉìÕ C@#• •�• ­ . !üì2üìÔì9991/ììôìî90²"]!2654&#!2654&#%!2#!“D£��£þ¼+”‘‘”þ çú€|•¥þðûýèÉý݇‹Œ…fþ>orqp¦À±‰¢ ˘ÈÚsÿã'ð6@ ¡® •¡®•‘Œ 0üì2ì1äôìôìîöî0´].# !267# !2'fç‚ÿþð‚çfjí„þ­þz†S†íbÕ_^þÇþØþÙþÇ^_ÓHHŸghŸGɰÕ.@• �•  2 üìôì99991/ìôì0²`]3 !%! )“ô5þáþËþBŸ²–þhþPþa/ûw.,¦þ—þ€þ~þ–É‹Õ .@••�•­   üì2ÔÄÄ1/ììôìî0² ]!!!!!!ɰýÇý9øü>ÕªþFªýãªÉ#Õ )@••�­ üì2ÔÄ1/ìôìî0² ]!!!!#ÉZýpPý°ÊÕªþHªý7sÿã‹ð9@ ••¡®•‘Œ43 üìüäüÄ1äôìôìþÔî990%!5!# !2&&# !26Ãþ¶uþæ þ¢þu‹^’opü‹þîþík¨Õ‘¦ýSU™mn™HF×_`þÎþÑþÒþÎ%É;Õ ,@•­� 8  üì2üì21/<ä2üì0²P ]3!3#!#ÉÊÞÊÊý"ÊÕýœdú+Çý9É“Õ9·¯üì1/ì0K°TX½ÿÀ@878Y@ 0@P`�Ÿ]3#ÉÊÊÕú+ÿ–þf“Õ M@ •°� 9 üìä991äüì990K°TX½ ÿÀ @878Y@ 0 @ P ` � Ÿ ]3+53265ÉÊÍãM?†nÕú“þòôª–ÂÉjÕ ï@(B¯  üì2ÔÄ91/<ì290KSXííííY"²]@’ ((764GFCUgvwƒˆ�”›ç    (+*66650 A@E@@@ b`hgwp ‹‹Ž�� š��¶µÅÅ×Öèéèê÷øù,]q]q3! !#ÉÊžýþöý3ÊÕý‰wýHüãÏý1ÉjÕ%@ •�:üìì1/äì0@ 0P€€]3!!ÉÊ×ü_ÕúÕªÉÕ ¿@4  B ¯   >  üìüì91/<Äì290KSXííííY"²p]@V   && & 45 i|{y €‚‚ � #,'( 4<VY ej vy ��•›]]! !###É-}-ÅþËþÄÕüøú+üúáÉ3Õ y@B¯6 üìüì991/<ì2990KSXííY"² ]@068HGif€ FIWXeiy…Š•šŸ ]]!3!#É–ÄþðýjÄÕûáú+áûsÿãÙð #@•• ‘Œ 3üìüì1äôìî0"32' ! 'ÜþýÜÜþÿÜ:xþˆþÆþÅþ‡yLþ¸þåþæþ¸HH¤þ[þžþŸþ[¤bb¥É�Õ:@•• �  ? üì2üì91/ôìÔì0@ ?_¯]32654&#%!2+#“þ�šš�þ8ÈûþÿûþÊ/ýÏ’‡†’¦ãÛÝâý¨sþøÙð R@*  B ••‘Œ    3üìüì9991Ääôìî990KSXíí9Y""32#'# ! 'ÜþýÜÜþÿ? ôÝ!#þÅþ‡y;:xÑLþ¸þåþæþ¸HHúÏþÝï¥ab¥þ[þžþüþŽÉTÕ±@5  B• • �   ?  üì2üÄì99991/<ôìÔì9990KSXíí9Y"²@]@Bz%%%&'&&& 66FFhuuwˆˆ˜˜]]#.+#! 32654&#�A{>ÍÙ¿J‹xÜÊÈüƒý‰þ’••’¼�~þh–bý‰ÕÖØ�ºOýƒ…‡ÿã¢ð'~@<    B ¡”••”%‘Œ( "-"(ÜÄìüìä99991äôäìîöîÆ90KSXí9í9Y"²)]¶)/)O)].#"!"&'532654&/.54$32HsÌ_¥³w¦zâ×þÝþçjï€{ìr­¼‡š{âÊõiÚ¤Å76€vce+Ù¶Ùà0/ÐEFˆ~n|-À«Æä&ÿúéÕJ@•�@@Ôäüä1/ôì20K° TX½@ÿÀ878Y@  @ p Ÿ ]!!#!ïýîËýîÕªúÕ+²ÿã)ÕK@ •Œ � 8Aüìüì1ä2ôì99990K°TX½@ÿÀ878Y¶�Ÿ]332653! ²Ë®Ã®ËþßþæþåþßÕüuðÓÓð‹ü\þÜþÖ*$hÕ·@'B¯ÔÄ91/ì290KSXííííY"²P]@b*GGZ}ƒ *&&))% 833<<7HEEIIGYVfiizvvyyu€˜—)]]!3 3JýÆÓÙÚÒýÇÕûéú+D¦Õ {@I      B ¯    ÔÌ91/<ì2290KSXííííííííY"²]@ò  ($ >>4 0 LMB @ Yjkg ` {|€ –•     !   # $ %  <:5306 9 ? 0FFJ@E@BBB@@ D M @@XVY Pfgab```d d d wv{xwtyywpx  †‡ˆ‰… Š �—Ÿ¯[]]3 3 3# #DÌ:9ã:9Íþ‰þþÅþÂþÕûîûîú+úð=;Õ ]@F      B ¯   ÔÄÜÄ91/<ì290KSXííííííííY"K° TK° T[K°T[X½ ÿÀ @878Y@¸ '' 486 KX[fkww †€‡‹… ”�—�–     &()&(' ) 54<;:;4 4 8 ? H O X _ eejjhiil l xyyx}  x €€ƒˆ…„ƒ � ��”——•“ Ÿ ¯ @]]3 3 # #�ÙsuÙþ Ùþ\þYÚÕýÕ+ý3üø{ý…ÿüçÕ”@(B¯@@ Ôäüä91/ì290KSXííííY"² ]@<5000F@@@QQQe„“ &)78@ ghxp Ÿ ]]3 3#Ùž›ÙýðËÕýšfüòý9Ç\Õ ›@B•�•B ÜÄÔä991/ìôì0KSXííY"K° TK° T[X½ @ ÿÀ878Y@@ )&8HGH    / 59? GJO UYfio wx Ÿ ]]!!!5!s•üPÇû=°ügÕšûoªš‘°þòXS@©²©±CÜüÌ21üìôì0K° TX½ÿÀ@878YK°TK°T[X½@ÿÀ878Y!#3!°¨ððþX�ùü�ÿB²Õ-@BŸ�/Ä991ôì0KSXííY"#ªªýøÕùm“Çþòo<@©²©±Cü<Üì1üìôì0K°TK°T[X½ÿÀ@878Y!53#5oþXïïøÞ��Ù¨ÛÕ@ �ÜÌ91ôÌ290##¼ÉþHþHÉÕýÓ‹þu-ÿìþþ¬µ©ÄÄ1Ôì0!5ûØþ¬��ªð‰f1@ ´³DÜì1ôì0K° TK°T[X½ÿÀ@878Y #o™þºfþŠv{ÿã-{ %¼@'  ©¹ †º¹#¸Œ   E&üìÌÔì22991/ÄäôüôìÆîî9990@n0000 0!0"?'@@@@ @!@"PPPP P!P"P'p'…‡‡‡ ‡!…"�' 'ð'000 0!@@@ @!PPP P!``` `!ppp p!€€€ €!]]"326=7#5#"&5463!54&#"5>32¾ß¬�o™¹¸¸?¼ˆ¬Ëýû§—`¶Te¾Zóð3f{bsÙ´)Lý�ªfaÁ¢½À‹..ª''üºÿ㤠8@¹  ¹Œ¸—G Füì22ôì1/ìäôÄìÆî0¶`€ ]4&#"326>32#"&'#3å§’’§§’’§ýŽ:±{ÌÿÿÌ{±:¹¹/ËççËËççRdaþ¼þøþøþ¼ad¨qÿãç{?@†ˆ† ˆ ¹¹¸Œ HEüä2ì1äôìþôîõî0@ €� ].#"3267#"!2çN�P³ÆÆ³P�NM¥]ýþÖ-U¢5¬++ãÍÍã++ª$$>:#qÿãZ8@¹¹Œ¸—G Eüìôì221/ìäôÄìÄî0¶`€ ]3#5#"3232654&#"¢¸¸:±|ËÿÿË|±ýǧ’’¨¨’’§¶^ùì¨daDDaþËççËËççqÿã{p@$ †ˆ©¹ »¹¸ ŒKEüìôìÄ91äôìäîîôî90@)?p Ðð?????,// , ooooo ]q]!3267# 32.#"ü² Í·jÇbcÐkþôþÇ)ü⸥ˆš¹^Z¾Ç44®*,8 CþÝÄ—´®ž/øp@ ©‡—¼    Lü<Äü<ÄÄ991/ä2üìî2990K° TX½ÿÀ@878YK°TX½@ÿÀ878Y¶@P ]#"!!##535463ø°cM/þѹ°°®½™Phc�ü/Ñ�N»«qþVZ{ (J@#  †¹¹&#¸'¼ ¹½& G E)üÄìôì221/ÄäìäôÄìþÕî990¶`*€* *]4&#"326!"&'5326=#"3253¢¥•”¥¥”•¥¸þþúa¬QQžRµ´9²|ÎüüÎ|²9¸=ÈÜÜÈÇÜÜëþâþé³,*½¿[cb::bcªºd4@ ‡¸ — N  Füì2ôì1/<ìôÄì90²`]#4&#"#3>32d¸||•¬¹¹B³uÁƤý\žŸž¾¤ý‡ýžedïÁy+@¾±¼Fü<ì21/äüì0@  @ P ` p ]3#3#Á¸¸¸¸`û éÿÛþVy D@ ¾ ‡½¼ ±O  Fü<ì2ä991ìäôìî990@ @P`p]3+532653#Á¸£µF1iL¸¸`ûŒÖÀœa™(麜 ¼@)B¼— F üì2ÔÄ91/<ìä90KSXííííY"² ]@_ ')+Vfgsw‚‰Ž“–—£    ('(++@ h` ‰…‰��š—ª§¶ÅÖ÷ð÷ð]q]33 ##º¹%ëý®kðýǹüiãýôý¬#ýÝÁy"·—Füì1/ì0@ @P`pð]3#Á¸¸ùìº{"Z@&  ‡ ¸¼PPF#üì2üüüì91/<<äô<Äì290@0$P$p$�$ $ $¿$ß$ÿ$ ]>32#4&#"#4&#"#3>32)EÀ‚¯¾¹ru�¦¹rw�¦¹¹?°yz«‰|võâý\ž¡œ¾¤ý‡ž¢›¿£ý‡`®gb|ºd{6@ ‡¸ ¼ N  Füì2ôì1/<äôÄì90´`Ï]#4&#"#3>32d¸||•¬¹¹B³uÁƤý\žŸž¾¤ý‡`®edïqÿãu{ J@¹¹ ¸Œ QEüìôì1äôìî0@#?{{   {  { ð]"32654&'2#"s”¬«•“¬¬“ðþîðñþïßçÉÉçèÈÇéœþÈþìþíþÇ98ºþV¤{>@¹¹¸Œ½¼ GFüì22ôì1äääôÄìÄî0@ `€ à]%#3>32#"&4&#"326s¹¹:±{ÌÿÿÌ{±8§’’§§’’§¨ý® ªdaþ¼þøþøþ¼aëËççËËççqþVZ{ >@¹  ¹¸Œ½¼ GEüìôì221äääôÄìÆî0@ `€ à]32654&#"#"3253#/§’’¨¨’’§s:±|ËÿÿË|±:¸¸/ËççËËççý®daDDadªùöºJ{0@  ‡¸ ¼ FüÄì21/äôìÄÔÌ90´PŸ].#"#3>32JI,œ§¹¹:º….´˾ý²`®fcoÿãÇ{'ç@<  S  SB †‰†‰¹¹%¸Œ( R"E(üÄìÔìä99991äôìþõîõî90KSXí9í9Y"²']@m   . , , , ; ; ; ; $( ( *//*(() )!$'† † † †      '/)?)_))€)�) )ð)]]q.#"#"&'532654&/.54632‹N¨Z‰‰b”?Ä¥÷ØZÃlfÆa‚Œe«@«˜àÎf´?®((TT@I!*™‰œ¶##¾55YQKP%$•‚ž¬7òž8@©¼‡  Fü<Äü<Ä2991/ìô<Äì2990²¯]!!;#"&5#53w{þ…Ks½½Õ¢‡‡žþÂ�ý ‰NšŸÒ`�>®ÿãX`6@ ‡Œ ¼  NFüìôì21/ä2ôÄì90´`Ï]332653#5#"&®¸||•­¸¸C±uÁȺ¦ýaŸŸ¾¤{û ¬fcð=`@'B¿ÔÄ91/ì290KSXííííY"K° TX½ÿÀ@878YK°TK°T[X½@ÿÀ878Y@ŽHj{†€‘¤  &&)) 55::0FFIIFH@VVYYPffiigh`ut{{uz……‰‰‰†––—š˜˜—¨§°Àßÿ>]]3 3#=Ã^^Ãþ\ú`üT¬û V5` @IU U U U   B ¿    ÔÌ91/<ì2290KSXííííííííY"K° TK°T[K°T[K°T[K° T[X½ ÿÀ @878YK° TK° T[K°T[X½ @ ÿÀ878Y@ÿ" 5 IIF @ [[U P nnf yy‡™˜” ¼¼ÎÇÏ         %%#'!%""%' $ ! # 9669 0FHF@B@@@D D D @@VVVPQRRPS T U cdejejjjn a g ouuy}x}zzxy  { v } ‡ˆ�——”“œ›˜˜™@/– Ÿ¦¦¤¤««©©«¤ ¯µ±½»¸ ¿ÄÃÌÊy]]333# #V¸æåÙæå¸þÛÙñòÙ`ü–jü–jû –üj;y` Z@F      B ¿  ÔÄÔÄ91/<ì290KSXííííííííY"K° TK°T[K°T[K°T[X½ ÿÀ @878YK°TX½ @ ÿÀ878Y@˜   & =1 UWX f vzvt �‚ ™Ÿ—’ � ¦©¯¥£       )&% * :9746 9 0 IFE J @ YVYYWVYVV Y P o x ›”«¤° Ï ß ÿ /]] # # 3 dþkªÙþºþºÙ³þrÙ))`ýßýÁ¸þHJþq�=þV`¢@C        B  ‡½ ¼  ÔÄÄ91ä2ôì9990KSXíííííí2Y"K° TK°T[X½ÿÀ@878YK°TX½@ÿÀ878Y@ð     # 5 I O N Z Z j ‡ € “        '$$  )( % $ $ ' ** 755008 6 6 8 990A@@@@@@@@B E G II@TQQUPPVUVW W U U YYPffh ii`{xx‰Š … … ‰ ‰‰™ • • šš¤ ¤ ««°Ïßÿe]]+5326?3 3“N”|“lLT3!þ;Ã^^ÃhÈzšH†TNü”lXÛ` ´@B©¼© ÜÄ2Ä991/ìôì0KSXííY"K° TK° T[X½ @ ÿÀ878YK°TX½ ÿÀ @878Y@B&GI  + 690 @@E@@CWY_ ``f``b € ¯ ]]!!!5!qjýL´ü}´ýe`¨üÛ“¨%þ²$‚@4 %   ! © ©À ©±% $  C %Ô<Äü<Ä299999991üìÄôìî99999990K° TX½%ÿÀ%%@878Y²&]#"&=4&+5326=46;#"3>ù©lŽ==�k©ù>D�V[noZV�¾�”Ýï—t�s•ðÝ“�X�ø�ŽŽœø�Xþ®·±Ôì1üÌ0#®ªøþ²$ž@6%   ©©#À©±%#C %Ô<Ä2ü<Ä99999991üìÄôìî99999990K° TX½%@%%ÿÀ878YK°TX½%ÿÀ%%@878Y²&]326=467.=4&+532;#"+FŒUZooZUŒF?ù§lŽ>>Žl§ù?¾V�øœŽŽ�øŽW�“Ýð•s�t—ïÝ”ÙÓÛ1#@ œœ ÔÄ1ÔüÔìÀ990#"'&'&'&#"56632326Ûi³an’ ›^X¬bi³an“ ›^V©1²OD;>MS²OE<>LÿÿhN'$¼uhm !Ë@T   !!  ! !!!B  Á • Ž  !  VV!"ÔÄÔì2Ôî299999991/<æÖîÔî9990KSXííííííííY"² #]@  s › P#f iu {yyv v!€# ]]4&#"326!.54632#!#TY?@WX??Y˜þð!þX=>Ÿsr¡?<Òˆý_ˆÕZ?YWA?XXþóýN)sIs ¡rFv)ú‹þ�ÿÿsþu'ð'&Ý-ÿÿÉ‹k'(žuÿÿÉ3^'1þuÿÿsÿãÙN'2'uÿÿ²ÿã)N'8îuÿÿ{ÿã-f'D�Rÿÿ{ÿã-f'DCRÿÿ{ÿã-f'D×Rÿÿ{ÿã-'DŽRÿÿ{ÿã-7'DØRÿÿ{ÿã-'DÜRÿÿqþuç{'FÝ�ÿÿqÿãf'H�‹ÿÿqÿãf'HC‹ÿÿqÿãf'H׋ÿÿqÿã'HŽ‹ÿÿ�of'Ö�ÿÿÿÿǦf'ÖCÿÿÿÿÞ\f'Ö×ÿÿÿÿôF'ÖŽÿÿÿºd7'Qؘÿÿqÿãuf'R�sÿÿqÿãuf'RCsÿÿqÿãuf'R×sÿÿqÿãu'RŽsÿÿqÿãu7'RØsÿÿ®ÿãXf'X�{ÿÿ®ÿãXf'XC{ÿÿ®ÿãXf'X×{ÿÿ®ÿãX'XŽ{9ÿ;ÇÕ '@¹ � YW Y Ô<ìü<ì1äôÔ<ì203!!#!5!¨°oþ‘°þ‘oÕþ\™û£]™Ãu=ð  @ÃÄà ‘ Z[ZÜìüì1ôìüì0"32654&'2#"&546PnnPPnoO@v+..¹†‡´¸ooPOmmOOp�1.-rB„·´‡†º¬þÇ#˜!Q@+  †ˆ †ˆ ¹ ¹¸Œ"  "ÜìÔ<Ô<<ì221äô<ÄìÄþôîõî9990%&&'667#&73¦“¤¤JˆDF‰HA‰Mfñþ÷ ñfI‰ƒX⸹⡬)*ü *'ª#þä 32þá!�bð`@!† ©  ”‘   Ü<ÌÌü<ÄÔÄ1/ì2ôäìÔ<î2î990K° TX½ÿÀ@878Y´66].#"!!!!53#535632NLˆ=”t‡þy-üìÇÇÖè=—´¶))›Ô×�þ/ªªÑ�îó\ÿ=¢ð >‘@54&.#"#"&'532654/.5467.54632{?>‹ú?>�ÌS�8alÎÓƒ\]>9Ì­IšXW”:fqÝÖ€][;;ȦI™¨.Z.L…‡-[.Kˆ“¤''PGZswšeZŒ54m@ލ¤''TLf{x™f[�1,pE‚Ÿ3Ñ…! · Ç \ Ôì1Ôì04632#"&3­~|«¬}}¬ú|««|}¬¬žÿ;9Õ %@Á�]] ÔÔüÜì91Ä2ôì90!###&&54$yÀ�¾Ž×ëÕùfùáNݸ¾èºÿã¬/š@0-'!  *†¹*¹—Œ.  !' $'$-F0üÄüÌÆîÔîî99991/äþîþÕî990@@'(Š Š     ! "&  : :!MM I!I"jj ¥¥¦ ]]4632#"&'532654&/.5467.#"#ºïÚÐÛ—¨:A9¦`áÓ@ˆIPŒAtx;e\`W§—ƒq‚ˆ»qÈÛèàs`/Q*%jŽd¬·¤_[?T>7;‡[¬gp‹ƒû“åÍ/8L`@6EBC?2ÉH0É9JCÊ 9ÊÉÈ É$HE301BKL?gwyVpMIßÑ`3þœDåÍ/IC@&=Ë>:ÌAÊ$1Ë04ÌGÊÉÈ$É 7aD=0^* D^ JÜÌüìþí2î1/îöþýîÖîýîÖî02#"$'&5476$"32676654&'&&&&#"3267#"&54632˜mmllmmþù˜˜þùmmllmm˜ƒâ^^``^^⃄ã^]]^\^ã§B‚B•§«›@zBC‰FØûûØIˆÍnmmþúš˜þûmmnnmm˜šmmng^^^å‚�ã^^__^]⃅ã]^^õ�! ¯�Ÿ®"ôÐÑò'“FÕ >@!  É � b b cbcÔäüäÔìÔì91ô<<ì2Ô<<Ä903#######5J®¤ªqÃ7ËrqËrÉÕÿý¾äþÑ/þB^þä^sîRf1@ ´³DÔì1ôì0K° TK°T[X½ÿÀ@878Y3#‹Çþº™fþˆ×F)’@ÎÍddÜüÔì1ü<ì20K° TK° T[X½@ÿÀ878YK° TK° T[K°T[K°T[X½ÿÀ@878YK°TK°T[X½@ÿÀ878Y@````pppp]3#%3#^ËËþyËËÊÊÊÙ'ÛÝ>@" Ïœ Ï œ  Ü<Ä291Ô<Ì2ü<ìþ<ì990!!!!!'7!5!7!Ù}®/þHÃ{üúþþ}®þÕ¶Ãý‡¢;fÕ¨ðªþÇfÓªðHÕ‡@9  B• •••�•­    ÔÔ<ì2ÔÄÄ91/<ììÄôììîî0KSXííííY"²€]@gww† …–� ¿ ]!!!!!!#!5ýÇý9øü=ýð Íq‹þ¶ËÕªþFªýãªþ�Õžüðfÿºå +ž@< +,  )&  *&•& •‘&Œ,+,* # )#3,üìüìÀ999999991äôìîÀÀ99999990@*WZWU!je!{vu! FYVjddj(|svz( ]] 324&'.#"&5!27!"&''¶ý3>¡_Ü'y=¡_Üþý''†NOy;‚ÝW¢fªNPþˆþÆ€Ý[¢gXü²@CHp¸¸@Cþ¸þåp¼Džf b¥MK¿YÆgþöžþŸþ[KK¿XÝÝÏî /ÿ@- !$'!!0 $*0ÔÄÔÄ99991ÔÄÔÄÀ9990@¾     $$$   $$ $ ***///***55500055 5 :::???:::EEE@@@EE E JJJOOOJJJV´° °!°"°&°'°(´)]]32654&#".#"326#"&54632>32#"&“1†Te€vYR…Ä1…UfvYR†�F�^ˆº§†_™HDža†¼§†^•/XZ‡ie†‡7XX„je†ˆ‡ߦ¯Ø~ŠŠƒá§¯ÖwÙÛ .@МР œ   Ô<ì2ü<ì21/ìÔ<ìü<ì0!!#!5!!!®-ýÓ¨ýÓ-ýÓúþþ}ªþ}ƒªƒû¦ªÙÛ¨ T@.œœœœBѧœ $# ü<ì2291/ìôì90KSXííííY" 5!!Ûü@Àúþúþúþøþëþî²pªoüªÙÛ¨ V@/œœœœBѧœ$ # ü<<ì291/ìôì90KSXííííY"55!5ÙúþÁAúþø°þ‘ªþ�²ýǪªRÃÕÆ@F  B Ó Ó  � fe f eÔ<ì2ìüì2ì99991/ä2Ô<ì2Ô<ì290KSXííííY"K° TX½ÿÀ@878Y@(†�¦ µ' ' ')((79‡ ˆ¦ ¥ª©]]!#!5!5'!5!3 3!!!�þcÉþ` Tþ´þþ{y¿þÂþµTŸÇþ9Ç{3›{JýD¼ý¶{›3®þVå` M@% ‡Œ ¼½!   NF!üì2ôìÄ91ää2ô<ìÜÄ990¶"`"Ï"]3326533267#"&'#"&'®¸Š‡”•¸#% )I#ER2‘bf�*þV ýH‘”¨¨�ü¢<9 ”NPOONNý×hÿçÁ-)b@'! '!Õ* $$*ÔÌÜÌ9991äÌÜÌÎÎ990K° TK° T[K°T[K°T[K°T[X½*@**ÿÀ878Y>54&#"#"&54632#"&54324&#"32ôIH7�$$0�e´ÖþßÕ˜ËÝ¢e‚ WOm�VPm�mW£K�ƒt,>bþÊþùþ±þFØ£Æ[àt}þþÏt{þw;Á ]@    ÔÄ91ÄÔÌÎ990@0QVPZ spvupz €€ Z pp{ t ��€€ ]]!! !!5 7êüA ýJïúÞÕýIÁÁý3ýÀ•!ãœþwqÁ@×Ö¯ggÔìÔì1üìì20!#!#œÕðý ïÁø¶}ùƒÿáÿðª/#Ë@1 ÚÙ"ØÕ $ #" #h#$ÔÔÔì9999991/<äôì22î9990K° TX½$ÿÀ$$@878Y@V             ##(]]#3267#"&5467!##"#>3!‡¶i/7.%7vy"PþºÂµÃ)6<  �¥y‘þJ\:1�fd.�¡xüo‘@E¦}/þ�ú%&@ Û Ûܱ& iji&Üìüì1üìÜäÞä026732#"&'&&#"#"&546327j ¾ÊPd@7*8  k½ÄOeD=!0 þú°l9¼TA6?&#Hý•Ánþ!þbSA8?SsÕ;ð)_@3(%ãÝá%Ý ßÞÝ à‘* "(kl"k *ÜìÌüì22ÀÀ9991ôäüôìÄîíÖîî99990!!#5#"&5463354&#"56632"32655‹°ýP®•,�]€˜¿¼¶uu>ˆDI‘E·³þì¡~bRh‚P{¸þ@p?D‡q‡Š[[""°ðCO@M�r`Õdð.@ãáÝ àÝ‘ klk Üìüì991ôìôìüì0!!2#"&546"32654&‹°ýPX³Îγ³Ðгi~hi}|P{Ý¿¿Ûܾ¿Ýs¡ˆ…  …‰ NÏç@@" å‘å  mm  ÔììÔììÀÀ9991/<ì2ôì0%!5654#"!5!&5! Ïý¨±ÆþøØØþ÷Dzý¨?ž‘1/�Ž¡²²²aLÊð"þÝïÊþ´a²²‹*¸>ŠþwþËÂþØ�{ÿão{3>@C'-%= 4©%†ˆ©:¹.†-º*¹»1 ¸Œ%?47&%7& =&-7"E?üìÌÔü<ÔìÄ999991Ää2ô<Ääü<ôìÄî2îôîî9990@�0+0,0-0.0/00@+@,@-@.@/@0P+P,P-P.P/P0…+…0€@�@ @°@À@Ð@à@à@ð@??? ??0,0-0.0/@,@-@.@/P,P-P.P/ooo oo`,`-`.`/p,p-p.p/€,€-€.€/]q].#">32!3267#"&'#"&5463!54&#"5>32"326=¶¥‰™¹DJÔ„âü² Ì·hÈddÐj§øMIØ�½Òýû§—`¶Te¾ZŽÕï߬�o™¹”—´®ž0Z^þÝúZ¿È55®*,ywxx»¨½À‹..ª''`þf{bsÙ´)Hÿ¢œ¼ +ä@<+,&  )&  *&¹& ¹¸&Œ,+,* # #Q)E,üì2ôì2À9999991äôìîÀÀ99999990@p(?-YVUV jf!{    { z{ {!"#$%{&›•%¨ -ð-&YVUZ(ifej(ztvz(‰•š$¢­$]] 32654&'.#".5327#"&''‰þ)gA“¬\*g>—©}66ñ]ŸC‹_’56þîð`¡?‹`!ý°*(èÈOuš))ëÓHn.—MÅw834¨O³MÆxþíþÇ43¨N�ÿã¬Õ $†@/ †ˆ !ƒ# •Œ#�%" " "!& %ÜìÔüìÔì99991äôìþÍôî9990K°TK°T[K°T[X½%ÿÀ%%@878Y@ ttttv]33267#"&546?>7>5#53ô¾7ZZ:3ƒmN´`^Àg¸àIYX0&ÄÊÊDœe‚WX5^1YnFC¼98ŸL‰VV/5<6þ5Õ b@ƒ� ü<ì2991/ôüÌ0K° TX½ @ ÿÀ878YK°TK°T[K°T[X½ ÿÀ @878Y¶ P ]#53#3ËËË¢×þú+�eþ›ÙÛ^@ œÜÔì1ÔÄì0!#!Ù¨û¦^ýÁ•=ÿ×} *@    ÔÌ91ÔÌÄ903##'%\½sý®BþÁ}}`ùºs-Pbý;þV#Š@@   B   ©Šæ©Šæ©!—$  $ÔÌ91Ä2Äüìôìîöîî299990KSXí2í9Y"K° TX½$ÿÀ$$@878Y.#"!!#"&'53267#5!>32&P,`r<þÃ:¼º:d/4a/am"‰ø?$Æ—5dð¤z„þÉ�ý…þãÓ¦!!‰¦­�J·ÃÙÛô;?@.9*-" *œ19œ"œ œ<-<Ô<Ä21ÔìÔìÜüÔìÀ9999990#"'&'&'&#"56632326#"'&'&'&#"56632326Ûi³an’ ›^X¬bi³an“ ›^V©gi³an’ ›^X¬bi³an“ ›^V©o³NE;=LT³NE;=KÚ²OE;=LS²NE;=Kÿú`Á8@ÔÌ91/ÄÌ90@cmpxyvn]] !3!¬þ^DýïàCúšîûÄú?ž�%# †@Ièèèè è è è  è B  ç¦ o o nüü<Ôì2991ô<ì2990KSXííííííííY"55%þÓ-þ+#þÓ-þ+#¿þôþô¿¢R¢¿þôþô¿¢RÁ�H# †@I è è è è èèèèB  ç¦ o opü<üÔ<ì991ô<ì2990KSXííííííííY"5%5ÁÕþ+-þÓ²Õþ+-þÓ#þ^Rþ^¿  ¿þ^Rþ^¿  ìþ #@ƒ   ÔüÔìÔì1/<<ì220%3#%3#%3#–ÔÔ©ÕÕú­ÕÕþþþþþþÿÿhk'$¼uÿÿh^'$¼uÿÿsÿãÙ^'2'us Õ;@••�•­   üìÔÄÄÔì299991/ìì2ôì2î0!!!!! !# !3úýÇý9øû×þOþA¿±g�þ¿þÀ@A�ÕªþFªýãª|pm|ªþáþàþßþßqÿãÃ{'3„@1†ˆ ©. ¹(¹»"%¸Œ4"1 K1 Q+E4üìôüôìÄ9991ä2ô<Ääì2Äî2îôî90@%?5_5p5Ÿ5Ï5Ð5ð5????? ooooo ]q].#"!3267#"&'#"32>32%"32654& ¤‰™¹Hü² Ì·jÈbdÐj òQGÑŒñþïñŒÓBNè�âú°”¬«•“¬¬”˜³®ž5Z¾Ç44®*,nmnm98olkpþ݇çÉÉçèÈÇééy¶©é/Æ1üì0!!üy�éyµ©/Ì1Ôì0!!øy�®émÕ '@ž �  ÜüÌÔÌþÔÎ1ô<ì20#53#53�Ó¤�RšÓ¤�Ré­?þÁ­­?þÁ®émÕ '@ ž�  ÜìÔÌÜîÔÎ1ô<ì203#%3#Ó¤�RšÓ¤�RÕ¬þÀ@¬¬þÀ@®éÓÕ@ ž�ÜüÔÌ1ôì0#53�Ó¤�Ré­?þÁ²þ×Õ@ ž�qÜìÔÌ1ôì03#Ó¤�RÕ˜þÁ?Ù–Ûo )@êêœ r ÜÔ<ü<Ä1ÔÄüÄîî03#3#!!ßööööýúúþoöþõAªþ#îu"@ÔÌ91ÔÌ990 úþ��þôþ þ �üÏüÇ9%ûÛûÓ-ÿÿ=þV'\Ž^ÿÿÿüçN'<suþ‰ÿãÍð+@BŒ‘ÔÌ1ää0KSXííY"3#- ü\ ðùó^R¼²#/ƒ@I -'! - -¹ëì'¹ë!0 *$0* $ $(st*(s0Üäìôäì9999999991ÔäìôäìÀ9999999907'#"&''7&&5467'766324&#"326{ÏrÎ%$&(ÑrÏ;t=:x=ÏqÏ%%&&ÏsÏ7t@?s9ÏqÏ(&%%ÏsÎ>v:@t8ÎsÏ'%$þ|pššprœ�ž�s#G@%èèèèBç¦onüì291ôì90KSXííííY"5sþÓ-þ+#¿þôþô¿¢RÁ�–#I@&èèèèBç¦opü<ì91ôì90KSXííííY"5ÁÕþ+-þÓ#þ^Rþ^¿  /J›@( ©‡¾±— ¼ Lü<Ä2Äü<Äî2991/<æ2îþîîî2990K° TX½ÿÀ@878YK°TX½@ÿÀ878Y@0P€€€ Ðï]]#!##53546;#"3#J¹þ¹°°­³¹°cMù¹¹`û Ñü/Ñ�N·¯™Phc²é/J„@! © ‡— ¼   Lü<ÄÄü<Äî991/<æ2þîî2990K° TX½ÿÀ@878YK°TX½@ÿÀ878Y@0P€ € € Ðï]!#!"!!##53546J¹þ·cM/þѹ°°®ùì{Phc�ü/Ñ�N»«9ÿ;ÇÕ>@ ¹¹  �ÂY W Y Ô<<ì2ü<<ì21äôÄ2Ä2î2î20%!#!5!!5!3!!!Çþ‘°þ‘oþ‘o°oþ‘oßþ\¤š™¤þ\™ýáÛH®F·ƒÔì1Ôì03#ÛÓÓFþ®ÿÓþ@ žƒÔìÔÌ1üì0%3#Ó¤�Rþ¬þÀ@®ÿmþ '@ žƒ   ÜìÔÌÜîÔÎ1ü<ì20%3#%3#šÓ¤�RþfÓ¤�Rþ¬þÀ@¬¬þÀ@qÿã Lð #'3?K®@D$%&%&'$'B@’ .’�(’F’4 �:&Œ$‘L%IC'1+C =  1 =I 7+ ! LüäìÔÄìäîîöîî991ä2ô<<ä2ì2îöîî20KSXííY"K°TK° T[K° T[K° T[K° T[K°T[X½L@LLÿÀ878Y"32654&'2#"&5462#"&546!3#"32654&2#"&546"32654&ôWddWUccUžº»� º»ùtž¼»ŸŸ¹º% üZ VcbWWcd²žº»� º»ŸWccWUcc‘”„‚••‚ƒ•Ü»»ÛÛ»¼ÛàÛ»½ÚÛ¼ºÜùóŽ•‚„””„�–ýŸÜ»»ÛÛ»¼Û”„‚••‚ƒ•ÿÿhm'$¼uÿÿÉ‹m'(žuÿÿhk'$¼uÿÿÉ‹N'(žuÿÿÉ‹k'(žuÿÿ¢k',ÿ/uÿÿÿþ`m',ÿ/uÿÿXN',ÿ/uÿÿ;ºk',ÿ/uÿÿsÿãÙk'2'uÿÿsÿãÙm'2'uÿÿsÿãÙk'2'uÿÿ²ÿã)k'8îuÿÿ²ÿã)m'8îuÿÿ²ÿã)k'8îuÁy` ·¿Füì1/ì0@ @P`p]3#Á¸¸`û Áî?f7@ ´³uÜì91ôì290K° TK°T[X½ÿÀ@878Y3#'#¶”õ‹´´‹fþˆõõ¶J7c@$  Ãà íVwVvôìüì99991ü<üÔ<ì99990K° TK° T[X½ÿÀ@878Y'.#"#>3232673#"&ü9! &$}f[&@%9! &$}f[&@Z7IR‡“!7IR‡“Õb+ö/·ïîÔÌ1üì0K° TK°T[X½ÿÀ@878Y!!ÕVýªö”Ç)9H W@ ð³VVÜìÔì1ô<Ôì0K° TX½ÿÀ@878YK°TK°T[K°T[X½@ÿÀ878Y332673#"&Çv aWV` v ž‘‘žHKKJL���šDf,@ ÎÍdÔì1üì0K° TX½ÿÀ@878Y3#šÌÌÌîá _@Áò ÁñV xVÔìôì1ôìôì0K° TK° T[X½ÿÀ@878YK° TK° T[K° T[X½ÿÀ@878Y4&#"3267#"&54632˜X@AWWA@XzŸssŸŸssŸô?XW@AWX@s  ssŸŸ#þuÁ@  ó' ÜÔìÔÌ1/ÔüÄ90!#"&'532654&'T76xv.W+"J/;<+->i0Y[ ƒ0.W=ðî®fB@´³ÔÜÔÌ991ô<ì20K° TK°T[X½ÿÀ@878Y3#3#ü²ø‡�ªß‰fþˆxþˆLþuÁ @  óô 'ÔìÄÔÌ1/üüÄ90!33267#"&546¸w-+76 >&Dzs5=X..… W]0iÁî?f7@ ´³uÜì91ô<ì90K° TK°T[X½ÿÀ@878Y373¶õ‹´´‹õîxõõþˆÿòuÕ ?@ •�  : yô<ìÄü<Ä991/äì90´0P]3%!!'7ÓË9Pþw×ü^”MáÕý˜Ûoþîýãª;jnžH ^@ — z z Ô<äü<ä991/ì90K°TX½ @ ÿÀ878Y@ @ P ` sz p à ð ]37#'7Ǹ}Lɸ{JÅý¦Zj�üãšXj�ÿÿ‡ÿã¢m'6‹uÿÿoÿãÇf'Vàÿÿ\m'=¾uÿÿXÛf']àþ¢®˜@ õõÜ<ì21ÔìÔì0##®ªªª˜ý öý ö ºÕ g@  © •�•  2  yô<ì2ÄôìÄ91/Æ2îöîî20@( °Ÿ Ÿ Ÿ Ÿ ŸŸŸŸ¿ ¿ ¿ ¿ ¿¿¿¿]]! )#53!!3 !Ó ±–þiþPþ`ÉÉËPþ°ó5þáþËÕþ—þ€þ~þ–¼�ãþ�ýê.,qÿãu('@^%{&%#${##{#({'(#&'('%$%(('"#" ! B('&%"! ##¹ ¹Œ#±)&' ! (%#" QE)üìôì99999991ìÄôìî9990KSXÉÉÉÉííííY"²?*]@v%+("/#/$)%-&-'*(6%F%X X!` `!f"u u!u"%#%$&&&''(6$6%F$E%Z Z!b b!z{     {zzv v!x" *ð*']].#"32654&#"5432''%'3%F2X)§¹®’‘®6 ~rþäæçþåÝ4*ŸþÁ!µäM!þÙ“ØÃ¼ÞÞ¼z¼&�þà­ÿþÉ7ÿú7´kc\Ì‘oabÿÿÿüçk'<suÿÿ=þVf'\�^É�Õ =@• •ö�  ? üì22üì91/ôüìÔì0@ ?_]332+#32654&#ÉÊþûþÿûþÊÊþ�š™ŽÕþøáÜÜâþ®'ýÑ’††‘ºþV¤>@¹¹Œ¸½— GFüì22ôì1ìääôÄìÆî0@ `€ à]%#3>32#"&4&#"326s¹¹:±{ÌÿÿÌ{±8§’’§§’’§¨ý®¾ý¢daþ¼þøþøþ¼aëËççËËççÙ-Û×¶œÔÄ1Ôì0!!Ùúþת?œÅ …@M œ  œœœœœ œ œ B   Ô<Ì291Ô<Ì290KSXííííííííY" '7œþ7Éwþ5þ5vÈþ8vËËLþ5þ7yËþ5yÉËyþ5ˉœÅß ,@Ý ÝÝ ÷‘ |]|| Üôäüä1ôììÔìî2035733!œÌßæ‰Íý× c)t'ý+n^œ´ðJ@$}}BÝÝ÷ Ý‘~ÜÄÔÄì91ôÄìüìî90KSXí2íY"!!56754&#"56632 ¨ýª"?XhU4zHM…9‘®þµ8rn81^BQ##{„l‹þä0b�Íð(H@' Ý Ý Ý Ý ø÷Ý ø#‘)~&~ )ÜÄÄÔìÔì9991ôäìüäìÔìîî90#"&'532654&##532654&#"56632 \e¾±9}F4wCmxolV^^ad_(fQI€7�©Z`mR|†yOFJLl?<:=svcE`ÿÿ‰ÿãð'ð'¼5 ‹ýdÿÿ‰ÿã?ð'ð'¼5ñ‹ýdÿÿbÿãð'ò'¼5 ‹ýdÿÿsÿã‹m'* uÿÿqþVZH'JÚ‹ÿÿÉ•P', ÿ/uÿÿ‡þu¢ð'6Ý‹ÿÿoþuÇ{'VÝÿÿsÿã'k'&-uÿÿqÿãçf'F�‰ÿÿsÿã'm'&-uÿÿqÿãçf'Fà‰qÿãô$J@$Ó ù"¹¹ Œ¸—   GE%üìô<Äü<Ä1/ìäôÄìÄîý<î20¶`&€& &]!5!533##5#"3232654&#"¢þºF¸šš¸:±|ËÿÿË|±ýǧ’’¨¨’’§¶N}““}úü¨daDDaþËççËËççd߃¶œÜÌ1Ôì0!!dý僤ÛH®F·ƒÔì1Ôì03#ÛÓÓFþÿã�ð1@: Ó"+Ó ¡®•¡®•/‘Œ) 2+"!)#&  , & &*!/<ÔÄ2üÄÄ99999999991Ä2äôìôìîöîî2Ý<î20K° TK° T[K° T[K°T[K°T[K°T[X½2ÿÀ22@878Y@z  1Ti lnooooiko o!o"o#n$l%i'i-ŸŸŸ Ÿ Ÿ Ÿ Ÿ ŸŸŸŸŸŸ–Ÿ Ÿ!Ÿ"Ÿ#Ÿ$Ÿ%Ÿ&Ÿ'Ÿ(Ÿ)Ÿ*Ÿ+Ÿ,�-2   USjg ]].#"!!!!3267#"#734&5465#7332�[©f�Ê A7ýæ¾8þŠ Ê�f©[Y¹`íþË(Ó7‹Â7œ(6ìb¹bÕiZÈ»{.# .{»ÊZiÓHH"{/ #/{"G×)Ù¥@ ÎddÔüÜì1Ô<ì20K°TK°T[X½@ÿÀ878YK°TK° T[K°T[X½ÿÀ@878YK°TK°T[X½@ÿÀ878YK°TX½ÿÀ@878Y@````pppp]3#%3#^ËËþyËËÙËËËsîðö@BúÄÀ1ôÌ0KSXÉÉY"K° TX½ÿÀ@878YK°TX½@ÿÀ878Y@ %%6FVjg //]]3#7¹ä™öþø¶Jéu@!  ÃÃúVV ÔìÔì99991ô<ìÔì2990K° TX½ÿÀ@878YK°TX½@ÿÀ878Y´ ]'.#"#4632326=3#"&ü9 $(}gV$=09" (}gT";9! 2-ev 3)dw î‹ö‰@BúÄÀ1ôÌ0KSXÉÉY"K° TX½ÿÀ@878YK°TX½@ÿÀ878Y@*$$5CUUŸŸ¯¯//]]#ÇÄ™æöþøÏî1øw@ úÔÄ91ô<Ä90K° TX½ÿÀ@878YK°TX½@ÿÀ878YK°TX½ÿÀ@878Y@ //- ]3#'#¢¼Ó‹¦¦‹øþö²²Ïî1ø†@ úÔÄ91ôÄ290K° TK° T[K° T[K° T[X½ÿÀ@878YK°TX½@ÿÀ878YK°TX½ÿÀ@878Y@ "  ]373¢Ó‹¦¦‹Óî ²²þö?œôß Ô@ Ý ÷‘ ] ÜÔ<Äì291ôüÔ<ì290K°TK°T[K°T[K°T[K° T[K° T[X½@ÿÀ878YK°TK°T[X½ÿÀ@878Y@T /9IFYi‹«»       "5GK S[ e„¥µ]] !33##5!5ÝþË5¦‡‡�þbfþ]ýämººyÇ9ø j@à úVVÔìÔì1ôüÄ20K° TX½ÿÀ@878YK°TX½@ÿÀ878YK°TK°T[X½ÿÀ@878Y332673#"&Çv cSRav  ��Ÿø6978w{zšfÛ¶úÔÌ1ôÌ03#šÌÌÛÍ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€�‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßà>: ~ÿ1BSax~’ÇÝ©À & 0 : ¬!""""+"H"e%Êûÿÿ   0AR^x}’ÆØ©À  0 9 ¬!""""+"H"`%ÊûÿÿÿãÿõÿØÿ ÿ^ÿCÿhÿüöüÛà–à…àVßjÞqÞ_Úï¿8ôüúúü (B¬£„…½–熎‹�©¤ŠÙƒ“ñò�—ˆÃÝðžªóôõ¢­ÉÇ®bc�dËeÈÊÏÌÍÎèfÒÐѯgï‘ÕÓÔhêì‰jikmln oqprsutvwéxzy{}|¸¡~€�ëíºýþÿøÖùúãä×àÚÛÜßØÞ²³¶·Ä´µÅ‚‡«˜¨š™î¼¥’�”•Íf‹‹55®Å´žªšq=�3ۤ=´Ù‹žãd‹Û²‡á–œdž�¨‹�²ð²ž´Ù´Ù´Ù?“‡y}É–s)ÉÉšÉ3sÉ\É\ÿ–?ÉuÉçÉüÉLsÓÉLs�ɇãÿúÛ²yéD{=ãÿü{\°²Ç´Ùÿìªç{ºfqqìqÑ/qº9Á9ÿÛ¢º9Á˺ºåqºqJº+o#7®¼=‹V¼;¼=3X²´Ùyy–sÉüÉLsÛ²ç{ç{ç{ç{ç{ç{fqìqìqìqìq9�9ÿÇ9ÿÞ9ÿôºåqåqåqåqåq®®®®9ì�\¸3ž º's×´ÙËLfªÝ´Ù´Ù´ÙR®#hdœ¶ÿá+/ÅsÅ`NÛ{åH?�55´Ù=´ÙZÿúåžåÁìyyLs�s/q%®%®‹®‹²´Ùô¼=ãÿüVþ‰^3ž3Á / /9‹Û‹®%® ¼qyÉyÉÉ\¢\ÿþ\\;LsLsLsÛ²Û²Û²9ÁÁ¶ÕÇšî#ðLÁÿòF‡+o{\3X²3 åqãÿü¼=×ɺ´Ù´5‰5^5bÁ‰Á‰Áb3sq\ɇ+o–sfq–sfqqãd‹Û×s¶ ÏÏ5?Çšÿ+�   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€�‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóõôöøùúûüýþÿ     sfthyphenperiodcenteredEuroc6459c6460c6461c6462c6463c6466c6467c6468c6469""""X“ÿ¶Oƒ²ö!n˜´ÊÞE‚~áL·üeÏî  R s ®  ß X ° û : i “ æ  = z /¬E…ëuñ)pཊëP‹±á@Ö"m¹#{ßC€øw³R¡Ø‡Ä�º‡wè [ r ó!5!B!â!ï!ü" ""#"0"="J"W"d"q"~"‹"˜"¥"²"¿"Ì"Ù"æ"ó## ##'#4#A#N#[#h#”#Ï$4$�%3%S%�&&º'K'�'·((X(Ã)_*%*\*£*é+z+Ó,D,�,±-P- ..R.ª/‡0A0½11!1P1Ï2H2z2ß3F3p3p3}3Š3—3æ4z4�4£4Ñ4ÿ5595g5‘5ž5«5Ï6[6“6Í7C7©7ë888J999)969C9P9]9j9w9„9‘9ž9«9¸9Å9Ò9ï::{: :å;;^;Ž;Ä;ô<"<_<§<´<Á<Î<Û<þ=c>;>H>U>˜>ç>ý?a?�?Ü@:@K@\@m@z@‡@”@¡@®@»@È@Õ@âA@AVAkBEBªB÷C_C²CÿDUDÛE*E?-†” x$ÿÓ%ÿ·&')*K+-r./2934K57ÿD9ÿˆ:ÿ­;ÿš<ÿ =IQR&UYÿÉZ\ÿÜbÿÓdg9xy&z&{&|&}&‰�­ÿÓ®ÿÓ¯9ºÿÜ»ÿ ÇÿÓÉÿÓÐ9Ñ9Ò9åéêÿ ëÿÜìöKûý$ÿÓ$ÿÜ$ÿÜ$$9$&ÿÜ$*ÿÜ$2ÿÜ$4ÿÜ$6$7ÿa$8$9ÿ}$:ÿ�$;$<ÿa$FÿÜ$GÿÜ$HÿÜ$Iÿ·$RÿÜ$TÿÜ$WÿÜ$X$Yÿˆ$Zÿ­$\ÿu$b9$dÿÜ$gÿÜ$h$oÿÜ$pÿÜ$qÿÜ$rÿÜ$sÿÜ$yÿÜ$zÿÜ${ÿÜ$|ÿÜ$}ÿÜ$~$$€$�$©ÿ·$ª$­9$®9$¯ÿÜ$´þø$µÿ$ºÿu$»ÿa$Å/$Ç9$É9$ÐÿÜ$ÑÿÜ$ÒÿÜ$Ó$Ô$Õ$ã$êÿa$ëÿu$öÿÜ$ù$ûÿÜ$üÿÜ$ýÿÜ$þÿÜ%%&ÿÜ%*ÿÜ%2ÿÜ%6ÿÜ%9ÿÁ%:ÿ·%<ÿ�%dÿÜ%gÿÜ%©ÿÁ%ªÿÜ%¯ÿÜ%´ÿ�%µÿ�%»ÿ�%Åÿ­%ÐÿÜ%ÑÿÜ%ÒÿÜ%ãÿÜ%êÿ�%öÿÜ%ùÿÜ%ûÿÜ%ýÿÜ&&$&6&<ÿÜ&b&©ÿÜ&ªÿÜ&­&®&´&µ&&»ÿÜ&Å&Ç&É&ã&êÿÜ&ù''$ÿÜ'9ÿÜ':'<ÿ�'bÿÜ'©ÿÜ'ªÿÜ'­ÿÜ'®ÿÜ'´ÿÓ'µÿÉ'»ÿ�'ÅÿD'ÇÿÜ'ÉÿÜ'êÿ�))þ·)ÿa)$ÿD)6ÿÜ)7ÿÜ)DÿD)Hÿ�)Lÿk)Rÿ·)Uÿk)Xÿ�)\ÿD)bÿD)iÿD)jÿD)kÿD)lÿD)mÿD)nÿD)pÿ�)qÿ�)rÿ�)sÿ�)yÿ·)zÿ·){ÿ·)|ÿ·)}ÿ·)~ÿ�)ÿ�)€ÿ�)�ÿ�)©)ª)­ÿD)®ÿD)´ÿÓ)µ)ºÿD)Åþˆ)ÇÿD)ÉÿD)ãÿÜ)ëÿD)ùÿÜ**$*7ÿ·*:*<ÿš*b*©ÿÜ*ªÿÜ*­*®*´ÿÓ*µÿÓ*»ÿš*ÅÿÉ*Ç*É*êÿš++ÿÜ++©+ª+´ÿ·+µÿÁ+Åÿ·-ÿ·-$ÿÜ-bÿÜ-©ÿÜ-ªÿÜ-­ÿÜ-®ÿÜ-´ÿ·-µÿÁ-Åÿ�-ÇÿÜ-ÉÿÜ.ÿ).$ÿÜ.&ÿ�.2ÿ�.7ÿa.8ÿÉ.:ÿ·.<ÿ·.DÿÜ.Hÿš.Rÿš.Xÿš.\ÿk.bÿÜ.dÿ�.gÿ�.hÿÉ.iÿÜ.jÿÜ.kÿÜ.lÿÜ.mÿÜ.nÿÜ.pÿš.qÿš.rÿš.sÿš.yÿš.zÿš.{ÿš.|ÿš.}ÿš.~ÿš.ÿš.€ÿš.�ÿš.©ÿ}.ª.­ÿÜ.®ÿÜ.¯ÿ�.´ÿÁ.µÿÁ.ºÿk.»ÿ·.Å.ÇÿÜ.ÉÿÜ.Ðÿ�.Ñÿ�.Òÿ�.ÓÿÉ.ÔÿÉ.ÕÿÉ.êÿ·.ëÿk.ûÿ�.ýÿ�/ÿÜ/$//2ÿ·/7þæ/8ÿš/9ÿ/:ÿD/<þð/D/HÿÜ/RÿÜ/XÿÜ/\ÿD/b//gÿ·/hÿš/i/j/k/l/m/n/pÿÜ/qÿÜ/rÿÜ/sÿÜ/yÿÜ/zÿÜ/{ÿÜ/|ÿÜ/}ÿÜ/~ÿÜ/ÿÜ/€ÿÜ/�ÿÜ/©/ª/­//®//¯ÿ·/´þa/µýæ/ºÿD/»þð/Å/Ç//É//Ðÿ·/Ñÿ·/Òÿ·/Óÿš/Ôÿš/Õÿš/êþð/ëÿD292ÿ­2ÿÜ2$ÿÜ29ÿÜ2;ÿ}2<ÿ�2bÿÜ2©ÿÜ2ª2­ÿÜ2®ÿÜ2´ÿÓ2µÿÜ2»ÿ�2ÅÿD2ÇÿÜ2ÉÿÜ2êÿ�3ÿÓ3þÁ33$ÿ}383:3<ÿÓ3Dÿ¤3Hÿ·3LÿÓ3QÿÜ3Rÿ·3UÿÜ3VÿÜ3XÿÜ3\3bÿ}3h3iÿ¤3jÿ¤3kÿ¤3lÿ¤3mÿ¤3nÿ¤3pÿ·3qÿ·3rÿ·3sÿ·3xÿÜ3yÿ·3zÿ·3{ÿ·3|ÿ·3}ÿ·3~ÿÜ3ÿÜ3€ÿÜ3�ÿÜ3©ÿÜ3ª3­ÿ}3®ÿ}3´&3µ&3º3»ÿÓ3Åþ·3Çÿ}3Éÿ}3Ó3Ô3Õ3äÿÜ3êÿÓ3ë3úÿÜ494©4ª4´ÿÓ4µÿÜ4Åÿ}5ÿ­5ÿ·5ÿÁ5$ÿ­5&ÿš57ÿk59ÿ�5:ÿ­5<ÿ}5DÿÓ5Hÿ¤5Rÿ¤5Xÿ¤5\ÿ�5bÿ­5dÿš5iÿÓ5jÿÓ5kÿÓ5lÿÓ5mÿÓ5nÿÓ5pÿ¤5qÿ¤5rÿ¤5sÿ¤5yÿ¤5zÿ¤5{ÿ¤5|ÿ¤5}ÿ¤5~ÿ¤5ÿ¤5€ÿ¤5�ÿ¤5©ÿ�5ªÿÜ5­ÿ­5®ÿ­5´ÿk5µÿ}5ºÿ�5»ÿ}5ÅÿÜ5Çÿ­5Éÿ­5êÿ}5ëÿ�5ûÿš5ýÿš6$&6&6*6264666b&6d6g6­&6®&6¯6Ç&6É&6Ð6Ñ6Ò6ã6ö6ù6û6ý7ÿD7ÿ 7ÿ7$ÿa7&ÿˆ77ÿÜ7Dþ­7Fþ¤7Hþ¤7LÿÁ7Rþ¤7UþÓ7Vþ­7XþÉ7Zþ­7\þÁ7bÿa7dÿˆ7iþ­7jþ­7kþ­7lþ­7mþ­7nþ­7oþ¤7pþ¤7qþ¤7rþ¤7sþ¤7yþ¤7zþ¤7{þ¤7|þ¤7}þ¤7~þÉ7þÉ7€þÉ7�þÉ7©ÿD7ªÿ�7­ÿa7®ÿa7´7µÿÓ7ºþÁ7Åþø7Çÿa7Éÿa7äþ­7ëþÁ7úþ­7ûÿˆ7üþ¤7ýÿˆ7þþ¤8$8-8=ÿÜ8b8­8®8Ç8É8åÿÜ9ÿˆ9þø9ÿY9$ÿ}92ÿÜ9Dÿa9Hÿa9LÿÓ9Rÿa9Xÿu9\ÿÉ9bÿ}9gÿÜ9iÿa9jÿa9kÿa9lÿa9mÿa9nÿa9pÿa9qÿa9rÿa9sÿa9yÿa9zÿa9{ÿa9|ÿa9}ÿa9~ÿu9ÿu9€ÿu9�ÿu9©ÿN9ªÿ�9­ÿ}9®ÿ}9¯ÿÜ9´9µ9ºÿÉ9Åþæ9Çÿ}9Éÿ}9ÐÿÜ9ÑÿÜ9ÒÿÜ9ëÿÉ:ÿ­:ÿ:ÿˆ:$ÿ�:Dÿ}:Hÿˆ:LÿÓ:Rÿˆ:Uÿ¤:Xÿ·:\ÿÜ:bÿ�:iÿ}:jÿ}:kÿ}:lÿ}:mÿ}:nÿ}:pÿˆ:qÿˆ:rÿˆ:sÿˆ:yÿˆ:zÿˆ:{ÿˆ:|ÿˆ:}ÿˆ:~ÿ·:ÿ·:€ÿ·:�ÿ·:©ÿ�:ªÿÜ:­ÿ�:®ÿ�:´ÿÜ:µ:ºÿÜ:Åþø:Çÿ�:Éÿ�:ëÿÜ;ÿš;$;&ÿk;2ÿ};7ÿÜ;Hÿ¤;b;dÿk;gÿ};pÿ¤;qÿ¤;rÿ¤;sÿ¤;©ÿ�;ª;­;®;¯ÿ};´ÿa;µÿ­;ÅÿÓ;Ç;É;Ðÿ};Ñÿ};Òÿ};ûÿk;ýÿk<ÿ <þa<þð<$ÿa<&ÿ�<2ÿ�<Dþæ<Hþð<Lÿ·<Rþð<Xÿ<bÿa<dÿ�<gÿ�<iþæ<jþæ<kþæ<lþæ<mþæ<nþæ<pþð<qþð<rþð<sþð<yþð<zþð<{þð<|þð<}þð<~ÿ<ÿ<€ÿ<�ÿ<©ÿ<ªÿk<­ÿa<®ÿa<¯ÿ�<´ÿ�<µÿÜ<Åþø<Çÿa<Éÿa<Ðÿ�<Ñÿ�<Òÿ�<ûÿ�<ýÿ�=ÿÜ=©=ª=´ÿÜ=µÿÜ=ÅÿÜH[ÿÜIÿ�IÿkIÿ·IWÿÜIZÿÜI\ÿÜI©ÿ·IªÿÜI´AIµIºÿÜIÅÿIëÿÜNDÿÜNHÿ·NRÿ·NXÿÁN\ÿ·NiÿÜNjÿÜNkÿÜNlÿÜNmÿÜNnÿÜNpÿ·Nqÿ·Nrÿ·Nsÿ·Nyÿ·Nzÿ·N{ÿ·N|ÿ·N}ÿ·N~ÿÁNÿÁN€ÿÁN�ÿÁNºÿ·Nëÿ·QQQQ©QªQ´ÿkQµÿ�QÅÿ¤R&RÿÜRR[ÿÁR©RªR´ÿkRµÿ·RÅÿ}Uÿ}UÿDUÿÜUFÿÓUGÿÜUHÿÓUIUJÿÜUKÿÜUPÿÜUQÿÜURÿÓUTÿÜUUÿÜUXUYUZU[ÿÉU\U]UoÿÓUpÿÓUqÿÓUrÿÓUsÿÓUxÿÜUyÿÓUzÿÓU{ÿÓU|ÿÓU}ÿÓU~UU€U�U©ÿ·UªU´UµVUºUÅþÉUæUëU÷ÿÜUüÿÓUþÿÓYÿÉYÿaYÿ�Y©ÿÜYªÿÜY´YµÿÜYÅþðZZÿDZÿ�Z©ÿÜZªÿÜZ´ZµZÅÿ)[FÿÜ[HÿÁ[RÿÁ[oÿÜ[pÿÁ[qÿÁ[rÿÁ[sÿÁ[yÿÁ[zÿÁ[{ÿÁ[|ÿÁ[}ÿÁ[üÿÜ[þÿÜ\ÿÜ\þÜ\ÿk\©ÿÜ\ªÿÜ\´\µ\ÅþÓbÿÓbÿÜbÿÜb$9b&ÿÜb*ÿÜb2ÿÜb4ÿÜb6b7ÿab8b9ÿ}b:ÿ�b;b<ÿabFÿÜbGÿÜbHÿÜbIÿ·bRÿÜbTÿÜbWÿÜbXbYÿˆbZÿ­b\ÿubb9bdÿÜbgÿÜbhboÿÜbpÿÜbqÿÜbrÿÜbsÿÜbyÿÜbzÿÜb{ÿÜb|ÿÜb}ÿÜb~bb€b�b©ÿ·bªb­9b®9b¯ÿÜb´þøbµÿbºÿub»ÿabÅ/bÇ9bÉ9bÐÿÜbÑÿÜbÒÿÜbÓbÔbÕbãbêÿabëÿuböÿÜbùbûÿÜbüÿÜbýÿÜbþÿÜdd$d6d<ÿÜdbd©ÿÜdªÿÜd­d®d´dµ&d»ÿÜdÅdÇdÉdãdêÿÜdùg9gÿ­gÿÜg$ÿÜg9ÿÜg;ÿ}g<ÿ�gbÿÜg©ÿÜgªg­ÿÜg®ÿÜg´ÿÓgµÿÜg»ÿ�gÅÿDgÇÿÜgÉÿÜgêÿ�h$h-h=ÿÜhbh­h®hÇhÉhåÿÜp[ÿÜq[ÿÜr[ÿÜs[ÿÜxxxx©xªx´ÿkxµÿ�xÅÿ¤y&yÿÜyy[ÿÁy©yªy´ÿkyµÿ·yÅÿ}z&zÿÜzz[ÿÁz©zªz´ÿkzµÿ·zÅÿ}{&{ÿÜ{{[ÿÁ{©{ª{´ÿk{µÿ·{Åÿ}|&|ÿÜ||[ÿÁ|©|ª|´ÿk|µÿ·|Åÿ}}&}ÿÜ}}[ÿÁ}©}ª}´ÿk}µÿ·}Åÿ}‰&‰©‰ª‰´ÿ�‰µÿ�‰Åÿ­��©�ª�´ÿ­�µÿ¤�Åÿ�©$©%ÿÜ©&ÿÜ©'ÿÜ©)©*ÿÜ©+©-ÿÜ©.©/©2©3©4©5©7ÿ�©9ÿ�©:ÿÜ©;©<ÿk©=©I©Q©R©U©YÿÜ©ZÿÜ©\ÿÜ©b©dÿÜ©g©x©y©z©{©|©}©‰©�—©­©®©¯©ºÿÜ©»ÿk©Ç©É©Ð©Ñ©Ò©å©é©êÿk©ëÿÜ©ì©öÿÜ©ûÿÜ©ýÿܪ$ÿ·ª%ÿ·ª&ÿܪ'ÿܪ)ª*ª+ª-ÿܪ.ª/ª2ÿܪ3ª4ª5ª7ÿDª9ÿNª:ÿ�ª;ÿ�ª<ÿª=ªIªQªRªUªYÿܪZÿܪ\ÿܪbÿ·ªdÿܪgÿܪxªyªzª{ª|ª}ª‰ª�ª­ÿ·ª®ÿ·ª¯ÿܪºÿܪ»ÿªÇÿ·ªÉÿ·ªÐÿܪÑÿܪÒÿܪåªéªêÿªëÿܪìªöªûÿܪýÿÜ­ÿÓ­ÿÜ­ÿÜ­$9­&ÿÜ­*ÿÜ­2ÿÜ­4ÿÜ­6­7ÿa­8­9ÿ}­:ÿ�­;­<ÿa­FÿÜ­GÿÜ­HÿÜ­Iÿ·­RÿÜ­TÿÜ­WÿÜ­X­Yÿˆ­Zÿ­­\ÿu­b9­dÿÜ­gÿÜ­h­oÿÜ­pÿÜ­qÿÜ­rÿÜ­sÿÜ­yÿÜ­zÿÜ­{ÿÜ­|ÿÜ­}ÿÜ­~­­€­�­©ÿ·­ª­­9­®9­¯ÿÜ­´þø­µÿ­ºÿu­»ÿa­Å/­Ç9­É9­ÐÿÜ­ÑÿÜ­ÒÿÜ­Ó­Ô­Õ­ã­êÿa­ëÿu­öÿÜ­ù­ûÿÜ­üÿÜ­ýÿÜ­þÿÜ®ÿÓ®ÿÜ®ÿÜ®$9®&ÿÜ®*ÿÜ®2ÿÜ®4ÿÜ®6®7ÿa®8®9ÿ}®:ÿ�®;®<ÿa®FÿÜ®GÿÜ®HÿÜ®Iÿ·®RÿÜ®TÿÜ®WÿÜ®X®Yÿˆ®Zÿ­®\ÿu®b9®dÿÜ®gÿÜ®h®oÿÜ®pÿÜ®qÿÜ®rÿÜ®sÿÜ®yÿÜ®zÿÜ®{ÿÜ®|ÿÜ®}ÿÜ®~®®€®�®©ÿ·®ª®­9®®9®¯ÿÜ®´þø®µÿ®ºÿu®»ÿa®Å/®Ç9®É9®ÐÿÜ®ÑÿÜ®ÒÿܮӮԮծã®êÿa®ëÿu®öÿÜ®ù®ûÿÜ®üÿÜ®ýÿÜ®þÿܯ9¯ÿ­¯ÿܯ$ÿܯ9ÿܯ;ÿ}¯<ÿ�¯bÿܯ©ÿܯª¯­ÿܯ®ÿܯ´ÿÓ¯µÿܯ»ÿ�¯ÅÿD¯ÇÿܯÉÿܯêÿ�´$þø´%ÿÁ´&ÿ·´'ÿÁ´)ÿÁ´*ÿ·´+ÿÁ´-ÿÁ´.ÿÁ´/ÿÁ´2ÿ·´3ÿÁ´4ÿ·´5ÿÁ´7´9´:´;ÿˆ´<´=ÿÜ´Iÿ·´Qÿ�´Rÿk´Uÿ�´Yÿ·´Zÿ·´\ÿ·´bþø´dÿ·´gÿ·´xÿ�´yÿk´zÿk´{ÿk´|ÿk´}ÿk´‰ÿÁ´�þ}´­þø´®þø´¯ÿ·´ºÿ·´»´Çþø´Éþø´Ðÿ·´Ñÿ·´Òÿ·´åÿÜ´éÿ·´ê´ëÿ·´ìÿÁ´öÿ·´ûÿ·´ýÿ·ºÿܺþܺÿkº©ÿܺªÿܺ´ºµºÅþÓ»ÿ »þa»þð»$ÿa»&ÿ�»2ÿ�»Dþæ»Hþð»Lÿ·»Rþð»Xÿ»bÿa»dÿ�»gÿ�»iþæ»jþæ»kþæ»lþæ»mþæ»nþæ»pþð»qþð»rþð»sþð»yþð»zþð»{þð»|þð»}þð»~ÿ»ÿ»€ÿ»�ÿ»©ÿ»ªÿk»­ÿa»®ÿa»¯ÿ�»´ÿ�»µÿÜ»Åþø»Çÿa»Éÿa»Ðÿ�»Ñÿ�»Òÿ�»ûÿ�»ýÿ�Å$&Å%ÿ·Å&ÿ�Å'ÿ·Å)ÿ·Å*ÿ·Å+ÿ·Å-/Å.ÿ·Å/ÿ·Å2ÿ�Å3ÿ·Å4ÿ�Å5ÿ·Å7þæÅ9þˆÅ:ÿÅ;ÿ·Å<þˆÅ=ÅIÿÜÅQÿ·ÅRÿ·ÅUÿ·ÅYÿÅZÿ<Å\ÿ�Åb&Ådÿ�Ågÿ�Åxÿ·Åyÿ·Åzÿ·Å{ÿ·Å|ÿ·Å}ÿ·Å‰ÿ·Å�&Å­&Å®&ůÿ�źÿ�Å»þˆÅÇ&ÅÉ&ÅÐÿ�ÅÑÿ�ÅÒÿ�ÅåÅéÿ·ÅêþˆÅëÿ�Åìÿ·Åöÿ·Åûÿ�Åýÿ�ÇÿÓÇÿÜÇÿÜÇ$9Ç&ÿÜÇ*ÿÜÇ2ÿÜÇ4ÿÜÇ6Ç7ÿaÇ8Ç9ÿ}Ç:ÿ�Ç;Ç<ÿaÇFÿÜÇGÿÜÇHÿÜÇIÿ·ÇRÿÜÇTÿÜÇWÿÜÇXÇYÿˆÇZÿ­Ç\ÿuÇb9ÇdÿÜÇgÿÜÇhÇoÿÜÇpÿÜÇqÿÜÇrÿÜÇsÿÜÇyÿÜÇzÿÜÇ{ÿÜÇ|ÿÜÇ}ÿÜÇ~ÇÇ€Ç�Ç©ÿ·ÇªÇ­9Ç®9ǯÿÜÇ´þøÇµÿǺÿuÇ»ÿaÇÅ/ÇÇ9ÇÉ9ÇÐÿÜÇÑÿÜÇÒÿÜÇÓÇÔÇÕÇãÇêÿaÇëÿuÇöÿÜÇùÇûÿÜÇüÿÜÇýÿÜÇþÿÜÉÿÓÉÿÜÉÿÜÉ$9É&ÿÜÉ*ÿÜÉ2ÿÜÉ4ÿÜÉ6É7ÿaÉ8É9ÿ}É:ÿ�É;É<ÿaÉFÿÜÉGÿÜÉHÿÜÉIÿ·ÉRÿÜÉTÿÜÉWÿÜÉXÉYÿˆÉZÿ­É\ÿuÉb9ÉdÿÜÉgÿÜÉhÉoÿÜÉpÿÜÉqÿÜÉrÿÜÉsÿÜÉyÿÜÉzÿÜÉ{ÿÜÉ|ÿÜÉ}ÿÜÉ~ÉÉ€É�É©ÿ·ÉªÉ­9É®9ɯÿÜÉ´þøÉµÿɺÿuÉ»ÿaÉÅ/ÉÇ9ÉÉ9ÉÐÿÜÉÑÿÜÉÒÿÜÉÓÉÔÉÕÉãÉêÿaÉëÿuÉöÿÜÉùÉûÿÜÉüÿÜÉýÿÜÉþÿÜÐ9Ðÿ­ÐÿÜÐ$ÿÜÐ9ÿÜÐ;ÿ}Ð<ÿ�ÐbÿÜЩÿÜЪЭÿÜЮÿÜдÿÓеÿÜлÿ�ÐÅÿDÐÇÿÜÐÉÿÜÐêÿ�Ñ9Ñÿ­ÑÿÜÑ$ÿÜÑ9ÿÜÑ;ÿ}Ñ<ÿ�ÑbÿÜÑ©ÿÜѪѭÿÜÑ®ÿÜÑ´ÿÓѵÿÜÑ»ÿ�ÑÅÿDÑÇÿÜÑÉÿÜÑêÿ�Ò9Òÿ­ÒÿÜÒ$ÿÜÒ9ÿÜÒ;ÿ}Ò<ÿ�ÒbÿÜÒ©ÿÜÒªÒ­ÿÜÒ®ÿÜÒ´ÿÓÒµÿÜÒ»ÿ�ÒÅÿDÒÇÿÜÒÉÿÜÒêÿ�Ó$Ó-Ó=ÿÜÓbÓ­Ó®ÓÇÓÉÓåÿÜÔ$Ô-Ô=ÿÜÔbÔ­Ô®ÔÇÔÉÔåÿÜÕ$Õ-Õ=ÿÜÕbÕ­Õ®ÕÇÕÉÕåÿÜã$&ã&ã*ã2ã4ã6ãb&ãdãgã­&ã®&ã¯ãÇ&ãÉ&ãÐãÑãÒãããöãùãûãýåÿÜå©åªå´ÿÜåµÿÜåÅÿÜéé©éªé´ÿ¤éµÿ�éÅÿ·êÿ êþaêþðê$ÿaê&ÿ�ê2ÿ�êDþæêHþðêLÿ·êRþðêXÿêbÿaêdÿ�êgÿ�êiþæêjþæêkþæêlþæêmþæênþæêpþðêqþðêrþðêsþðêyþðêzþðê{þðê|þðê}þðê~ÿêÿê€ÿê�ÿê©ÿêªÿkê­ÿaê®ÿaê¯ÿ�ê´ÿ�êµÿÜêÅþøêÇÿaêÉÿaêÐÿ�êÑÿ�êÒÿ�êûÿ�êýÿ�ëÿÜëþÜëÿkë©ÿÜëªÿÜë´ëµëÅþÓììÿkìÿ·ì©ìªì´ÿÜìµìÅÿDöö$ö7ÿ·ö:ö<ÿšöbö©ÿÜöªÿÜö­ö®ö´ÿÓöµÿÓö»ÿšöÅÿÉöÇöÉöêÿšù$&ù&ù*ù2ù4ù6ùb&ùdùgù­&ù®&ù¯ùÇ&ùÉ&ùÐùÑùÒùãùöùùùûùýûû$û6û<ÿÜûbû©ÿÜûªÿÜû­û®û´ûµ&û»ÿÜûÅûÇûÉûãûêÿÜûùýý$ý6ý<ÿÜýbý©ÿÜýªÿÜý­ý®ý´ýµ&ý»ÿÜýÅýÇýÉýãýêÿÜýù MB@hmþ ¼þ‰þ‰ L �GÌþBGÌS�f €¯ JBits@ ûþšmãB±‹`#cÕVeraSansÿÿÿÿ6ÿÿþ628R00@                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        "                      "       #                       #     $               $    &              &    ÿÿ P ì_<õº¹ð¸ºÂg‘þ‰þ Lmdjango-simple-captcha-0.7.0/captcha/helpers.py000066400000000000000000000072741523260570200213030ustar00rootroot00000000000000import operator as operator_module import random from django.urls import reverse from captcha.conf import settings def math_challenge(): _ARITHMETIC_OPS = { "+": operator_module.add, "-": operator_module.sub, "*": operator_module.mul, } operands = (random.randint(1, 10), random.randint(1, 10)) operator = random.choice(list(_ARITHMETIC_OPS.keys())) if operands[0] < operands[1] and "-" == operator: operands = (operands[1], operands[0]) challenge = "%d%s%d" % (operands[0], operator, operands[1]) result = _ARITHMETIC_OPS[operator](operands[0], operands[1]) return ( "{}=".format(challenge.replace("*", settings.CAPTCHA_MATH_CHALLENGE_OPERATOR)), str(result), ) def random_char_challenge(): chars, ret = "abcdefghijklmnopqrstuvwxyz", "" for i in range(settings.CAPTCHA_LENGTH): ret += random.choice(chars) return ret.upper(), ret def unicode_challenge(): chars, ret = "äàáëéèïíîöóòüúù", "" for i in range(settings.CAPTCHA_LENGTH): ret += random.choice(chars) return ret.upper(), ret def word_challenge(): fd = open(settings.CAPTCHA_WORDS_DICTIONARY, "r") lines = fd.readlines() fd.close() while True: word = random.choice(lines).strip() if ( len(word) >= settings.CAPTCHA_DICTIONARY_MIN_LENGTH and len(word) <= settings.CAPTCHA_DICTIONARY_MAX_LENGTH ): break return word.upper(), word.lower() def huge_words_and_punctuation_challenge(): "Yay, undocumneted. Mostly used to test Issue 39 - http://code.google.com/p/django-simple-captcha/issues/detail?id=39" fd = open(settings.CAPTCHA_WORDS_DICTIONARY, "rb") lines = fd.readlines() fd.close() word = "" while True: word1 = random.choice(lines).strip() word2 = random.choice(lines).strip() punct = random.choice(settings.CAPTCHA_PUNCTUATION) word = "%s%s%s" % (word1, punct, word2) if ( len(word) >= settings.CAPTCHA_DICTIONARY_MIN_LENGTH and len(word) <= settings.CAPTCHA_DICTIONARY_MAX_LENGTH ): break return word.upper(), word.lower() def noise_arcs(draw, image): size = image.size draw.arc([-20, -20, size[0], 20], 0, 295, fill=settings.CAPTCHA_FOREGROUND_COLOR) draw.line( [-20, 20, size[0] + 20, size[1] - 20], fill=settings.CAPTCHA_FOREGROUND_COLOR ) draw.line([-20, 0, size[0] + 20, size[1]], fill=settings.CAPTCHA_FOREGROUND_COLOR) return draw def noise_dots(draw, image): size = image.size for p in range(int(size[0] * size[1] * 0.1)): draw.point( (random.randint(0, size[0]), random.randint(0, size[1])), fill=settings.CAPTCHA_FOREGROUND_COLOR, ) return draw def noise_null(draw, image): return draw def random_letter_color_challenge(idx, plaintext_captcha): # Generate colorful but balanced RGB values red = random.randint(64, 150) green = random.randint(64, 150) blue = random.randint(64, 150) # Ensure at least one channel is higher to make it colorful channels = [red, green, blue] channels[0] = random.randint(150, 255) random.shuffle(channels) # Format the color as a hex string return f"#{channels[0]:02X}{channels[1]:02X}{channels[2]:02X}" def post_smooth(image): from PIL import ImageFilter return image.filter(ImageFilter.SMOOTH) def captcha_image_url(key): """Return url to image. Need for ajax refresh and, etc""" return reverse("captcha-image", args=[key]) def captcha_audio_url(key): """Return url to image. Need for ajax refresh and, etc""" return reverse("captcha-audio", args=[key]) django-simple-captcha-0.7.0/captcha/jinja2/000077500000000000000000000000001523260570200204325ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/jinja2/captcha/000077500000000000000000000000001523260570200220355ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/jinja2/captcha/widgets/000077500000000000000000000000001523260570200235035ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/jinja2/captcha/widgets/captcha.html000066400000000000000000000002721523260570200257750ustar00rootroot00000000000000{% if audio %}{% endif %}captcha{% if audio %}{% endif %} {% include "django/forms/widgets/multiwidget.html" %} django-simple-captcha-0.7.0/captcha/locale/000077500000000000000000000000001523260570200205145ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/bg/000077500000000000000000000000001523260570200211045ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/bg/LC_MESSAGES/000077500000000000000000000000001523260570200226715ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/bg/LC_MESSAGES/django.mo000066400000000000000000000012151523260570200244670ustar00rootroot00000000000000Þ•<\pq�œ_´0.-_Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.4.1 Report-Msgid-Bugs-To: PO-Revision-Date: 2014-02-10 15:00+0200 Last-Translator: Venelin Stoykov Language-Team: bg Language: bg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1) Сгрешен текÑ�тЧуй текÑ�та като аудио файлТова поле е задължителноdjango-simple-captcha-0.7.0/captcha/locale/bg/LC_MESSAGES/django.po000066400000000000000000000020441523260570200244730ustar00rootroot00000000000000# django-simple-captcha Bulgarian translation # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the django-simple-captcha package. # Venelin Stoykov , 2014. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.4.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-02-10 14:43+0200\n" "PO-Revision-Date: 2014-02-10 15:00+0200\n" "Last-Translator: Venelin Stoykov \n" "Language-Team: bg \n" "Language: bg\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #: fields.py:91 msgid "Play CAPTCHA as audio file" msgstr "Чуй текÑ�та като аудио файл" #: fields.py:106 fields.py:135 tests/tests.py:99 tests/tests.py:239 #: tests/tests.py:246 msgid "Invalid CAPTCHA" msgstr "Сгрешен текÑ�Ñ‚" #: tests/tests.py:125 msgid "This field is required." msgstr "Това поле е задължително" django-simple-captcha-0.7.0/captcha/locale/cs/000077500000000000000000000000001523260570200211215ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/cs/LC_MESSAGES/000077500000000000000000000000001523260570200227065ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/cs/LC_MESSAGES/django.mo000066400000000000000000000011251523260570200245040ustar00rootroot00000000000000Þ•<\pq�œS´#>Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: 0.3.5 Report-Msgid-Bugs-To: PO-Revision-Date: 2012-10-09 07:08+0200 Last-Translator: Beda Kosata Language-Team: Czech <> Language: cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; Neplatná CAPTCHAPÅ™ehrát captchu jako audio souborToto pole je povinné.django-simple-captcha-0.7.0/captcha/locale/cs/LC_MESSAGES/django.po000066400000000000000000000016661523260570200245210ustar00rootroot00000000000000# Czech translation of django-simple-captcha. # Copyright (C) 2012 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Beda Kosata , 2012. # msgid "" msgstr "" "Project-Id-Version: 0.3.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-10-09 07:05+0200\n" "PO-Revision-Date: 2012-10-09 07:08+0200\n" "Last-Translator: Beda Kosata \n" "Language-Team: Czech <>\n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" #: fields.py:50 msgid "Play CAPTCHA as audio file" msgstr "PÅ™ehrát captchu jako audio soubor" #: fields.py:67 fields.py:99 tests/__init__.py:62 msgid "Invalid CAPTCHA" msgstr "Neplatná CAPTCHA" #: tests/__init__.py:88 msgid "This field is required." msgstr "Toto pole je povinné." django-simple-captcha-0.7.0/captcha/locale/de/000077500000000000000000000000001523260570200211045ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/de/LC_MESSAGES/000077500000000000000000000000001523260570200226715ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/de/LC_MESSAGES/django.mo000066400000000000000000000011721523260570200244710ustar00rootroot00000000000000Þ•<\pq�œs´(!<^Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha Report-Msgid-Bugs-To: PO-Revision-Date: 2013-07-16 12:10+0100 Last-Translator: Patrick Lauber Language-Team: DE Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.5.7 Ungültiges CAPTCHACAPTCHA als Audiodatei abspielen.Dieses Feld wird benötigt.django-simple-captcha-0.7.0/captcha/locale/de/LC_MESSAGES/django.po000066400000000000000000000017521523260570200245000ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-07-16 12:06+0200\n" "PO-Revision-Date: 2013-07-16 12:10+0100\n" "Last-Translator: Patrick Lauber \n" "Language-Team: DE \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.5.7\n" #: fields.py:90 msgid "Play CAPTCHA as audio file" msgstr "CAPTCHA als Audiodatei abspielen." #: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239 #: tests/tests.py:246 msgid "Invalid CAPTCHA" msgstr "Ungültiges CAPTCHA" #: tests/tests.py:125 msgid "This field is required." msgstr "Dieses Feld wird benötigt." django-simple-captcha-0.7.0/captcha/locale/en/000077500000000000000000000000001523260570200211165ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/en/LC_MESSAGES/000077500000000000000000000000001523260570200227035ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/en/LC_MESSAGES/django.mo000066400000000000000000000011171523260570200245020ustar00rootroot00000000000000Þ•<\pq�œW´ 7Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.2.0 Report-Msgid-Bugs-To: PO-Revision-Date: 2010-09-16 12:16+0200 Last-Translator: Marco Bonetti Language-Team: en Language: en MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=n>1; Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.django-simple-captcha-0.7.0/captcha/locale/en/LC_MESSAGES/django.po000066400000000000000000000016561523260570200245150ustar00rootroot00000000000000# django-simple-captcha French translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Patrick Samson , 2010. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.2.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-25 11:44+0300\n" "PO-Revision-Date: 2010-09-16 12:16+0200\n" "Last-Translator: Marco Bonetti \n" "Language-Team: en \n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n>1;\n" #: fields.py:49 msgid "Play CAPTCHA as audio file" msgstr "Play CAPTCHA as audio file" #: fields.py:66 fields.py:89 tests/__init__.py:62 msgid "Invalid CAPTCHA" msgstr "Invalid CAPTCHA" #: tests/__init__.py:88 msgid "This field is required." msgstr "This field is required." django-simple-captcha-0.7.0/captcha/locale/es/000077500000000000000000000000001523260570200211235ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/es/LC_MESSAGES/000077500000000000000000000000001523260570200227105ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/es/LC_MESSAGES/django.mo000066400000000000000000000011301523260570200245020ustar00rootroot00000000000000Þ•<\pq�œY´!=Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha Report-Msgid-Bugs-To: PO-Revision-Date: 2014-05-20 21:22+0100 Last-Translator: https://github.com/dragosdobrota Language-Team: es Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.6.5 CAPTCHA no válidoReproducir CAPTCHA de audioEste campo es obligatorio.django-simple-captcha-0.7.0/captcha/locale/es/LC_MESSAGES/django.po000066400000000000000000000017101523260570200245110ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-07-16 12:06+0200\n" "PO-Revision-Date: 2014-05-20 21:22+0100\n" "Last-Translator: https://github.com/dragosdobrota\n" "Language-Team: es\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.5\n" #: fields.py:90 msgid "Play CAPTCHA as audio file" msgstr "Reproducir CAPTCHA de audio" #: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239 #: tests/tests.py:246 msgid "Invalid CAPTCHA" msgstr "CAPTCHA no válido" #: tests/tests.py:125 msgid "This field is required." msgstr "Este campo es obligatorio." django-simple-captcha-0.7.0/captcha/locale/fa/000077500000000000000000000000001523260570200211025ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/fa/LC_MESSAGES/000077500000000000000000000000001523260570200226675ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/fa/LC_MESSAGES/django.mo000066400000000000000000000012601523260570200244650ustar00rootroot00000000000000Þ•<\pq�œn´##CG$‹Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.2.0 Report-Msgid-Bugs-To: PO-Revision-Date: 2020-10-04 19:08+0330 Last-Translator: Mehdi Namaki Language-Team: fa Language: fa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=n>1; X-Generator: Poedit 2.3 کد امنیتی صحیح نیستپخش کد امنیتی به عنوان یک پرونده صوتیاین Ù�یلد اجباری است.django-simple-captcha-0.7.0/captcha/locale/fa/LC_MESSAGES/django.po000066400000000000000000000020231523260570200244660ustar00rootroot00000000000000# django-simple-captcha French translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Patrick Samson , 2010. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.2.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-25 11:44+0300\n" "PO-Revision-Date: 2020-10-04 19:08+0330\n" "Last-Translator: Mehdi Namaki \n" "Language-Team: fa \n" "Language: fa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n>1;\n" "X-Generator: Poedit 2.3\n" #: fields.py:49 msgid "Play CAPTCHA as audio file" msgstr "پخش کد امنیتی به عنوان یک پرونده صوتی" #: fields.py:66 fields.py:89 tests/__init__.py:62 msgid "Invalid CAPTCHA" msgstr "کد امنیتی صحیح نیست" #: tests/__init__.py:88 msgid "This field is required." msgstr "این Ù�یلد اجباری است." django-simple-captcha-0.7.0/captcha/locale/fr/000077500000000000000000000000001523260570200211235ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/fr/LC_MESSAGES/000077500000000000000000000000001523260570200227105ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/fr/LC_MESSAGES/django.mo000066400000000000000000000011221523260570200245030ustar00rootroot00000000000000Þ•<\pq�œX´ 8Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.2.0 Report-Msgid-Bugs-To: PO-Revision-Date: 2010-09-16 12:16+0200 Last-Translator: Patrick Samson Language-Team: fr Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=n>1; CAPTCHA invalideÉcouter la version audioCe champ est obligatoire.django-simple-captcha-0.7.0/captcha/locale/fr/LC_MESSAGES/django.po000066400000000000000000000016611523260570200245160ustar00rootroot00000000000000# django-simple-captcha French translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Patrick Samson , 2010. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.2.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-25 11:44+0300\n" "PO-Revision-Date: 2010-09-16 12:16+0200\n" "Last-Translator: Patrick Samson \n" "Language-Team: fr \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n>1;\n" #: fields.py:49 msgid "Play CAPTCHA as audio file" msgstr "Écouter la version audio" #: fields.py:66 fields.py:89 tests/__init__.py:62 msgid "Invalid CAPTCHA" msgstr "CAPTCHA invalide" #: tests/__init__.py:88 msgid "This field is required." msgstr "Ce champ est obligatoire." django-simple-captcha-0.7.0/captcha/locale/it/000077500000000000000000000000001523260570200211305ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/it/LC_MESSAGES/000077500000000000000000000000001523260570200227155ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/it/LC_MESSAGES/django.mo000066400000000000000000000011671523260570200245210ustar00rootroot00000000000000Þ•<\pq�œh´;ZInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2012-11-14 02:53+0000 Last-Translator: Arjuna Del Toso MIME-Version: 1.0 Language-Team: Arjuna Del Toso Language: it Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=n>1; Parola di controllo sbagliataAscolta la parola di controlloQuesto campo è obbligatoriodjango-simple-captcha-0.7.0/captcha/locale/it/LC_MESSAGES/django.po000066400000000000000000000017421523260570200245230ustar00rootroot00000000000000# django-simple-captcha Italian translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Arjuna Del Toso , 2012 # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.6\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-11-14 02:53+0000\n" "PO-Revision-Date: 2012-11-14 02:53+0000\n" "Last-Translator: Arjuna Del Toso \n" "MIME-Version: 1.0\n" "Language-Team: Arjuna Del Toso \n" "Language: it\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n>1;\n" #: .\fields.py:56 msgid "Play CAPTCHA as audio file" msgstr "Ascolta la parola di controllo" #: .\fields.py:71 .\fields.py:96 .\tests\__init__.py:70 msgid "Invalid CAPTCHA" msgstr "Parola di controllo sbagliata" #: .\tests\__init__.py:97 msgid "This field is required." msgstr "Questo campo è obbligatorio" django-simple-captcha-0.7.0/captcha/locale/ja/000077500000000000000000000000001523260570200211065ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/ja/LC_MESSAGES/000077500000000000000000000000001523260570200226735ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/ja/LC_MESSAGES/django.mo000066400000000000000000000011741523260570200244750ustar00rootroot00000000000000Þ•<\pq�œ\´"+4`Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha Report-Msgid-Bugs-To: PO-Revision-Date: 2014-02-13 07:11+0900 Last-Translator: Keisuke URAGO Language-Team: Keisuke URAGO Language: ja MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; CAPTCHAã�®å€¤ã�Œé�•ã�£ã�¦ã�„ã�¾ã�™CAPTCHAをオーディオã�§èª­ã�¿ä¸Šã�’ã‚‹ã�“ã�®é …ç›®ã�¯å¿…é ˆã�§ã�™django-simple-captcha-0.7.0/captcha/locale/ja/LC_MESSAGES/django.po000066400000000000000000000017571523260570200245070ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Keisuke URAGO , 2014. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-02-13 07:11+0900\n" "PO-Revision-Date: 2014-02-13 07:11+0900\n" "Last-Translator: Keisuke URAGO \n" "Language-Team: Keisuke URAGO \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" #: fields.py:90 msgid "Play CAPTCHA as audio file" msgstr "CAPTCHAをオーディオã�§èª­ã�¿ä¸Šã�’ã‚‹" #: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239 #: tests/tests.py:246 msgid "Invalid CAPTCHA" msgstr "CAPTCHAã�®å€¤ã�Œé�•ã�£ã�¦ã�„ã�¾ã�™" #: tests/tests.py:125 msgid "This field is required." msgstr "ã�“ã�®é …ç›®ã�¯å¿…é ˆã�§ã�™" django-simple-captcha-0.7.0/captcha/locale/nl/000077500000000000000000000000001523260570200211255ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/nl/LC_MESSAGES/000077500000000000000000000000001523260570200227125ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/nl/LC_MESSAGES/django.mo000066400000000000000000000011661523260570200245150ustar00rootroot00000000000000Þ•<\pq�œb´%!=_Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-02-15 13:26+0100 Last-Translator: Leon de Rijke MIME-Version: 1.0 Language-Team: Leon de Rijke Language: nl Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1) CAPTCHA ongeldig, probeer het opnieuwSpeel CAPTCHA als audiobestand afDit veld is verplicht.django-simple-captcha-0.7.0/captcha/locale/nl/LC_MESSAGES/django.po000066400000000000000000000020001523260570200245040ustar00rootroot00000000000000# django-simple-captcha Dutch translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Leon de Rijke , 2013. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.6\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-02-15 13:26+0100\n" "PO-Revision-Date: 2013-02-15 13:26+0100\n" "Last-Translator: Leon de Rijke \n" "MIME-Version: 1.0\n" "Language-Team: Leon de Rijke \n" "Language: nl\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #: fields.py:50 msgid "Play CAPTCHA as audio file" msgstr "Speel CAPTCHA als audiobestand af" #: fields.py:67 fields.py:94 tests/__init__.py:64 tests/__init__.py:186 #: tests/__init__.py:193 msgid "Invalid CAPTCHA" msgstr "CAPTCHA ongeldig, probeer het opnieuw" #: tests/__init__.py:90 msgid "This field is required." msgstr "Dit veld is verplicht." django-simple-captcha-0.7.0/captcha/locale/pl/000077500000000000000000000000001523260570200211275ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/pl/LC_MESSAGES/000077500000000000000000000000001523260570200227145ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/pl/LC_MESSAGES/django.mo000066400000000000000000000012501523260570200245110ustar00rootroot00000000000000Þ•<\pq�œ—´L'i‘Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-08-18 18:52+0200 Last-Translator: SÅ‚awomir Zborowski Language-Team: Polisch Language: pl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) Niepoprawnie wpisana CAPTCHAOdtwórz CAPTCHĘ jako plik dźwiÄ™kowyTo pole jest wymagane.django-simple-captcha-0.7.0/captcha/locale/pl/LC_MESSAGES/django.po000066400000000000000000000021231523260570200245140ustar00rootroot00000000000000# Polish translation for django-simple-captcha. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the django-simple-captcha package. # SÅ‚awomir Zborowski , 2013. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.6\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-08-18 18:49+0200\n" "PO-Revision-Date: 2013-08-18 18:52+0200\n" "Last-Translator: SÅ‚awomir Zborowski \n" "Language-Team: Polisch\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2)\n" #: fields.py:90 msgid "Play CAPTCHA as audio file" msgstr "Odtwórz CAPTCHĘ jako plik dźwiÄ™kowy" #: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239 #: tests/tests.py:246 msgid "Invalid CAPTCHA" msgstr "Niepoprawnie wpisana CAPTCHA" #: tests/tests.py:125 msgid "This field is required." msgstr "To pole jest wymagane." django-simple-captcha-0.7.0/captcha/locale/pt_BR/000077500000000000000000000000001523260570200215225ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/pt_BR/LC_MESSAGES/000077500000000000000000000000001523260570200233075ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/pt_BR/LC_MESSAGES/django.mo000066400000000000000000000011451523260570200251070ustar00rootroot00000000000000Þ•<\pq�œg´/IInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.7 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-05-18 13:12-0300 Last-Translator: Alisson Patricio Language-Team: Alisson Patricio Language: pt_br MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); Resposta inválidaOuça o arquivo de áudioEste campo é obrigatório.django-simple-captcha-0.7.0/captcha/locale/pt_BR/LC_MESSAGES/django.po000066400000000000000000000020011523260570200251020ustar00rootroot00000000000000# django-simple-captcha Portuguese (Brazilian) translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Alisson Patricio , 2013. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.7\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-05-18 10:58-0300\n" "PO-Revision-Date: 2013-05-18 13:12-0300\n" "Last-Translator: Alisson Patricio \n" "Language-Team: Alisson Patricio \n" "Language: pt_br\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: fields.py:49 msgid "Play CAPTCHA as audio file" msgstr "Ouça o arquivo de áudio" #: fields.py:66 fields.py:93 tests/__init__.py:69 #: tests/__init__.py:198 tests/__init__.py:205 msgid "Invalid CAPTCHA" msgstr "Resposta inválida" #: tests/__init__.py:95 msgid "This field is required." msgstr "Este campo é obrigatório." django-simple-captcha-0.7.0/captcha/locale/ru/000077500000000000000000000000001523260570200211425ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/ru/LC_MESSAGES/000077500000000000000000000000001523260570200227275ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/ru/LC_MESSAGES/django.mo000066400000000000000000000013241523260570200245260ustar00rootroot00000000000000Þ•<\pq�œz´/DKC�Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.7 Report-Msgid-Bugs-To: PO-Revision-Date: 2012-07-25 11:17+0300 Last-Translator: Language-Team: ru Language: ru MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) Ð�еверный ответВоÑ�произвеÑ�ти CAPTCHA в виде аудио файлаЭто поле обÑ�зательно длÑ� заполнениÑ�.django-simple-captcha-0.7.0/captcha/locale/ru/LC_MESSAGES/django.po000066400000000000000000000020351523260570200245310ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.7\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-25 11:17+0300\n" "PO-Revision-Date: 2012-07-25 11:17+0300\n" "Last-Translator: \n" "Language-Team: ru \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" #: fields.py:49 msgid "Play CAPTCHA as audio file" msgstr "ВоÑ�произвеÑ�ти CAPTCHA в виде аудио файла" #: fields.py:66 fields.py:89 tests/__init__.py:62 msgid "Invalid CAPTCHA" msgstr "Ð�еверный ответ" #: tests/__init__.py:88 msgid "This field is required." msgstr "Это поле обÑ�зательно длÑ� заполнениÑ�." django-simple-captcha-0.7.0/captcha/locale/sk/000077500000000000000000000000001523260570200211315ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/sk/LC_MESSAGES/000077500000000000000000000000001523260570200227165ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/sk/LC_MESSAGES/django.mo000066400000000000000000000011361523260570200245160ustar00rootroot00000000000000Þ•<\pq�œ^´!%GInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.7 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-10-15 17:16+0200 Last-Translator: Pavol Otto Language-Team: SK Language: sk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; Neplatná CAPTCHAPrehraÅ¥ captchu ako audio súborToto pole je povinné.django-simple-captcha-0.7.0/captcha/locale/sk/LC_MESSAGES/django.po000066400000000000000000000017421523260570200245240ustar00rootroot00000000000000# Slovak translation of django-simple-captcha. # Copyright (C) 2013 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Pavol Otto , 2013. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.7\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-10-15 17:16+0200\n" "PO-Revision-Date: 2013-10-15 17:16+0200\n" "Last-Translator: Pavol Otto \n" "Language-Team: SK\n" "Language: sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" #: fields.py:90 msgid "Play CAPTCHA as audio file" msgstr "PrehraÅ¥ captchu ako audio súbor" #: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239 #: tests/tests.py:246 msgid "Invalid CAPTCHA" msgstr "Neplatná CAPTCHA" #: tests/tests.py:125 msgid "This field is required." msgstr "Toto pole je povinné." django-simple-captcha-0.7.0/captcha/locale/sv/000077500000000000000000000000001523260570200211445ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/sv/LC_MESSAGES/000077500000000000000000000000001523260570200227315ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/sv/LC_MESSAGES/django.mo000066400000000000000000000011671523260570200245350ustar00rootroot00000000000000Þ•<\pq�œy´.>XInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.2.0 Report-Msgid-Bugs-To: PO-Revision-Date: 2018-12-03 06:41+0100 Language-Team: en MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 2.2 Last-Translator: Stefan Norman Language: sv Ogiltig CAPTCHASpela CAPTCHA som ljudfilDetta fält är obligatoriskt.django-simple-captcha-0.7.0/captcha/locale/sv/LC_MESSAGES/django.po000066400000000000000000000017371523260570200245430ustar00rootroot00000000000000# django-simple-captcha Swedish translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Stefan Norman , 2018. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.2.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-07-25 11:44+0300\n" "PO-Revision-Date: 2018-12-03 06:41+0100\n" "Language-Team: en \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 2.2\n" "Last-Translator: Stefan Norman \n" "Language: sv\n" #: fields.py:49 msgid "Play CAPTCHA as audio file" msgstr "Spela CAPTCHA som ljudfil" #: fields.py:66 fields.py:89 tests/__init__.py:62 msgid "Invalid CAPTCHA" msgstr "Ogiltig CAPTCHA" #: tests/__init__.py:88 msgid "This field is required." msgstr "Detta fält är obligatoriskt." django-simple-captcha-0.7.0/captcha/locale/tr/000077500000000000000000000000001523260570200211415ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/tr/LC_MESSAGES/000077500000000000000000000000001523260570200227265ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/tr/LC_MESSAGES/django.mo000066400000000000000000000011351523260570200245250ustar00rootroot00000000000000Þ•<\pq�œb´ (IInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-01-19 20:52+0200 Last-Translator: Gokmen Gorgen Language-Team: TR Gokmen Gorgen Language: tr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0 Geçersiz deÄŸerDeÄŸeri ses dosyası olarak çalBu alan zorunludur.django-simple-captcha-0.7.0/captcha/locale/tr/LC_MESSAGES/django.po000066400000000000000000000017221523260570200245320ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.6\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-01-19 16:33-0200\n" "PO-Revision-Date: 2013-01-19 20:52+0200\n" "Last-Translator: Gokmen Gorgen \n" "Language-Team: TR Gokmen Gorgen \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0\n" #: fields.py:50 msgid "Play CAPTCHA as audio file" msgstr "DeÄŸeri ses dosyası olarak çal" #: fields.py:67 fields.py:94 tests/__init__.py:64 tests/__init__.py:186 #: tests/__init__.py:193 msgid "Invalid CAPTCHA" msgstr "Geçersiz deÄŸer" #: tests/__init__.py:90 msgid "This field is required." msgstr "Bu alan zorunludur." django-simple-captcha-0.7.0/captcha/locale/uk/000077500000000000000000000000001523260570200211335ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/uk/LC_MESSAGES/000077500000000000000000000000001523260570200227205ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/uk/LC_MESSAGES/django.mo000066400000000000000000000013131523260570200245150ustar00rootroot00000000000000Þ•<\pq�œŠ´*?6j)¡Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2014-02-21 22:05+0200 Last-Translator: @FuriousCoder Language-Team: uk @FuriousCoder Language: uk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); Ð�еправильна відповідь.Відтворити CAPTCHA Ñ�к аудіо файл.Це поле Ñ” обов'Ñ�зковим.django-simple-captcha-0.7.0/captcha/locale/uk/LC_MESSAGES/django.po000066400000000000000000000020721523260570200245230ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.6\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-02-21 22:05+0200\n" "PO-Revision-Date: 2014-02-21 22:05+0200\n" "Last-Translator: @FuriousCoder\n" "Language-Team: uk @FuriousCoder\n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: fields.py:91 msgid "Play CAPTCHA as audio file" msgstr "Відтворити CAPTCHA Ñ�к аудіо файл." #: fields.py:106 fields.py:135 tests/tests.py:99 tests/tests.py:239 #: tests/tests.py:246 msgid "Invalid CAPTCHA" msgstr "Ð�еправильна відповідь." #: tests/tests.py:125 msgid "This field is required." msgstr "Це поле Ñ” обов'Ñ�зковим." django-simple-captcha-0.7.0/captcha/locale/zh_CN/000077500000000000000000000000001523260570200215155ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/zh_CN/LC_MESSAGES/000077500000000000000000000000001523260570200233025ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/zh_CN/LC_MESSAGES/django.mo000066400000000000000000000011321523260570200250760ustar00rootroot00000000000000Þ•<\pq�œZ´!AInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-03-01 05:04+0800 Last-Translator: Ming Chen Language-Team: zh_cn Ming Chen Language: zh_cn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0 认è¯�ç �错误使用语音方å¼�播放认è¯�ç �这个字段是必须的django-simple-captcha-0.7.0/captcha/locale/zh_CN/LC_MESSAGES/django.po000066400000000000000000000017541523260570200251130ustar00rootroot00000000000000# django-simple-captcha Chinese Simplified translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Ming Chen , 2013. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.6\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-03-01 05:04+0800\n" "PO-Revision-Date: 2013-03-01 05:04+0800\n" "Last-Translator: Ming Chen \n" "Language-Team: zh_cn Ming Chen \n" "Language: zh_cn\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0\n" #: fields.py:49 msgid "Play CAPTCHA as audio file" msgstr "使用语音方å¼�播放认è¯�ç �" #: fields.py:66 fields.py:93 tests/__init__.py:69 tests/__init__.py:198 #: tests/__init__.py:205 msgid "Invalid CAPTCHA" msgstr "认è¯�ç �错误" #: tests/__init__.py:95 msgid "This field is required." msgstr "这个字段是必须的" django-simple-captcha-0.7.0/captcha/locale/zh_Hans/000077500000000000000000000000001523260570200221065ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/zh_Hans/LC_MESSAGES/000077500000000000000000000000001523260570200236735ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/locale/zh_Hans/LC_MESSAGES/django.mo000066400000000000000000000011321523260570200254670ustar00rootroot00000000000000Þ•<\pq�œZ´!AInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-03-01 05:04+0800 Last-Translator: Ming Chen Language-Team: zh_cn Ming Chen Language: zh_cn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0 认è¯�ç �错误使用语音方å¼�播放认è¯�ç �这个字段是必须的django-simple-captcha-0.7.0/captcha/locale/zh_Hans/LC_MESSAGES/django.po000066400000000000000000000017541523260570200255040ustar00rootroot00000000000000# django-simple-captcha Chinese Simplified translation. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Ming Chen , 2013. # msgid "" msgstr "" "Project-Id-Version: django-simple-captcha 0.3.6\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-03-01 05:04+0800\n" "PO-Revision-Date: 2013-03-01 05:04+0800\n" "Last-Translator: Ming Chen \n" "Language-Team: zh_cn Ming Chen \n" "Language: zh_cn\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0\n" #: fields.py:49 msgid "Play CAPTCHA as audio file" msgstr "使用语音方å¼�播放认è¯�ç �" #: fields.py:66 fields.py:93 tests/__init__.py:69 tests/__init__.py:198 #: tests/__init__.py:205 msgid "Invalid CAPTCHA" msgstr "认è¯�ç �错误" #: tests/__init__.py:95 msgid "This field is required." msgstr "这个字段是必须的" django-simple-captcha-0.7.0/captcha/management/000077500000000000000000000000001523260570200213715ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/management/__init__.py000066400000000000000000000000001523260570200234700ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/management/commands/000077500000000000000000000000001523260570200231725ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/management/commands/__init__.py000066400000000000000000000000001523260570200252710ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/management/commands/captcha_clean.py000066400000000000000000000015761523260570200263220ustar00rootroot00000000000000import sys from django.core.management.base import BaseCommand from django.utils import timezone class Command(BaseCommand): help = "Clean up expired captcha hashkeys." def handle(self, **options): from captcha.models import CaptchaStore verbose = int(options.get("verbosity")) expired_keys = CaptchaStore.objects.filter( expiration__lte=timezone.now() ).count() if verbose >= 1: print("Currently %d expired hashkeys" % expired_keys) try: CaptchaStore.remove_expired() except Exception: if verbose >= 1: print("Unable to delete expired hashkeys.") sys.exit(1) if verbose >= 1: if expired_keys > 0: print("%d expired hashkeys removed." % expired_keys) else: print("No keys to remove.") django-simple-captcha-0.7.0/captcha/management/commands/captcha_create_pool.py000066400000000000000000000021001523260570200275140ustar00rootroot00000000000000from django.core.management.base import BaseCommand from django.db import transaction from captcha.models import CaptchaStore class Command(BaseCommand): help = "Create a pool of random captchas." def add_arguments(self, parser): parser.add_argument( "--pool-size", type=int, default=1000, help="Number of new captchas to create, default=1000", ) parser.add_argument( "--cleanup-expired", action="store_true", default=True, help="Cleanup expired captchas after creating new ones", ) @transaction.atomic() def handle(self, **options): verbose = int(options.get("verbosity")) count = options.get("pool_size") CaptchaStore.create_pool(count) verbose and self.stdout.write("Created %d new captchas\n" % count) options.get("cleanup_expired") and CaptchaStore.remove_expired() options.get("cleanup_expired") and verbose and self.stdout.write( "Expired captchas cleaned up\n" ) django-simple-captcha-0.7.0/captcha/migrations/000077500000000000000000000000001523260570200214315ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/migrations/0001_initial.py000066400000000000000000000015741523260570200241030ustar00rootroot00000000000000from __future__ import unicode_literals from django.db import models, migrations class Migration(migrations.Migration): dependencies = [] operations = [ migrations.CreateModel( name="CaptchaStore", fields=[ ( "id", models.AutoField( verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ("challenge", models.CharField(max_length=32)), ("response", models.CharField(max_length=32)), ("hashkey", models.CharField(unique=True, max_length=40)), ("expiration", models.DateTimeField()), ], options={}, bases=(models.Model,), ) ] django-simple-captcha-0.7.0/captcha/migrations/0002_alter_captchastore_id.py000066400000000000000000000006151523260570200267710ustar00rootroot00000000000000# Generated by Django 3.2.12 on 2022-03-06 11:51 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("captcha", "0001_initial"), ] operations = [ migrations.AlterField( model_name="captchastore", name="id", field=models.AutoField(primary_key=True, serialize=False), ), ] django-simple-captcha-0.7.0/captcha/migrations/__init__.py000066400000000000000000000000001523260570200235300ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/models.py000066400000000000000000000053571523260570200211240ustar00rootroot00000000000000import datetime import hashlib import logging import random import time from django.db import models from django.utils import timezone from django.utils.encoding import smart_str from captcha.conf import settings as captcha_settings # Heavily based on session key generation in Django # Use the system (hardware-based) random number generator if it exists. if hasattr(random, "SystemRandom"): randrange = random.SystemRandom().randrange else: randrange = random.randrange MAX_RANDOM_KEY = 18446744073709551616 # 2 << 63 logger = logging.getLogger(__name__) class CaptchaStore(models.Model): id = models.AutoField(primary_key=True) challenge = models.CharField(blank=False, max_length=32) response = models.CharField(blank=False, max_length=32) hashkey = models.CharField(blank=False, max_length=40, unique=True) expiration = models.DateTimeField(blank=False) def save(self, *args, **kwargs): self.response = self.response.lower() if not self.expiration: self.expiration = timezone.now() + datetime.timedelta( minutes=int(captcha_settings.CAPTCHA_TIMEOUT) ) if not self.hashkey: key_ = ( smart_str(randrange(0, MAX_RANDOM_KEY)) + smart_str(time.time()) + smart_str(self.challenge, errors="ignore") + smart_str(self.response, errors="ignore") ).encode("utf8") self.hashkey = hashlib.sha1(key_).hexdigest() del key_ super().save(*args, **kwargs) def __str__(self): return self.challenge def remove_expired(cls): cls.objects.filter(expiration__lte=timezone.now()).delete() remove_expired = classmethod(remove_expired) @classmethod def generate_key(cls, generator=None): challenge, response = captcha_settings.get_challenge(generator)() store = cls.objects.create(challenge=challenge, response=response) return store.hashkey @classmethod def pick(cls): if not captcha_settings.CAPTCHA_GET_FROM_POOL: return cls.generate_key() def fallback(): logger.error("Couldn't get a captcha from pool, generating") return cls.generate_key() # Pick up a random item from pool minimum_expiration = timezone.now() + datetime.timedelta( minutes=int(captcha_settings.CAPTCHA_GET_FROM_POOL_TIMEOUT) ) store = ( cls.objects.filter(expiration__gt=minimum_expiration).order_by("?").first() ) return (store and store.hashkey) or fallback() @classmethod def create_pool(cls, count=1000): assert count > 0 while count > 0: cls.generate_key() count -= 1 django-simple-captcha-0.7.0/captcha/serializers.py000066400000000000000000000021461523260570200221660ustar00rootroot00000000000000from rest_framework import serializers from rest_framework.fields import empty from captcha.validators import captcha_validate class CaptchaSerializer(serializers.Serializer): """Serializer captcha code and captcha hashkey""" captcha_code = serializers.CharField(max_length=32, write_only=True, required=True) captcha_hashkey = serializers.CharField( max_length=40, write_only=True, required=True ) def run_validation(self, data=empty): values = super().run_validation(data=data) captcha_validate(values["captcha_hashkey"], values["captcha_code"]) return values class CaptchaModelSerializer(serializers.ModelSerializer): """Model serializer captcha code and captcha hashkey""" captcha_code = serializers.CharField(max_length=32, write_only=True, required=True) captcha_hashkey = serializers.CharField( max_length=40, write_only=True, required=True ) def run_validation(self, data=empty): values = super().run_validation(data=data) captcha_validate(values["captcha_hashkey"], values["captcha_code"]) return values django-simple-captcha-0.7.0/captcha/templates/000077500000000000000000000000001523260570200212535ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/templates/captcha/000077500000000000000000000000001523260570200226565ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/templates/captcha/widgets/000077500000000000000000000000001523260570200243245ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/templates/captcha/widgets/captcha.html000066400000000000000000000005711523260570200266200ustar00rootroot00000000000000{% load i18n %} {% spaceless %} {% if audio %} {% endif %} captcha {% if audio %}{% endif %} {% endspaceless %} {% spaceless %}{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %}{% endspaceless %} django-simple-captcha-0.7.0/captcha/tests/000077500000000000000000000000001523260570200204175ustar00rootroot00000000000000django-simple-captcha-0.7.0/captcha/tests/__init__.py000066400000000000000000000000721523260570200225270ustar00rootroot00000000000000from .tests import CaptchaCase, trivial_challenge # NOQA django-simple-captcha-0.7.0/captcha/tests/drf_views.py000066400000000000000000000016751523260570200227720ustar00rootroot00000000000000from rest_framework import serializers, status from rest_framework.decorators import api_view from rest_framework.response import Response from django.contrib.auth.models import User from captcha.serializers import CaptchaModelSerializer, CaptchaSerializer @api_view(["POST"]) def test_serializer(request): serializer = CaptchaSerializer(data=request.POST) serializer.is_valid(raise_exception=True) return Response(status=status.HTTP_200_OK) @api_view(["POST"]) def test_model_serializer(request): class UserCaptchaModelSerializer(CaptchaModelSerializer): subject = serializers.CharField(max_length=100) sender = serializers.EmailField() class Meta: model = User fields = ("subject", "sender", "captcha_code", "captcha_hashkey") serializer = UserCaptchaModelSerializer(data=request.POST) serializer.is_valid(raise_exception=True) return Response(status=status.HTTP_200_OK) django-simple-captcha-0.7.0/captcha/tests/tests.py000066400000000000000000000735041523260570200221440ustar00rootroot00000000000000import datetime import json import os import re from io import BytesIO from unittest import skipIf from unittest.mock import call, patch from PIL import Image from testfixtures import LogCapture import django from django.conf import settings as django_settings from django.core import management from django.core.exceptions import ImproperlyConfigured from django.test import TestCase, override_settings from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy from captcha.conf import settings from captcha.models import CaptchaStore @override_settings(ROOT_URLCONF="captcha.tests.urls") class CaptchaCase(TestCase): def setUp(self): self.stores = {} self.__current_settings_dictionary = settings.CAPTCHA_WORDS_DICTIONARY self.__current_settings_punctuation = settings.CAPTCHA_PUNCTUATION tested_helpers = [ "captcha.helpers.math_challenge", "captcha.helpers.random_char_challenge", "captcha.helpers.unicode_challenge", ] if os.path.exists("/usr/share/dict/words"): settings.CAPTCHA_WORDS_DICTIONARY = "/usr/share/dict/words" settings.CAPTCHA_PUNCTUATION = ";-,." tested_helpers.append("captcha.helpers.word_challenge") tested_helpers.append( "captcha.helpers.huge_words_and_punctuation_challenge" ) for helper in tested_helpers: challenge, response = settings._callable_from_string(helper)() ( self.stores[helper.rsplit(".", 1)[-1].replace("_challenge", "_store")], _, ) = CaptchaStore.objects.get_or_create( challenge=challenge, response=response ) challenge, response = settings.get_challenge()() self.stores["default_store"], _ = CaptchaStore.objects.get_or_create( challenge=challenge, response=response ) self.default_store = self.stores["default_store"] def tearDown(self): settings.CAPTCHA_WORDS_DICTIONARY = self.__current_settings_dictionary settings.CAPTCHA_PUNCTUATION = self.__current_settings_punctuation def _assertFormError(self, response, form_name, *args, **kwargs): self.assertFormError(response.context.get(form_name), *args, **kwargs) def __extract_hash_and_response(self, r): hash_ = re.findall(r'value="([0-9a-f]+)"', str(r.content))[0] response = CaptchaStore.objects.get(hashkey=hash_).response return hash_, response def test_image(self): for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertTrue(response.has_header("content-type")) self.assertEqual(response["content-type"], "image/png") def test_audio(self): if not settings.CAPTCHA_FLITE_PATH: return for key in ( self.stores.get("math_store").hashkey, self.stores.get("math_store").hashkey, self.default_store.hashkey, ): response = self.client.get(reverse("captcha-audio", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertTrue(response.ranged_file.size > 1024) self.assertTrue(response.has_header("content-type")) self.assertEqual(response["content-type"], "audio/wav") def test_animation(self): _setting = settings.CAPTCHA_ANIMATED settings.CAPTCHA_ANIMATED = True for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertTrue(response.has_header("content-type")) self.assertEqual( response["content-type"], "image/avif" if settings.CAPTCHA_ANIMATED_USE_AVIF else "image/gif", ) settings.CAPTCHA_ANIMATED = _setting def test_background_color(self): _setting = settings.CAPTCHA_BACKGROUND_COLOR settings.CAPTCHA_BACKGROUND_COLOR = "#f00" for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertTrue(response.has_header("content-type")) self.assertEqual(response["content-type"], "image/png") settings.CAPTCHA_BACKGROUND_COLOR = _setting def test_form_submit(self): r = self.client.get(reverse("captcha-test")) self.assertEqual(r.status_code, 200) hash_, response = self.__extract_hash_and_response(r) r = self.client.post( reverse("captcha-test"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertTrue(str(r.content).find("Form validated") > 0) r = self.client.post( reverse("captcha-test"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertFalse(str(r.content).find("Form validated") > 0) def test_modelform(self): r = self.client.get(reverse("captcha-test-model-form")) self.assertEqual(r.status_code, 200) hash_, response = self.__extract_hash_and_response(r) r = self.client.post( reverse("captcha-test-model-form"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertTrue(str(r.content).find("Form validated") > 0) r = self.client.post( reverse("captcha-test-model-form"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertFalse(str(r.content).find("Form validated") > 0) def test_wrong_submit(self): for urlname in ("captcha-test", "captcha-test-model-form"): r = self.client.get(reverse(urlname)) self.assertEqual(r.status_code, 200) r = self.client.post( reverse(urlname), dict( captcha_0="abc", captcha_1="wrong response", subject="xxx", sender="asasd@asdasd.com", ), ) self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA")) def test_wrong_response_invalidates_store(self): for urlname in ("captcha-test", "captcha-test-model-form"): r = self.client.get(reverse(urlname)) self.assertEqual(r.status_code, 200) hash_, response = self.__extract_hash_and_response(r) # Wrong answer: form is invalid and the store is deleted r = self.client.post( reverse(urlname), dict( captcha_0=hash_, captcha_1="wrong response", subject="xxx", sender="asasd@asdasd.com", ), ) self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA")) self.assertFalse(CaptchaStore.objects.filter(hashkey=hash_).exists()) # The (previously) correct answer cannot be replayed r = self.client.post( reverse(urlname), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA")) def test_deleted_expired(self): self.default_store.expiration = timezone.now() - datetime.timedelta(minutes=5) self.default_store.save() hash_ = self.default_store.hashkey r = self.client.post( reverse("captcha-test"), dict( captcha_0=hash_, captcha_1=self.default_store.response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertFalse("Form validated" in str(r.content)) # expired -> deleted try: CaptchaStore.objects.get(hashkey=hash_) self.fail() except Exception: pass def test_custom_error_message(self): r = self.client.get(reverse("captcha-test-custom-error-message")) self.assertEqual(r.status_code, 200) # Wrong answer r = self.client.post( reverse("captcha-test-custom-error-message"), dict(captcha_0="abc", captcha_1="wrong response"), ) self._assertFormError(r, "form", "captcha", "TEST CUSTOM ERROR MESSAGE") # empty answer r = self.client.post( reverse("captcha-test-custom-error-message"), dict(captcha_0="abc", captcha_1=""), ) self._assertFormError( r, "form", "captcha", gettext_lazy("This field is required.") ) def test_repeated_challenge(self): CaptchaStore.objects.create(challenge="xxx", response="xxx") try: CaptchaStore.objects.create(challenge="xxx", response="xxx") except Exception: self.fail() @patch("captcha.tests.tests.random_color_challenge") def test_custom_letters_color(self, color_challenge_func_mock): color_challenge_func_mock.return_value = "#ffffff" _current_captcha_challenge_func = settings.CAPTCHA_CHALLENGE_FUNCT settings.CAPTCHA_LETTER_COLOR_FUNCT = ( "captcha.tests.tests.random_color_challenge" ) settings.CAPTCHA_CHALLENGE_FUNCT = "captcha.tests.tests.random_char_challenge" challenge, response = settings.get_challenge()() _store, _ = CaptchaStore.objects.get_or_create( challenge=challenge, response=response ) _response = self.client.get( reverse("captcha-image", kwargs=dict(key=_store.hashkey)) ) assert _response.status_code == 200 _calls = [] for index, char in enumerate(challenge): _calls.append(call(index, challenge)) color_challenge_func_mock.assert_has_calls(_calls, any_order=True) settings.CAPTCHA_LETTER_COLOR_FUNCT = None settings.CAPTCHA_CHALLENGE_FUNCT = _current_captcha_challenge_func def test_repeated_challenge_form_submit(self): __current_challange_function = settings.CAPTCHA_CHALLENGE_FUNCT for urlname in ("captcha-test", "captcha-test-model-form"): settings.CAPTCHA_CHALLENGE_FUNCT = "captcha.tests.trivial_challenge" r1 = self.client.get(reverse(urlname)) r2 = self.client.get(reverse(urlname)) self.assertEqual(r1.status_code, 200) self.assertEqual(r2.status_code, 200) if re.findall(r'value="([0-9a-f]+)"', str(r1.content)): hash_1 = re.findall(r'value="([0-9a-f]+)"', str(r1.content))[0] else: self.fail() if re.findall(r'value="([0-9a-f]+)"', str(r2.content)): hash_2 = re.findall(r'value="([0-9a-f]+)"', str(r2.content))[0] else: self.fail() try: store_1 = CaptchaStore.objects.get(hashkey=hash_1) store_2 = CaptchaStore.objects.get(hashkey=hash_2) except Exception: self.fail() self.assertTrue(store_1.pk != store_2.pk) self.assertTrue(store_1.response == store_2.response) self.assertTrue(hash_1 != hash_2) r1 = self.client.post( reverse(urlname), dict( captcha_0=hash_1, captcha_1=store_1.response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r1.status_code, 200) self.assertTrue(str(r1.content).find("Form validated") > 0) try: store_2 = CaptchaStore.objects.get(hashkey=hash_2) except Exception: self.fail() r2 = self.client.post( reverse(urlname), dict( captcha_0=hash_2, captcha_1=store_2.response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r2.status_code, 200) self.assertTrue(str(r2.content).find("Form validated") > 0) settings.CAPTCHA_CHALLENGE_FUNCT = __current_challange_function def test_custom_generator(self): r = self.client.get(reverse("test_custom_generator")) hash_, response = self.__extract_hash_and_response(r) self.assertEqual(response, "111111") def test_refresh_view(self): r = self.client.get( reverse("captcha-refresh"), HTTP_X_REQUESTED_WITH="XMLHttpRequest" ) try: new_data = json.loads(str(r.content, encoding="ascii")) self.assertTrue("image_url" in new_data) self.assertTrue("audio_url" in new_data) except Exception: self.fail() def test_content_length(self): for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertTrue(response.has_header("content-length")) self.assertTrue(response["content-length"].isdigit()) self.assertTrue(int(response["content-length"])) def test_test_mode_issue15(self): __current_test_mode_setting = settings.CAPTCHA_TEST_MODE settings.CAPTCHA_TEST_MODE = False r = self.client.get(reverse("captcha-test")) self.assertEqual(r.status_code, 200) r = self.client.post( reverse("captcha-test"), dict( captcha_0="abc", captcha_1="wrong response", subject="xxx", sender="asasd@asdasd.com", ), ) self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA")) settings.CAPTCHA_TEST_MODE = True # Test mode, only 'PASSED' is accepted r = self.client.get(reverse("captcha-test")) self.assertEqual(r.status_code, 200) r = self.client.post( reverse("captcha-test"), dict( captcha_0="abc", captcha_1="wrong response", subject="xxx", sender="asasd@asdasd.com", ), ) self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA")) r = self.client.get(reverse("captcha-test")) self.assertEqual(r.status_code, 200) r = self.client.post( reverse("captcha-test"), dict( captcha_0="abc", captcha_1="passed", subject="xxx", sender="asasd@asdasd.com", ), ) self.assertTrue(str(r.content).find("Form validated") > 0) settings.CAPTCHA_TEST_MODE = __current_test_mode_setting def test_test_mode_warns_when_debug_false(self): # Regression test: CAPTCHA_TEST_MODE must warn loudly when DEBUG is off # so production deployments don't silently ship with captcha disabled. import logging import warnings logging.disable(logging.CRITICAL) from captcha.conf import settings as captcha_conf_settings with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("always") captcha_conf_settings._warn_if_test_mode_enabled( test_mode=True, debug=False ) self.assertTrue( any(issubclass(w.category, RuntimeWarning) for w in caught), "Expected a RuntimeWarning when CAPTCHA_TEST_MODE is on and DEBUG is False", ) logging.disable(logging.NOTSET) def test_test_mode_does_not_warn_when_debug_true(self): import warnings from captcha.conf import settings as captcha_conf_settings with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("always") captcha_conf_settings._warn_if_test_mode_enabled(test_mode=True, debug=True) self.assertFalse( any(issubclass(w.category, RuntimeWarning) for w in caught), "No warning expected when DEBUG is True", ) def test_test_mode_does_not_warn_when_disabled(self): import warnings from captcha.conf import settings as captcha_conf_settings with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("always") captcha_conf_settings._warn_if_test_mode_enabled( test_mode=False, debug=False ) self.assertFalse( any(issubclass(w.category, RuntimeWarning) for w in caught), "No warning expected when CAPTCHA_TEST_MODE is False", ) def test_get_version(self): import captcha captcha.get_version() def test_missing_value(self): r = self.client.get(reverse("captcha-test-non-required")) self.assertEqual(r.status_code, 200) hash_, response = self.__extract_hash_and_response(r) # Empty response is okay when required is False r = self.client.post( reverse("captcha-test-non-required"), dict(subject="xxx", sender="asasd@asdasd.com"), ) self.assertEqual(r.status_code, 200) self.assertTrue(str(r.content).find("Form validated") > 0) # But a valid response is okay, too r = self.client.get(reverse("captcha-test-non-required")) self.assertEqual(r.status_code, 200) hash_, response = self.__extract_hash_and_response(r) r = self.client.post( reverse("captcha-test-non-required"), dict( captcha_0=hash_, captcha_1=response, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) self.assertTrue(str(r.content).find("Form validated") > 0) def test_autocomplete_off(self): r = self.client.get(reverse("captcha-test")) captcha_input = ( '' ) if django.VERSION >= (5, 0): captcha_input = ( '" ) self.assertContains(r, captcha_input, html=True) def test_issue201_autocomplete_off_on_hiddeninput(self): r = self.client.get(reverse("captcha-test")) # Inspect the response context to find out the captcha key. key = r.context["form"]["captcha"].field.widget._key # Assety that autocomplete=off is set on the hidden captcha field. if django.VERSION >= (5, 0): self.assertInHTML( ( f'" ), str(r.content), ) else: self.assertInHTML( ( f'' ), str(r.content), ) def test_transparent_background(self): __current_test_mode_setting = settings.CAPTCHA_BACKGROUND_COLOR settings.CAPTCHA_BACKGROUND_COLOR = "transparent" for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertTrue(response.has_header("content-type")) self.assertEqual(response["content-type"], "image/png") settings.CAPTCHA_BACKGROUND_COLOR = __current_test_mode_setting def test_expired_captcha_returns_410(self): for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) CaptchaStore.objects.filter(hashkey=key).delete() response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 410) def test_id_prefix(self): r = self.client.get(reverse("captcha-test-id-prefix")) self.assertTrue( '' in str(r.content) ) self.assertTrue('id="form1_id_captcha1_1"' in str(r.content)) self.assertTrue( '' in str(r.content) ) self.assertTrue('id="form2_id_captcha2_1"' in str(r.content)) def test_image_size(self): __current_test_mode_setting = settings.CAPTCHA_IMAGE_SIZE for key in [store.hashkey for store in self.stores.values()]: settings.CAPTCHA_IMAGE_SIZE = (201, 97) response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertTrue(response.has_header("content-type")) self.assertEqual(response["content-type"], "image/png") self.assertEqual(Image.open(BytesIO(response.content)).size, (201, 97)) settings.CAPTCHA_IMAGE_SIZE = __current_test_mode_setting def test_multiple_fonts(self): vera = os.path.join(os.path.dirname(__file__), "..", "fonts", "Vera.ttf") __current_test_mode_setting = settings.CAPTCHA_FONT_PATH settings.CAPTCHA_FONT_PATH = vera for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertEqual(response["content-type"], "image/png") settings.CAPTCHA_FONT_PATH = [vera, vera, vera] for key in [store.hashkey for store in self.stores.values()]: response = self.client.get(reverse("captcha-image", kwargs=dict(key=key))) self.assertEqual(response.status_code, 200) self.assertEqual(response["content-type"], "image/png") settings.CAPTCHA_FONT_PATH = False for key in [store.hashkey for store in self.stores.values()]: try: response = self.client.get( reverse("captcha-image", kwargs=dict(key=key)) ) self.fail() except ImproperlyConfigured: pass settings.CAPTCHA_FONT_PATH = __current_test_mode_setting def test_math_challenge(self): __current_test_mode_setting = settings.CAPTCHA_MATH_CHALLENGE_OPERATOR settings.CAPTCHA_MATH_CHALLENGE_OPERATOR = "~" helper = "captcha.helpers.math_challenge" challenge, response = settings._callable_from_string(helper)() while settings.CAPTCHA_MATH_CHALLENGE_OPERATOR not in challenge: challenge, response = settings._callable_from_string(helper)() self.assertEqual( response, str( eval( challenge.replace(settings.CAPTCHA_MATH_CHALLENGE_OPERATOR, "*")[ :-1 ] ) ), ) settings.CAPTCHA_MATH_CHALLENGE_OPERATOR = __current_test_mode_setting def test_get_from_pool(self): __current_test_get_from_pool_setting = settings.CAPTCHA_GET_FROM_POOL __current_test_get_from_pool_timeout_setting = ( settings.CAPTCHA_GET_FROM_POOL_TIMEOUT ) __current_test_timeout_setting = settings.CAPTCHA_TIMEOUT settings.CAPTCHA_GET_FROM_POOL = True settings.CAPTCHA_GET_FROM_POOL_TIMEOUT = 5 settings.CAPTCHA_TIMEOUT = 90 CaptchaStore.objects.all().delete() # Delete objects created during SetUp POOL_SIZE = 10 CaptchaStore.create_pool(count=POOL_SIZE) self.assertEqual(CaptchaStore.objects.count(), POOL_SIZE) pool = CaptchaStore.objects.values_list("hashkey", flat=True) random_pick = CaptchaStore.pick() self.assertIn(random_pick, pool) # pick() should not create any extra captcha self.assertEqual(CaptchaStore.objects.count(), POOL_SIZE) settings.CAPTCHA_GET_FROM_POOL = __current_test_get_from_pool_setting settings.CAPTCHA_GET_FROM_POOL_TIMEOUT = ( __current_test_get_from_pool_timeout_setting ) settings.CAPTCHA_TIMEOUT = __current_test_timeout_setting def test_captcha_create_pool(self): CaptchaStore.objects.all().delete() # Delete objects created during SetUp POOL_SIZE = 10 management.call_command("captcha_create_pool", pool_size=POOL_SIZE, verbosity=0) self.assertEqual(CaptchaStore.objects.count(), POOL_SIZE) def test_empty_pool_fallback(self): __current_test_get_from_pool_setting = settings.CAPTCHA_GET_FROM_POOL settings.CAPTCHA_GET_FROM_POOL = True CaptchaStore.objects.all().delete() # Delete objects created during SetUp with LogCapture() as log: CaptchaStore.pick() log.check( ("captcha.models", "ERROR", "Couldn't get a captcha from pool, generating") ) self.assertEqual(CaptchaStore.objects.count(), 1) settings.CAPTCHA_GET_FROM_POOL = __current_test_get_from_pool_setting @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_serializer(self): r = self.client.post( reverse("captcha-test-serializer"), dict( captcha_code=self.default_store.response, captcha_hashkey=self.default_store.hashkey, ), ) self.assertEqual(r.status_code, 200) @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_wrong_serializer(self): r = self.client.post( reverse("captcha-test-serializer"), dict( captcha_code="xxx", captcha_hashkey="wrong hash", ), ) self.assertEqual(r.status_code, 400) self.assertEqual(json.loads(r.content), {"error": "Invalid CAPTCHA"}) @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_serializer_wrong_code_invalidates_store(self): hashkey = self.default_store.hashkey response = self.default_store.response # Wrong code: validation fails and the store is deleted r = self.client.post( reverse("captcha-test-serializer"), dict( captcha_code="wrong code", captcha_hashkey=hashkey, ), ) self.assertEqual(r.status_code, 400) self.assertEqual(json.loads(r.content), {"error": "Invalid CAPTCHA"}) self.assertFalse(CaptchaStore.objects.filter(hashkey=hashkey).exists()) # The (previously) correct code cannot be replayed r = self.client.post( reverse("captcha-test-serializer"), dict( captcha_code=response, captcha_hashkey=hashkey, ), ) self.assertEqual(r.status_code, 400) self.assertEqual(json.loads(r.content), {"error": "Invalid CAPTCHA"}) @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_model_serializer(self): r = self.client.post( reverse("captcha-test-model-serializer"), dict( captcha_code=self.default_store.response, captcha_hashkey=self.default_store.hashkey, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_wrong_model_serializer(self): r = self.client.post( reverse("captcha-test-model-serializer"), dict( captcha_code="xxx", captcha_hashkey="wrong hash", subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 400) self.assertEqual(json.loads(r.content), {"error": "Invalid CAPTCHA"}) def trivial_challenge(): return "trivial", "trivial" def random_color_challenge(index, string): return "#ffffff" def random_char_challenge(): chars = "abcdefghijklmnopqrstuvwxyz" ret = chars[: settings.CAPTCHA_LENGTH] return ret.upper(), ret django-simple-captcha-0.7.0/captcha/tests/urls.py000066400000000000000000000022521523260570200217570ustar00rootroot00000000000000from django.conf import settings from django.urls import include, re_path from .views import ( test, test_custom_error_message, test_custom_generator, test_id_prefix, test_model_form, test_non_required, ) urlpatterns = [ re_path(r"test/$", test, name="captcha-test"), re_path(r"test-modelform/$", test_model_form, name="captcha-test-model-form"), re_path( r"test2/$", test_custom_error_message, name="captcha-test-custom-error-message" ), re_path(r"custom-generator/$", test_custom_generator, name="test_custom_generator"), re_path( r"test-non-required/$", test_non_required, name="captcha-test-non-required" ), re_path(r"test-id-prefix/$", test_id_prefix, name="captcha-test-id-prefix"), re_path(r"", include("captcha.urls")), ] if "rest_framework" in settings.INSTALLED_APPS: from .drf_views import test_model_serializer, test_serializer urlpatterns += [ re_path(r"test-serializer/$", test_serializer, name="captcha-test-serializer"), re_path( r"test-model-serializer/$", test_model_serializer, name="captcha-test-model-serializer", ), ] django-simple-captcha-0.7.0/captcha/tests/views.py000066400000000000000000000057341523260570200221370ustar00rootroot00000000000000from django import forms from django.contrib.auth.models import User from django.http import HttpResponse from django.template import engines from captcha.fields import CaptchaField TEST_TEMPLATE = r""" captcha test {% if passed %}

Form validated

{% endif %} {% if form.errors %} {{form.errors}} {% endif %}
{{form.as_p}}

""" def _get_template(template_string): return engines["django"].from_string(template_string) def _test(request, form_class): passed = False if request.POST: form = form_class(request.POST) if form.is_valid(): passed = True else: form = form_class() t = _get_template(TEST_TEMPLATE) return HttpResponse( t.render(context=dict(passed=passed, form=form), request=request) ) def test(request): class CaptchaTestForm(forms.Form): subject = forms.CharField(max_length=100) sender = forms.EmailField() captcha = CaptchaField(help_text="asdasd") return _test(request, CaptchaTestForm) def test_model_form(request): class CaptchaTestModelForm(forms.ModelForm): subject = forms.CharField(max_length=100) sender = forms.EmailField() captcha = CaptchaField(help_text="asdasd") class Meta: model = User fields = ("subject", "sender", "captcha") return _test(request, CaptchaTestModelForm) def test_custom_generator(request): class CaptchaTestModelForm(forms.ModelForm): subject = forms.CharField(max_length=100) sender = forms.EmailField() captcha = CaptchaField(generator=lambda: ("111111", "111111")) class Meta: model = User fields = ("subject", "sender", "captcha") return _test(request, CaptchaTestModelForm) def test_custom_error_message(request): class CaptchaTestErrorMessageForm(forms.Form): captcha = CaptchaField( help_text="asdasd", error_messages=dict(invalid="TEST CUSTOM ERROR MESSAGE") ) return _test(request, CaptchaTestErrorMessageForm) def test_non_required(request): class CaptchaTestForm(forms.Form): sender = forms.EmailField() subject = forms.CharField(max_length=100) captcha = CaptchaField(help_text="asdasd", required=False) return _test(request, CaptchaTestForm) def test_id_prefix(request): class CaptchaTestForm(forms.Form): sender = forms.EmailField() subject = forms.CharField(max_length=100) captcha1 = CaptchaField(id_prefix="form1") captcha2 = CaptchaField(id_prefix="form2") return _test(request, CaptchaTestForm) django-simple-captcha-0.7.0/captcha/urls.py000066400000000000000000000010141523260570200206100ustar00rootroot00000000000000from django.urls import re_path from captcha import views urlpatterns = [ re_path( r"image/(?P\w+)/$", views.captcha_image, name="captcha-image", kwargs={"scale": 1}, ), re_path( r"image/(?P\w+)@2/$", views.captcha_image, name="captcha-image-2x", kwargs={"scale": 2}, ), re_path(r"audio/(?P\w+).wav$", views.captcha_audio, name="captcha-audio"), re_path(r"refresh/$", views.captcha_refresh, name="captcha-refresh"), ] django-simple-captcha-0.7.0/captcha/validators.py000066400000000000000000000020661523260570200220030ustar00rootroot00000000000000from rest_framework import exceptions from django.utils import timezone from django.utils.translation import gettext_lazy as gettext_lazy from captcha.conf import settings from captcha.models import CaptchaStore def captcha_validate(hashkey, response): hashkey, response = hashkey.lower(), response.lower() if not settings.CAPTCHA_GET_FROM_POOL: CaptchaStore.remove_expired() if settings.CAPTCHA_TEST_MODE and response.lower() == "passed": try: CaptchaStore.objects.get(hashkey=hashkey).delete() except CaptchaStore.DoesNotExist: pass else: store = CaptchaStore.objects.filter( hashkey=hashkey, expiration__gt=timezone.now() ).first() # A captcha can only be attempted once: invalidate the store # even when the response is wrong, to prevent brute-force attacks. if store is not None: store.delete() if store is None or store.response != response: raise exceptions.ValidationError({"error": gettext_lazy("Invalid CAPTCHA")}) django-simple-captcha-0.7.0/captcha/views.py000066400000000000000000000237061523260570200207740ustar00rootroot00000000000000import json import os import random import secrets import subprocess import tempfile from io import BytesIO from PIL import Image, ImageDraw, ImageFont from ranged_response import RangedFileResponse from django.core.exceptions import ImproperlyConfigured from django.http import Http404, HttpResponse from captcha.conf import settings from captcha.helpers import captcha_audio_url, captcha_image_url from captcha.models import CaptchaStore # Distance of the drawn text from the top of the captcha image DISTANCE_FROM_TOP = 4 def getsize(font, text): if hasattr(font, "getbbox"): _top, _left, _right, _bottom = font.getbbox(text) return _right - _left, _bottom - _top elif hasattr(font, "getoffset"): return tuple([x + y for x, y in zip(font.getsize(text), font.getoffset(text))]) else: return font.getsize(text) def makeimg(size): if settings.CAPTCHA_BACKGROUND_COLOR == "transparent": image = Image.new("RGBA", size) else: image = Image.new("RGB", size, settings.CAPTCHA_BACKGROUND_COLOR) return image def add_noise(image): draw = ImageDraw.Draw(image) for f in settings.noise_functions(): draw = f(draw, image) for f in settings.filter_functions(): image = f(image) return image def _captcha_image(store, scale): text = store.challenge if isinstance(settings.CAPTCHA_FONT_PATH, str): fontpath = settings.CAPTCHA_FONT_PATH elif isinstance(settings.CAPTCHA_FONT_PATH, (list, tuple)): fontpath = random.choice(settings.CAPTCHA_FONT_PATH) else: raise ImproperlyConfigured( "settings.CAPTCHA_FONT_PATH needs to be a path to a font or list of paths to fonts" ) if fontpath.lower().strip().endswith("ttf"): font = ImageFont.truetype(fontpath, settings.CAPTCHA_FONT_SIZE * scale) else: font = ImageFont.load(fontpath) if settings.CAPTCHA_IMAGE_SIZE: size = settings.CAPTCHA_IMAGE_SIZE else: size = getsize(font, text) size = (size[0] * 2, int(size[1] * 1.4)) image = makeimg(size) xpos = 2 charlist = [] for char in text: if char in settings.CAPTCHA_PUNCTUATION and len(charlist) >= 1: charlist[-1] += char else: charlist.append(char) if settings.CAPTCHA_ANIMATED: frames = [] for index, char in enumerate(charlist): # If we're rendering an animated captcha, render # each char onto a fresh image. if settings.CAPTCHA_ANIMATED: image = makeimg(size) fgimage = Image.new( "RGB", size, settings.get_letter_color(index, "".join(charlist)) ) charimage = Image.new("L", getsize(font, " %s " % char), "#000000") chardraw = ImageDraw.Draw(charimage) chardraw.text((0, 0), " %s " % char, font=font, fill="#ffffff") if settings.CAPTCHA_LETTER_ROTATION: charimage = charimage.rotate( random.randrange(*settings.CAPTCHA_LETTER_ROTATION), expand=0, resample=Image.BICUBIC, ) charimage = charimage.crop(charimage.getbbox()) maskimage = Image.new("L", size) maskimage.paste( charimage, ( xpos, DISTANCE_FROM_TOP, xpos + charimage.size[0], DISTANCE_FROM_TOP + charimage.size[1], ), ) size = maskimage.size image = Image.composite(fgimage, image, maskimage) xpos = xpos + 2 + charimage.size[0] # Animated captcha: apply individual noise on each frame if settings.CAPTCHA_ANIMATED: image = add_noise(image) frames.append(image) if settings.CAPTCHA_ANIMATED: for i, frame in enumerate(frames): if settings.CAPTCHA_IMAGE_SIZE: # centering captcha on the image tmpimg = makeimg(size) tmpimg.paste( frame, ( int((size[0] - xpos) / 2), int((size[1] - charimage.size[1]) / 2 - DISTANCE_FROM_TOP), ), ) frames[i] = tmpimg.crop((0, 0, size[0], size[1])) else: frames[i] = frame.crop((0, 0, xpos + 1, size[1])) else: if settings.CAPTCHA_IMAGE_SIZE: # centering captcha on the image tmpimg = makeimg(size) tmpimg.paste( image, ( int((size[0] - xpos) / 2), int((size[1] - charimage.size[1]) / 2 - DISTANCE_FROM_TOP), ), ) image = tmpimg.crop((0, 0, size[0], size[1])) else: image = image.crop((0, 0, xpos + 1, size[1])) out = BytesIO() if settings.CAPTCHA_ANIMATED: frames[0].save( out, "AVIF" if settings.CAPTCHA_ANIMATED_USE_AVIF else "GIF", save_all=True, append_images=frames[1:], optimise=False, duration=500, loop=0, disposal=2, ) content_type = ( "image/avif" if settings.CAPTCHA_ANIMATED_USE_AVIF else "image/gif" ) else: image = add_noise(image) image.save(out, "PNG") content_type = "image/png" out.seek(0) response = HttpResponse(content_type=content_type) response.write(out.read()) response["Content-length"] = out.tell() return response def captcha_image(request, key, scale=1): if scale == 2 and not settings.CAPTCHA_2X_IMAGE: raise Http404 try: store = CaptchaStore.objects.get(hashkey=key) except CaptchaStore.DoesNotExist: # HTTP 410 Gone status so that crawlers don't index these expired urls. return HttpResponse(status=410) try: random.seed(key) # Do not generate different images for the same key response = _captcha_image(store, scale) finally: # We fixed the random seed so that we always generate the # same image, see: https://github.com/mbi/django-simple-captcha/pull/194 # This is a problem though, because knowledge of the seed will let an attacker # predict the next random (globally). We therefore reset the random here. # Reported in https://github.com/mbi/django-simple-captcha/pull/221 random.seed() return response def captcha_audio(request, key): if settings.CAPTCHA_FLITE_PATH: try: store = CaptchaStore.objects.get(hashkey=key) except CaptchaStore.DoesNotExist: # HTTP 410 Gone status so that crawlers don't index these expired urls. return HttpResponse(status=410) text = store.challenge if "captcha.helpers.math_challenge" == settings.CAPTCHA_CHALLENGE_FUNCT: text = text.replace("*", "times").replace("-", "minus").replace("+", "plus") else: text = ", ".join(list(text)) path = str( os.path.join(tempfile.gettempdir(), f"{key}_{secrets.token_urlsafe(6)}.wav") ) subprocess.run([settings.CAPTCHA_FLITE_PATH, "-t", text, "-o", path]) # Add arbitrary noise if sox is installed if settings.CAPTCHA_SOX_PATH: try: sample_rate = ( subprocess.run( [settings.CAPTCHA_SOX_PATH, "--i", "-r", path], capture_output=True, ) .stdout.decode() .strip() ) except Exception: sample_rate = "8000" arbnoisepath = str( os.path.join( tempfile.gettempdir(), f"{key}_{secrets.token_urlsafe(6)}_noise.wav", ) ) subprocess.run( [ settings.CAPTCHA_SOX_PATH, "-r", sample_rate, "-n", arbnoisepath, "synth", "2", "brownnoise", "gain", "-15", ] ) mergedpath = str( os.path.join( tempfile.gettempdir(), f"{key}_{secrets.token_urlsafe(6)}_merged.wav", ) ) subprocess.run( [ settings.CAPTCHA_SOX_PATH, "-m", arbnoisepath, path, "-t", "wavpcm", "-b", "16", mergedpath, ] ) os.remove(arbnoisepath) os.remove(path) os.rename(mergedpath, path) if os.path.isfile(path): # Move the response file to a filelike that will be deleted on close temporary_file = tempfile.TemporaryFile() with open(path, "rb") as original_file: temporary_file.write(original_file.read()) temporary_file.seek(0) os.remove(path) response = RangedFileResponse( request, temporary_file, content_type="audio/wav" ) response["Content-Disposition"] = 'attachment; filename="{}.wav"'.format( key ) return response raise Http404 def captcha_refresh(request): """Return json with new captcha for ajax refresh request""" if not request.headers.get("x-requested-with") == "XMLHttpRequest": raise Http404 new_key = CaptchaStore.pick() to_json_response = { "key": new_key, "image_url": captcha_image_url(new_key), "audio_url": captcha_audio_url(new_key) if settings.CAPTCHA_FLITE_PATH else None, } return HttpResponse(json.dumps(to_json_response), content_type="application/json") django-simple-captcha-0.7.0/docs/000077500000000000000000000000001523260570200166025ustar00rootroot00000000000000django-simple-captcha-0.7.0/docs/Makefile000066400000000000000000000061201523260570200202410ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoSimpleCaptcha.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoSimpleCaptcha.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." django-simple-captcha-0.7.0/docs/_static/000077500000000000000000000000001523260570200202305ustar00rootroot00000000000000django-simple-captcha-0.7.0/docs/_static/captcha3.png000066400000000000000000000226411523260570200224310ustar00rootroot00000000000000‰PNG  IHDR(¢}9ÇDpiCCPICC Profilexí—{4T{Ç÷f†æZ0û¸âñŽÅ'×aEÝÅm§þ¬:]\røLZé¶²ˆrŸ ýŠwg‹+yçœÏëVÉVõ^x~±ùRõå²+9Õ{kbjý®š_“»ÖZǯ÷¹®ß Ü(ÙØw£«©ífíó·‹îšwß�¹G»oÑ¢ÔÒÞz¬-â�ýCÒ#Ü£�Ÿ>¹Ý^ó´ôYÞóýñ/x/C_9wÊwfuvÕw³^O}Ýò&ý­ë;¹w�ï ?d~LøÄýìÕ3§W¶÷þ~ßò~…þú¯ñß\¾«ï¸ûWå`îÐ^¡·ý�£ùÍÿèùÿ}ÏÿpíÕª_9qŽWøKt(£ºG‘p7œ‰ÉÈ%®¾Épªà¼‚o˜<¦�™bK0_Œ…mÇøXV�=À>á¤q¸ù8O—†+Áµà†$t$\$¢%r%îH‚¤�¤Ÿdªäeɼ6Þ Ÿ„¿„ï%èü ™„{De"•˜A|(¥%µNꊴª4Sº~Ìô1qc^˸ȔÊN’Ý"ûBÎ^._/O—¯U0TÈQœ¤x`¬ÚØl%#¥Zå•ÊßTö�SwDU_µr”'l„ÏF@S3„€>D<}4(OØ�kG5-鈯ŸŸ%b$Ct®”–¶Ý qˆÇõcñâ¶÷†4ŽŽÀsωÒCšjŠ#)!Z‡¯ux‘4! D=-QÆÔuu©;±®S42ÞëíÈ»ù{4Mf5›6o)�íyÛÊÝlì†Ù7î18êyµðÝ-›¶å\`,rÒ´»Š „ŠS¿|ýêYß;§?Ý®Y¼p:hL‰èX•xªŸ;SåÊ*™´o÷®``uű/V»q”×ÂBá µÅ/¾ñïŸcÆþë…8íGž^ùvÅŒŸê!¯Ñ¨.8ði̘—fô —vÀÉB€CÀªÄ»°zÛcº¼½pR€ŒUkXÄ„YoêªÃ^£¨<÷ÃÏ,dñ¤¡1ðiq#�ùßûò¦÷ÀS+4ŒÝºú¾›Cèz«/òí™ÓãõZ¨V±¤ð=ƒCÈÉ]šs\Ÿö£ËDŽx!ž´>@£LÇ@ if{ªkëôj:¬Õ±Ì…?Yw²(‡É%zœÕ¯DyB Ý#`Uâ©Î_.;[Vö{IÉïeWuÄj/O¿n!ìô[?ä—ÕÕ]8—µò0»1¹9ºfñ¢íë9h¾°t¹ð%½,ó\PÑÃËàнû?gýý©wWNÜÎIÄŽ™6?Î_'+ƒëÓ«DYB # àÔò‹)ñ@[Kž«É}]œ/Âj•šZæ"õ”XÕw„+G6Ø�çž³äèx®f[hk[ó$ž2òo¶…œZ³äXì�:µÙé âuú!@Ø"ž=P§6;=D¼N?{ @ijêÔf§G€ˆ×é‡`ċ݋ô O{€@m¶F@âY²¥hk˨=BÀ� ©¦_êZÇE€ˆ×q¯-YæÀñøâP×:.D¼Ž{mÉ2F€ˆçÀ‡ºÖq âuÜkK–90D<¾8ÔµŽ‹¯ã^[²Ì� â9ðÅ¡®u\ˆx÷Ú’eŒÏ�/u­ã"@Äë¸×–,s`ˆx|q¨k±‰§Q•^*---WiZ~€íu ª9¾ssæ%¼J�!ÐîçkA:Ê÷¯|÷ÝYM�tŸœ¾t‚¼é¸�9í‰O2”óG&Ñ«ºÚˆ$U·?bOõÍ‚é«òå“ç-½#!”iT%Ïœ<׺—~³hîÞþ«—ÞÙ,±ä̵u*í�/õ€0‹€8ÄÓ~·*Ÿ ›µdBb׌Ä;4"!49Í¡5o-û’sƒò¨”矙åÍ4…;ç,9žš¾ñƒ�ÅŒ%MžÿÔ„Mþ湫 –uïÝS½ww”¼ðÐæw>Ì(Tqú&/JŸŸG¾Wü¶yÑòŒ,U`|êügK™ë.áô0Ö}òü&$ø1¦úvṈ̃Ͽ†:”‡D@œ5žªø"c�£èXg`§¶Šõ˜³hùòE�zäïØz´'µê²ââ#|ðýè9óu$ãõÏ3Kåáƒ&ÇcZš4kþÜaÁ²òì5s—eTõŸ¼hùÒ—çÌŠÅËõ“ú³Ë^ß�:oÖ¸â¬�;òÊQ¨,©¾yÖËï½·xr|aÆG»t<•x÷ˆéàfÐ:  q<ÞùÜÆÜÍÙ%‘–®iTlp Ûý{1ª“ÏYñ~2œSr�s»§ï>þç´Ä„„(ÿŒÓábeL»÷£/ùò8*‡v×UÑÔT±À”yïOÃÛ*U©+¿üþÄÙi Þ·¥Á³jµª/ÆÎó]�%§=“Ìgé“pHÄ!^XÿØ—»ùIšY¾wå[ìÈgr9S±î®BsІ•œÄÕƒyèʵ¬ u •1UñÌýŒ3Õ•lð°èý ‹­}7Õ䄹·¦S"ÚâL5å¾ðLªŒo³�,.ÍÌø`Ç•9ËW¯OO¿?�•4�v— !”J¼$Ý(¹va¬Diº#Q]cX¦ÉëºÜ¿xýúõ 'w7Ud¤— A@âIBo~(žn\øÖæÌR�›y™ß¼õÎÎÒ«º)¨ºèø7ðJÊŠFÞ®ßt¨\«-:ôÅF3°‡Ô�©þ8SªÒ2Ù€±)Lµã­5‡JUª¢üãùæ¶ï8ºjy}ZUiþ–ŒB¬øÊ¹mÁþo÷g—r:)‰€0÷kcïd·-XÁV,û$cÉ‘ŒUQãæôp[<{}ÙìéLŸ2¬ûŽ=Ë>üñ$ÝbðôöeÓ7r’ñ“çÿ5Š›'F Míž±lጩ)ó?ž–pßË÷+®Z6k=ÆR¥G颚ÜQCòw•2yØø¤ÀOV-˜ºŠ%¥ “ïØ1ûIßô÷o;²ò“�ÃÆÇbI‰pDÄy1¥`™VU^ª©•0¹—wã‹î4ååZ¹7÷ZWU¹Jæ-×䯟ºà§E«—FhË5¹¼QŽS‚(Œ†É„"øN­VÒt,´c�ѨʙŒkN£RéU6�¡BÀ–ØúÅ”¹w�QHœðnXÏ�~0^S£d¬°JË lìpA2ý"8'¬›ÅM&÷æÏÉ¡D´ô‡¹�ú+ïu×âÅ£ƒ‰#6›šqDì@<&ó‹ˆp¬Õ&Žxq¬OØ1¥$ö ž8=YK˯й±v¨.33³öÚq»,Îv‚ãÚG=#q<Þ®]»Òº–:5zôè–NÓ9BÀšˆC<ôpâĉÖì§Èº7lØ ²FRG´šj¶-’%DB€ˆ'�¤†h ¢M5Ñh}}=ß´““SkúÀêêêjjjjkkëY½«ÔÕÅÅÅÙÙÌA¿ \¿!á¬~¡ IBÀ¡“x0Œýà�³“æ4gyuMuáÙ¼ü¼m�ÂSáéáé*sõp÷ðñòñòòrww´áì’+%J¥R"‘AX_'Îjkµ 1Ø+•JÁ^Û�°hÏ?ºŽZ;"žíewæf¾mÔÃïÜ›üiËbksÜ­8½ñ…^¿‰�×ï>ó÷�O¾kLêø±Âì2+ÖP'++9=ëÃâph¡˜Act`WÌ�‘6tH­V_º|I­Q»»¹ûùøyzzZ2ôA˜¬ì¬U«Î_<ÏsŒ ŠŽŠ–<¬wDoo0 ý33Éܺmkß>}'ÿm²ñÀÉ£Ç�–•—AÞ×Ç×Mæ&ã“«ÌSîéáá�KúÓZB“F§±yk²P/ë¹§nyb€‘õéuÉO®á ã—ÜÍ‘ª²$ÂY\ý”µwïš7Ÿdi+­~dN4#ÖXåRê,k¬Fí�€˜Ä؆¯Ë<ž¹gßž®]ºNøË„Èž‘ ’`es³Aïü…óžû³²²~ .«´¬ôÂÅ ÙyÙ'O�œ4aÒ�ýo‘ ÓÑâ’â“¿�„N ­ÊëêëŠ/¯þbõ©Ó§\]]Á8/…já<ìÓ«ÏðÁûuí&È‹™Q xeÃì5—CçÖ'çg¤ÁWr‹ß™>…oníOðßÀg¬Á§¥-Y;5ÞO©,Íûaû¼å:r2¶fFòø”šÔp\�&±)ñžÕÕú½®ö�Šh<¶P¬QœþÚ1‰[À xªüÓùð`ðT!A!®>Mă@uuum]­sÂT�wb(„SUªd®²¤IãÇŒO@ª¼Sy;÷ìüù—Ÿá¦º„t‘ãk ®2Tñòô�¯V\ŲP=m�öÜùsg‹Î–”•„u é+eWð‹ z…S`¯¾¼¸ùÈÔg^�_þçÁöŽZ°±æÃTÙÓߘ·—kmÄ«ûî‰6~̰1ÇŽK½gÎ O º‰'ßÖ½¹©ºi$W“1ˆ¥èÄøÃæ>-k®:•Û axˆÐ"ø€fp`pltìw¾;”y¨Bo/o>R?VUUUt±èל_±lƒ ‚$¦hRª” b�n=úÇõ ©ÖTÇÅÄ��ékÓ³r²@ãðná \‚HW•W5Õ´ˆ<ßu‚®ÈÇõ�›:e*”ƒu(á>¯–u é ‹`d³*Bg§¯x)qwþ£‰ŸÎ¼úHœÎ ]Ù?]ç {ø_Ï 7­Í=I†5tL8üÙikfpÔÃ?}a}1ýr£¼…bFµèÐöˆI<¾÷þýãûþùpN^B&¡!¡`#jœ)øúÛ¯:àïçŸ:>õö·óUàñp2pkði.Î.`&¼\bBâ‡~È9™Sz¥®Â Ö� (&¥XObæ)¸MÌWÁ1�¼G÷ m¯ž½Àgª«ÕÈ€´è†UñU ¸ÃÃ3&~Ä52#탿6�©¿|î&�·c¯î~Ù�Å„'b\µj_I¹½hEìÑ®"Œ‘›€yfIiÉ/'~�Ï�¯Ã'x¸vÃÚ-Û¶”_-‡[‹í+(ä=|ê‚T|¹T"å]và蜜9Ï^¡1è„0VPŽ¡°—« –›››··7\_Xhþ€Õ‚°u2n©/ooˆSfÍûçžbõ‰Uãu Åô5SÞ._9Q:�Í·¸Ø8Ÿÿùœ+:w8ó0ȶaË„7à|¸ç�ÁIƒ¹ ‹kSÐ…÷x˜Ož*8õSæO … ¹T|ió7›Q«Oï>Ý»vo"ž”#ú‰ø 6…sSÍò+ø=SxæØ‰c`,š@‚0~áKA`AØz™AO¼–6ok#}¸vƯHO m缿œöð)ÊÎÜ´|êr~VŠM„a" V¤oNLl¤qCßã—dÖxßÂB1ë™Oš-D Ùaa}S1¸„òÃâܾk{Æ3P‚°J¿¾ýÒîNëÓ/À/@X˜¡:|ˆ§R©0Q\·a"™üþ[•º  ñ•±·�EdE¨.Á§¡"G<½ø:7Õ,+»zõê·{¾…§ÅRÐ[á IliŒM‹-Agf â1·¸E[g¯Ïm-èÒóÿzĘ�§¸¿Ë§Ü$ˆòåãŸß�þ¢™0Œ~-ä{6<ïÂ¨ØøÐB1ãjtleÄ':ŒMáØ»/B#ðo)£Rîºó®èÞÑ ’‘çñ*«*±Z£üýýÁ:Ä'±�ÌÀÄ�· ½eÈÀ!Ƶx'†Vô‰%¨…É'øÙ%˜›ÄB'RÑ…"xÅÑ·Œ†¼Õç™�—*|œ°µÀVd>ÞX~­¿ñiÏ?6óÁ»EúšJ.ÙwfNRp•ÞŠTaföj¡˜©~*±1V!lm°ŠÃš Ó¼ÛGÝ>e┞á=á…±4²PiFöˆ|ôÁG»‡uG¸´At[‚Øò†×Ù„Zñø5_®Õjqî!ŽòøC�#°‰0)·‘P^†I&¦¸ÂMg‚kfÝzâ¾´�–c†EúM/ÙwnÎ`¿ª*æ®pkáb¸Éý$nn 7ƒù§¾>o¡˜iE*±1-\ëëì ïX)Á¬Ž [sãRÆõŠè…•žYŸ#„4Á™˜>1˜‘¢.6Ù1AK‘_Çw‹=Ó5¿‰‡*àmߨ¾p¶Ð�‰(Ê¡‘R#%×i[««U0„][$‹›\!‘¸™ó^F�ñ�î5*4=´PÌ´"•Øñ‰Çwç÷?~ÇèïÙŽ n§9³xâá^3~äÄtQší<žé�‹¬ènRñõöÅ 4Cß ll®iÇ('ª8Æu°m/¬o(¯(/ø£t AÎŒ‚_ªPV`A¯yìï�xàjÁ¡auadxâùøø`£O¿:˜Ì'ýBÊöEÀ*ă£Ã<a}ÌA<£ï r"æ‰ÍnðÓˆ3F’ü!¦ŽX@bõȹJÝîÊ¡» !ïñÌV¤BBÀqhvF×–.bi‡pâå’ËØ‘þ‚°Çmªó@Ì ±*ƒ¿Š��‡ÓC ”©¤P‚³~b)ˆ-;èG9_ñxÂÀÀ@ÐR¶WFsí†ñ:K’¸b–´H2¶@À*Ä~/äxu€ÂœYƒ@Ë¡ƒ†â«` ü˜Y£B?_¿‡ï|Ã-`† !¬rC¿À�–Mñð þ bË’ÂYÌ'ñ+"ƒïª�ºyb¡pw–ÌWõëZ!ï–úáñú[R}χõ÷´(¡«-®XK¢s¶E@|â¡ÿ<‚`Ü]Ë <ýY¥~¾eL%áåÀѵÜk:K4!`âaËN¬[(÷uo„›Z£!@èŸx¼;Bì‘&�æ wÔ2TNX"žÁ%Õ„@sñšC†Ê +" Ú¯½¿„ÞÿfÅQFªM‡xíý•Wô®S“�AÖE€¦šÖÅ—´f â™…… ë"@ij.¾¤�0‹Ï,,THX"žuñ%í„€Yˆxfa¡BBÀºñ¬‹/i'Ì"@Ä3 ÖE€ˆg]|I;!`"žYX¨�°.D<ëâKÚ ³ˆC¼‚‚h§OB 3 `–H­-¼Æ#†pÏ>Ý@ÜZLIžÀ? ÄÄÄpÇãµÐ�"Sˆx¦˜P !`uˆxV‡˜ L â™bB%„€ÕçèVï&5ÐNÀÛ°ËËËñ><ÀßòGƒ;”qx>%ÿNEooo¼|ÊJ}#âY ØN§öòåËgÏžýñÇÏ�;‡ôbÔš>ð»]€‚ÿx×^øÑµk×!C†„††ˆÞs"žè�vF…§N�úúë¯A¹#FÜqÇݺuñÚéCÄñ²78í?ÿüóĉk×®ëÒ%EEE‰{]iO\<;£¶_ýõóÏ?���7~\DψŽÿ%77÷Þ{ïíׯŸå¦]s�<žå`’¤1p¸ÅbåÊ•“&MºõÖ[]e®jµ3L'g'VÏœðÓ“ã^3\__[WÖí�صk×ûï¿ÿàƒ0@,7NÄk�CÃQúŒµÜ¦M›âããq—kµ¦šcÞ+Z˸O]R—_©aîî OO·v1Ú°ÆCça2µµ50ÂõmÙ²¼ Kôi;A;£Ä-<î!�O�F�$­ð§,÷½©~¡á‘áá¡A~^wg–éNC¢•¿™K=<^?ÔÚZ×)_Wsx#ð©®Rƒ�°¡Z„ŽP"ÊÅnÿƒD±””ˆ‰Æ"Þ;�yf\\bîµ!xx  ÿ¢(g§‹ÿM|#vÎ÷§žMpS+/ŸyiÖåÊœ�s߯öÏâã;3óJeáþ�ÿ½ë´YI®ð³™~”‰LeUñÚ‡îøðh1ò¥göε2xVúŸÜ©küföЃŸqJð£¬T"Á@uUU]}Öu°–b›…¢\xòx¢ÀØé”Àã!Ž‚×ÖûûûcË £SX!Spl7c¯Äùa‚fÎÅï_ÿËS9s”­¾ÈÀŠÓoÞZUþÇš?V½’qôèSï¸}KöÝOŽ|lNôšm£>_÷ø�Þ.5yEûÖÜ]f$û{W”ŸÎÖ¨u-ÕgÔ¨«jjŠ_ï?Aöɾ?nõÙ0=þé� ��Ôë‡6ïàÞ“6aá–›{æ¿ëFɪ¶¼6¥?XqQ"ç:Æ[Ka/¬Æ©¶{<"^#öô·•ÀÅyzz"Øo€Ä»~DâS¢ub}�9F¨Õfïü´ïü¯n ÷fÌûÞE¯¾2jÛÙׯH´J6}í{ÓobLÝ»/«p“HÜÂü˜_pP°7$™‹“¯“³ §ÍTR"qåug“¸9± »øë§Œ ùnõÒãnù;ÙÎx•aG´ª’¢³¥LÖØ7Xq¹„‹�)UÕJ$²zDV8îÁ(ÌŸiÁlS*•b’ «+µé¯!,mRE•;”HxﱨãјàXPŽÏ€ø”,'uÒ’œ’ZN²V•³oßÅZÅпܟ³`í) Ê4{?{–Mœ£�J\p$WÉʼnÁµI¥î�¡ìe _(=I®zÎÁ“*'Mfú ó�H½“žbìਗ਼CB}Ýy=-|FL\¹iö0 óú –ñ‡X¸ V·}ñÚŽagÔ€!ˆfhˆ¯À~ Mý1ͺG½Q°o~�ŽŒíŠÕQp×È‘ ƒF=ÿöÚž.޲ð©OÿøößÀ ww8´ îŒÏ*†Ü÷*[|§¿ÿcÙ©›;[.hœ¤´ß˜Gb·ÍŠ _|ùæÇbùê=ŸÏþ*øù;ƒÑŽðÿ9­ß=³yß.]̲“glDXöÂjØÞöKN·ŒµÃΨ3.PîÈ‘#øLJJ {p@D�Ê+ÅUZ&qWøzbbÙ�´j¥Z+Á–zcA3ÊÀ²Í’�wN#3Q¨U¢TâÖª­{˜†Þ`’)$„mŠ‹‹�=Š©5ŒU(‚uÍô›{þ\msgQNk¼À¡SÍ"€‘‡n†>pà@QQîßç�?åÔ¯æê«¬ËK¥¾ðl×NðM×ÒI˜×(õµ¬ÓFÖñ»y.\÷°�«¯É:Sm¦%DJd¡±×D<Á$UÜ ©]/ðlséÎÛàL­ñ à BÀ6ñlƒ3µB @Ä3€ƒÛ @ij ÎÔ !`€Ï: lƒÏ68S+„€D<8耰 D<ÛàL­ñ à BÀ6\û–1Ürn›®P+„@çAàÏ\é<@�¥„€- ©¦-Ѧ¶�ˆx4; @ijèÔ$!@Ä£1@Ø"ž@§& "�BÀü�*î¶ï„õïIEND®B`‚django-simple-captcha-0.7.0/docs/_static/dict.png000066400000000000000000000172631523260570200216720ustar00rootroot00000000000000‰PNG  IHDR#…Ÿp =oiCCPICC Profilexí—{4T{Ç÷9Ì yRI5ò(Ly¥¢"„ŠƒÑãQŒif�I¥wòH¢šèNÉ#º)¤ôBé¡B/• ½‹¨\æþ&w´î­ûÏý/{­³~Ÿ³Ï÷·¿söù­½6€R6�Íà œÅãÐlÈ^ÞËȤfÀ€²€ƒ �Áe[S©NHò ë¹�Ôȧ‰c�_çcï`R&ääkŸmö+üÅ$‰[‘ƒÀ(È¡<ÈóÅ0ÈîbŽæ±yH"fF=q,b Ç�f‹ø(bÅàA.sÀ ׈9Š,žÛ @Tf†²Ho[2¹ ôX¼® —Ž8q}xxНä…x*ƒÍAs•âk‹¿ ‘ñfÌÕÀ³~ú|EEÞc[út¡í J³úºh?¾¦v“dj"Ž˜¼ á‰HÔ¥‹ö– п[$êˉú�H=¸ƈäDýÐ"5và¿îßùŸR(9âËÂD°„`È€&l%ÕB0&öȼ–¡°DéਯªsÔÖŒMT_§a5áé¤õšc&Óž§Ó0…©‡é )ÖÓÚ ãŒÇšdÌP6‹žÙ0{Œ¹‡Eüœ’¹O-IVó]­cmNÚ~°›mŸêðfe±»c¤SÂ’ÎB—LjºkmƒÛZw¡GÕÒçž=ÞØ2i̧×÷Íò'~·ü¯¬(¡ä3N–1ë‚:BäB)+ Wi…©†Â{Xí-ìë«Ïq ¸Bž R%ŒÎ_SSÌÏ[›°Ž²ž¸þã†çq�«6•lÎÛ²ëÎmüí«v,�wÙi“`š¨“¤–LLîÝÕ™Ò´»$5z�E)íIzÕÞŠ}W÷?ôe(gôeVÜö‡«PK8pèÙáëYeGò³÷çÄçòóBŽºæÏ86òXÛñ¢« L ±Â΢–“uVœ**>t:õÌæ’ÈRŸ2ƒ²wg‹ÊùçœÏëUÈWt_x~±ñRåå’+Ù•{ªb«ý¯Z\S¸Ö\#¨õ¹nP§R/]ßs££¡åfÝ­ó· ïwÝ�½G¿oÙ¤ÜÔÚ|´%ò�ýCò#üчÇOŸÜn­zzæYîó}mñ/ø/Ã^9·+¶gvuÔv²_OzÝô&í­ë;…wõï >d|Üù‰÷Ù«kf·|÷ý/‚ž¥½J½µ_㿹|×øÞÕw÷¯òþœ�="oú;‡ó?œÿáóÿûžÿÁÚ'® DT¿²·x´8^,Ð�  ‚êUÀÝpSÓ¡KR?|!’àTÀxß0EL3Ãb¾Û‚ °B¬{€}ÂeqM|î‰sñT¼o¤t¥\¤b¤r¤îHƒ´¡´¿tŠôeé.‚Á‹�H¸Dè&êˆÄ{$�”Nz(£-³Z抬š,K¶vÄ”ÛG¼–s‘;#?^>Nþ…‚½Bž"A‘¡X­d¤”=rüÈý£ÔGe)+W«,Wù¦ºw´ÆèÃjjåc¨cÞŽM7kÜKõ}ã5p�ò œ‰zOJ"[�Û4ã'OŸÜ¬•©½RÇBWA÷Ù”Ò©»ôVêÛh PîNË�m¸ÐHÓ˜dÜoª8ÃÈÌdæÔYäÙêæjªsT玞§nI¶Ò�ohmncoë± Øn­}ŠCîÂúņŽINMÎò.úÔY®437}w+�O²W‚÷+ß5ËKýºW˜Ñ#J"&-¨8D'´`•SØG–[D>{€ãÁ-ˆ”‰ Œ®ŒÑâgÆ.]?zÃÅ�ž›Z·øm½·}ᎊ�Æ IÉ%)λû÷T§çïK8�iu°Vè}¨#+&[.'+Ïâèãci'…Ž'ç�Ò;�Ÿ©)åŸÕ*¿yžAïâƒËI•ó«z®¯É­Kkð¸u£QãžSnKçÚÇé­áϨmv/ií›:Þš¿ÏýøµËô µ×ë›[Ÿm¿Ù�ú1œÿáüŸÿßöüöKâú„Ð%tÔ;ý¿)‰©„"˳œ]Ð(ƒ®*n”›�„ƒBíI8�¾ÀQÂü[g qìi^I_L•0“åá&a6ÏfHÃäÚ ùù!îžͪÇ!M sÁÐXaÎNM(o‘¸Gý±O…%@Æ`‰¼@ �‰ú[€ŠÏ~âá_Æc®Aý%€m;†Â#[£šI!/b1¦SÈ&FF³áoTÄ*­Ÿ‹�Œ pHYs  šœêIDATxí� XSWšÇO  @䊢 ˆB•Å*õãñ£®ÚÑÖë2ÓNk­³�¶vZkÛíê<­SíÚÕuûttÛi;R­”®ZëØ�¡>Z?‹NìŠ #ZTZ‘„$`ÿ— —�›�.!È{õ çž{Î{ÎûËùßóqo´´0Úˆèe^½lŸÌ"À ¥Q; î @Jse*ƒ�Ò¨ w ¥¹ƒ2•AHiÔˆ€;È»(¤   ‹£tˆ{ÒÒÒ¬u¥4$���·Ê@»D€tM ¼¼\œ€F�b&C¤'@J“ž)Y$b¤41Š!Ò ¥IÏ”,1Rš˜ Åé �Ò¤gJ‰€˜)MÌ„bˆ€ôHiÒ3%‹D@L€”&fB1D@z¤4é™’E" &@J3¡" =RšôLÉ" ¥‰™P �ž)Mz¦d‘ˆ �ÒÄL(†HO€”&=S²HÄHib&C¤'@J“ž)Y$b¤41Š!Ò�Ti&½æ¦F£ÑêMf©kj:{àó‚›&©Í’="à&žØãt-´Ç·ýñ�y…é‡feoZ¤ìØïaÈ|îÃ\Ýê™i‘Š¢ìD OH¢4ýWk–o/Uf½²i~ªš™ô·n\¹x]î’銯֭:<þãM÷ÙU’’ùºf²OxR¡DÀ6—ä`Û„©âÈöR6eÅ[‹Ò"¹r•:>U��éÔÎ�›÷s�2qî«/=‘¨b¦Š+ß:›™·çÝ=UŒ¥g­~aQª©ôóUÛ‘¬ðч÷d®Ûòp¢²âÔçïü)·BÏÙËZ—½(‘ù2ÖP÷�Ï×½�[¨�HÉ\ýÒÃj»Y°kí[œƆf­þ÷E©¡Œé¿Y÷ì‰äWß@Úˆ€Ç�`ž¦¯ºÁXĬ ­2ëä˜ÙÀ†­\÷öÛë~Pš÷—3•8h6ÖVU�~÷Ý£³W®þíÂÄÓ¹ëwh”qwg¥`¤™¾bõª)Q mÑÎU›s ã³Ö½½éµ•+’ù~Î'Œåm^0<ó• « ÷ä]Ð"Rw«aúŠ×¶lÙ�•R‘ûþÁVaÊUÃÆ ÷ëTÚ!}M@‚>í§’bÆüm9¢œñÈ#—IÏ&G°C—«Ø uk2åÊ÷¶NB÷3iØõCË�½úDZjjbX‰©É f>üþ~Æf¾öÜ"N»ê¡­YL�1÷•­OàѰúÌmû�ž»öDª*~Î#è;Íf}x0c?ñUPLzä¥I|�>‰€Ç�@i±ãïbûm,7joÛøn^)S*™ž õÊ j›�É}X@k¼™À6L_uƒÁP«Õ”†z6yÊè6nm+OíZ¿™=r‰#Ú"éð@Œ•!è{ô¹ßY¹§)È}7¯fåÛïÊÎ~,‚Ýê8ìï#¬{Ô³úŽx>ä1˜±[:±r;Ç™Š ³Á�mصk×Ú¬¡bCÖ†iŸô ”&WO2…UìY»ñó�É„‹jå_m|ç€æv]ë¨ÒXyö+ô;ººv¡TìÚ{Jk6Wžúß=z6oâ0Î}¦ÿñŠFofŠ æ2}ÞÆ�§4z}eéÙR[—Ñ8}šy{f½¦t_nfmZ.Â\~ü›ãEÎ&mDÀc#ºžÔH1gÍ{ì½Íæ¾u:·ÍNâ•Ã&ÌIaë7?¿œ)SæNš÷íæ“?XÒ:¡»ô×ÍË÷p)S²V?˜È ýâïÉš»yíSKç®þà‰Ô_¿ö˜níöÍOa¾ÆXæºìÄÈÖµGn¯m óõaÊØûÓ#>ܾfév–>wŠ2/ïùß…do�szÛ‡{¦ÆNMÆD�6"à)d---öê‚·:¹ô®³^«15É™·2X¥h“°I«5+UJìéµz…Ji*ݵtÍ÷ë>ÞoÖšäJe{:®X:11……ÞÑl–wìÛ®¦I¯e ®8“^o‘ÙvbŠ%n €w͸üV'—ª%Wª"­Ö1 UÛœ zƒ5S£Ž± ƒ™!±u UYF) 2a>g·" ¥Š?¦Àº mDÀS X6mwÔQ9ò� fG‘(Ü›Êð (íàÁƒ®:T^^êjJß�Ù³gwq”y ”7/^ì Î Ì:ìÞ½ÛÒq›/¤´L@apiBbÒ(M’ª�©ˆ§ãRY¾3ìôÉ Ü%¸žvgÐ'/ˆ@¯ ¥õ*^2NÚ�Ò¨)w ¥¹ƒ2•A$^©¾U]­©ÖðÐðð°pø˜[š[!ƒBæíí-pï"½�F„…†Y™Å!«W â-àÕF^¡,{ÔA8$-Ä àÐ#™Læíåíëë« PªTªàÀ`…ŸÂKÖé”'@³´,„-9‘ðpR+MS½ñ��ðù¥ç^jSZkÌÅ\TV?½ìéñãÆ«‚Ûîê@2´o›é/”]ƒ�0ÚÊlÏ â-àÕ°Y¨U5P‡…óîÿ+wG¦PË4]{™aƒÌ‚ƒ‚££¢G�•>>}ØÐa�Ÿ¢ã׫¼{õ±ä`Y4…=™€ÄJC·À·¡àcNž>‰†­ŽRã\.o"ˆ½ôP&z ËÄ ¢½Œ®³0%.‘hî8*®â­|´¬›½ŠµyÖÒÜb4ë õ×+¯ü_Á7ß~óÀüfOŸ38ÆÏ¯Ml¼‡,Ë¥°‡�Xiö¼Å ¿§AÃ*,.D_âàF{´oô8y[äGM–1â°«ñøâоƒÐ’ÐÙZVu¸XvQHæ|@𨹹ÙÔ`ª­­½\q9ÿûüò+åÙ9ÙMMM æ.€Ø¼¼:†‘B«Rœá`•…vûœ€›”i�ŒYWW—w0oL☠À Ÿ.œGcBû¾çî{ºHcó�«ñF ~~¬kiuÀ®¸–‚´Lßu˜÷(cbF k�®LF“¶N;-cÚ§Ÿ}zèØ¡½_îMJLÂT6P(Øé6Á<‡@Ç´Wë„F3kÚ¬���ó%ç ÎÔÞ®í¥âÜVP÷ê�I?|ä>���êhõØÑc™ùKÌÖjjk0îÕÞæCDÛIÀMJÃÜlÄðÓ3¦›Íæ¼Cy•?UbÕ@ÝVPÏ+ÕÄÆÄN™4EÆd˜•Õj{ëÔóÚ’…pÓè­ ó¸!q§þ~ªìRÎßQ‘QQQöj/¬+ ÐYaÞ"ã ø€«Yewó.j« R ‹&÷‘߬ºi0p�-61r’ƒ`„BÀMJƒ·þXΞ9mfîg¹¾=�vWšår¿~™­JˆÇ|Éæªº�@¸T��«¯ÜŠ`0†”X�l47ZVC G�ç`iŠÂ}NÀM£Gø‰ó7–+°04fhÅõŠcßÃË2ºx¶B·Ñ¸£ Ykí°¸ÑùÙØm‹á8çÌ8—�RõcîëÓ KðCb†Ì™9gÛÎmXp›”> 'r›ðÄ Üü¨Éfbq¤ó‰ó:Œ�’Ñ5·4ã?t…]! v±ˆ�]$°Œˆ˜¯Þ®» S˜ab¥Ä2�ŽºÄÁÒ…û–€[•†Æ‡…ìÉé“�åÃÀ¡£‡°ôoÕXyhOâåuçI9_�ó6…”¸›L¡Pèëõõõõ��� ‡°‹HÈ&@ ÷v̾߾}ûÊ�WÌ�æÈˆHŒ{Sô‚¥) ÷9÷�yW¾ ¬nÏ�5çoèíÒåKh²½A¡÷ ò÷óÇÝ›ÐÆµÊku:<Ó²c»­»}µò*A$ ‡;‡ 3øŽôÇOÇkýƒ ꜄öîîVÈ R ÂrHrR2." c'«ùƒ8†¯7×3DDâ¿•öÒ;Lf/c÷ ²gM¨ñ1³šŒ G-MY&æÃÝÈ"6B1E Ö=ʪ pRš{8S)�)m ·òß=HiîáL¥ t¤´�ÞÈ÷ ¥¹‡3•2Ð �Òz ÿÝC€”æÎTÊ@' Í•k«—å t¨}퟼¬¯�öôò%Pšå­@žîûßv9 Jà"�%€H&ˆ€C¤4‡ˆ(�€)Mˆd‚8$@Jsˆˆ �Ò$€H&ˆ€C¤4‡ˆ(�€)Mˆd‚8$@Jsˆˆ �Ò$€H&ˆ€C¤4‡ˆ(�€)Mˆd‚8$ �ÒðÌ-CŸD` p¨({ $xÞ£=ÓO&œqÒÒÒ¬|— O³²H»D€ˆ �ÒÄL(†HO€”&=S²HÄHib&C¤' Áo®¥¯YìŸjñ:­/mÄ[GðÚªþè^E‚9àÍ{x9‰´¯h%¥õÇöàqu®®®¾víÚÉ“'¯_¿Ž÷¡™Z½=Çãjl§B8AàÍãxUPLLLFF†Z­Æ{íì¤u-š”æ/J-&PVVöå—_Bc3f̘?þ�!C 4«·.ŠsyfLss3ºå«W¯ž;w.'ç“Áƒ£áQbbbÏkK×ÓzÎp@[8þüŽ;’““Þ¿0~xü�Ä‚?ƒ”””<úè£cÇŽuÞ5›×Ó¨Os ¥ìD§<înÛ¶mK–,¹÷Þ{}¾F£ƒF™— oÆ ;¥î/;2¿XKKSsslì�Ç/âܺuë²eË&L˜Ð“Žš”Ö_š€ÇÕó±½{÷¦¤¤à~´ÎS'3™Œ51î³u3ÖVÕ42ÿ À@¿~ÑÔ0OCåáMM�p¢sÛ·o¾¾¾Ýþh•¿ÛètF¬.æççClX6À§ÉdD 6³ð§¶dËÒ€Pu܈¸8udhpÀõ­‡‘ÂÅÿ›ÖŸr5W7Ó77ÃÞ | FH>bAë=ˆéö·Þ/N4ÝöŽ2ö 4>ƒÁ€¡ã¸qã°n2š°2Ž~ÀÛ[ŽŠô’ÝøOuÚ›É+�–½œîgÔU_ßé|ænølÔéü‘±EÖÂ0xd-Ð[CC”†|Ä<í‡~@ï­T*…Û¥‚¨Os %n# ×ëëêêð¼dNZ2†F©×ÕóDXufÿ›Œåì\• l®7Ô7{$Ü5FÕX_oМøè÷ƒBáÿü—³KkS_øÉKÏnÙ·ûÍáâ³6V×ïYõÌ×ìøÊŒA!ÏÔÔWÿà^²™’‹üèÙeï Po¨ÊyrþŸÎT!¬¹røÙ ®”É+²¯r‡ü/úèÉeqFðOW¯Ã�CsK3æfðžbõ‘ÝnÔ§uÝÀ͈> ‹ …",, —ž°¡9 ÓÊ8ÄØëãB1æêDéÆÑõ¿x¡87¿xbxíÇ�eL\té?î5hÜùûô¯çž9óÂÖ ÿ¼¯èáßÍ|zåè�_ÏÚñÉ3ÿ¤òn¼Pyüï¦`ËFÊñª:í¥"“±µ¤Æª¢|“ÑÐØXµ~ü"ŇÇ~¼wÐîå)/îIýàþõ0_È?\®g>íQ˜|•”\ùì“Y þ7~5’€ƒ8ˆ¥¯fÆ{Oá/¼Æ¡îõi¤´vÞô×èıB€ó=6þÄÏ7A|ÊÍ2–äÅI°“MsÑ�?'­þbZœŠ1Õ£ëþðú¬¯¯½1OnÖ±å9[–Oc̘�Äêüär¿ðØP¥BJæ- ‘yysÖÄ)åroh¼õ(cr?kô–³çÿÌXÆ‘�7�õ+=À¤è;WĬ¿UyMÃíuƒÕ·¸%œ+}“\®hÁr'68…!1–G0€Ä]#7ÂëöL.ÿíŒÂåì”a @+Äæçç‡&È+�4† 46m*û·OŠk~3¥Óý�“ù+�Ô¼½üÃÀÒ»ÉK6&:¢5² m¬y!Cí)‹ Í”¹LæÏ§ô2—pÙ1âƒýù,¾'˜5.X°&2ž·ÓþU)32“ѾÃÿ-1Q<ñ/Ÿ=s7êÏ{‡OÈ ¾`~ˆ0>ýýýùC�³:»Gó4gIQ:�š#ÎñøÄÄŒS@û†®ñø O™û+Î\òVñ­&.e“¾øØ±MA÷üâ±â59e&Ä™ô2[¼dL��Ü÷r±yË:/ÿ5ûN×ÈG „€EJ.{qþE½ÌT�ýÜÆäò „ôË/kƒŒ´Tuˆ?o§‹ÏøÅÛö>? PyË žñ»˜| ^ \ �Ò\ÂE‰9hs˜´`Ð…Eì¢-Z6bn×?ñÍòc«‡ý÷ÌäÌp¢bFÌ\û7èføCo|úbùÔ8ÄÅ--ñä=øû£Ëj³à�uK.”ñë?° ÷……=]dòñóÇ„‰»ë×VJŸ±óþ5ùë#¢â6TO:™Ï>üÕ¢/¢^½/ å„E=þé%ËêÙ ‡ lS޼Ìà#ÖBà/¼†ï×7ºËuf>QÐØéÓ§ñ™žž!LØÐ �Ðu5U3“û…b¬Ø¶™�:£YŽkÙívþbýS/g®xs™È Y‡X¹ŸK×Ìájƒq£°a­¥ªªêÌ™3-ÃÙ   Á;;õæž©ƒÎÔê(ÍÓ¬€Ð®chj˜´àNâ'NTVVânw¾[ãG‘–ùC"Õ!–û­aŸô]Ž7ô>޵¦°mÑ'ĹrÄ…2㯪ýüóÏ®_Ãk‡2[ãcHiöÈP|W ­èèè„„„‹/âÎ}\ÂZ]eë'ǰ‚šò×Ê+**JJJFŒác·=è~ÎnIï8µc•Ô¨Q˜Ààft¸·' Ñ™À©ÂÂBŒCCCá)üív‡ïHiø÷�*áÄ�qã¤I“pîßZ74Gl±±±˜¶õlÕÍð«ÖÒÒRt×8ž””„®;88˜ïèlåp*Ž”æ&Jd“šàèÑ£ñ‰e€ .àçjH‰ [ONÿ6ËrO$é ea’‰›°ð³k\³î¡Ì`�”æžoðŽ-MËqh‘h—5­n4™L½zCpïÑÄ9Kù(†´n8¿.Óu­Hi]ó¡£N@sDËDφ¡#Öë°9•Í#ñK;âuÔV–”ÖC€”½ƒß:ûõ$­Ã©Ct�ˆÔDɰE€”f‹ Å© �Ò¤&Jöˆ€-¤4[T(ŽHM€”&5Q²Gl ¥Ù¢BqD@j¤4©‰’="`‹)ÍŠ#R ¥IM”ì[Hi¶¨P�š€ƒ»±pƒ¶Ô%’="0 tõ‘�ȃ|&½C€F�½Ã•¬�ÎHi�yÐ褴ÞáJV‰@g¤´Ît:õÌæ’ÈRŸ2ƒ²wg‹ÊùçœÏëUÈWt_x~±ñRåå’+Ù•{ªb«ý¯Z\S¸Ö\#¨õ¹nP§R/]ßs££¡åfÝ­ó· ïwÝ�½G¿oÙ¤ÜÔÚ|´%ò�ýCò#üчÇOŸÜn­zzæYîó}mñ/ø/Ã^9·+¶gvuÔv²_OzÝô&í­ë;…wõï >d|Üù‰÷Ù«kf·|÷ý/‚ž¥½J½µ_㿹|×øÞÕw÷¯òþœ�="oú;‡ó?œÿáóÿûžÿÁÚ'® DT¿²·x´8^,Ð�  ‚êUÀÝpSÓ¡KR?|!’àTÀxß0EL3Ãb¾Û‚ °B¬{€}ÂeqM|î‰sñT¼o¤t¥\¤b¤r¤îHƒ´¡´¿tŠôeé.‚Á‹�H¸Dè&êˆÄ{$�”Nz(£-³Z抬š,K¶vÄ”ÛG¼–s‘;#?^>Nþ…‚½Bž"A‘¡X­d¤”=rüÈý£ÔGe)+W«,Wù¦ºw´ÆèÃjjåc¨cÞŽM7kÜKõ}ã5p�ò œ‰zOJ"[�Û4ã'OŸÜ¬•©½RÇBWA÷Ù”Ò©»ôVêÛh PîNË�m¸ÐHÓ˜dÜoª8ÃÈÌdæÔYäÙêæjªsT玞§nI¶Ò�ohmncoë± Øn­}ŠCîÂúņŽINMÎò.úÔY®437}w+�O²W‚÷+ß5ËKýºW˜Ñ#J"&-¨8D'´`•SØG–[D>{€ãÁ-ˆ”‰ Œ®ŒÑâgÆ.]?zÃÅ�ž›Z·øm½·}ᎊ�Æ IÉ%)λû÷T§çïK8�iu°Vè}¨#+&[.'+Ïâèãci'…Ž'ç�Ò;�Ÿ©)åŸÕ*¿yžAïâƒËI•ó«z®¯É­Kkð¸u£QãžSnKçÚÇé­áϨmv/ií›:Þš¿ÏýøµËô µ×ë›[Ÿm¿Ù�ú1œÿáüŸÿßöüöKâú„Ð%tÔ;ý¿)‰©„"˳œ]Ð(ƒ®*n”›�„ƒBíI8�¾ÀQÂü[g qìi^I_L•0“åá&a6ÏfHÃäÚ ùù!îžͪÇ!M sÁÐXaÎNM(o‘¸Gý±O…%@Æ`‰¼@ �‰ú[€ŠÏ~âá_Æc®Aý%€m;†Â#[£šI!/b1¦SÈ&FF³áoTÄ*­Ÿ‹�Œ pHYs  šœzIDATxí� \Te¾ÇŸ��™a€áEÅÀV¹¾„º}LmÍ–Ê’Œ»µfå^·²-Óº]ÝO¹akW¯·O«·ÌÖ·4Ì\_2[2ÌR#ËÐ…$EeS``„ÃÌÀ÷w80Î03¼Í9ÓŒü�~†sžóœÿyþßy~óÞfެ¥¥…ÑFˆ€«x¹êFt"@x$9ªDÀ¥Hr.ÅM7#$9ªDÀ¥Hr.ÅM7#$9ªDÀ¥Hr.ÅM7#òÎäæævž�Î"`—@rr²Ýô.$‡kbccí^I‰D€8"PZZêè5,‘¡t" ’œ$XÉ(pD€$çˆ ¥I�ä$ÁJF‰€#$9Gd(�HB€$' V2J É9"CéD@$9I°’Q"àˆIÎJ'’ ÉI‚•ŒGHrŽÈP:�„IN¬d”8"@’sD†Ò‰€$Hr’`%£DÀ’œ#2”N$!@’“+%Ž�ä‘¡t" ’œ$XÉ(pD€$çˆ ¥Iˆ-9#§½®ÕjuœÑ$vy�gíͽnÛ,Ù#.%ÐõÏ u»8ºc›þö·¬¼›ù¥o^3[}óØÉ=Sþ{™uËîLŽP8iˆ.'¿ ±$Ç\¾`k±:ý¥5³’¢˜‘«ºvñüUy�¬—ÌXrdÌûkîq()5óí™É_,ÝšØ'Ð#QØ7�TcÙ—[‹ÙäEoÌNŽà3É5Q±IQ±ü™ÛW¯ý˜}êø™//}<^ƒÌ‡¿qfΜ˜Ýoï®`l\ú²çg'‹÷.ÙŠly�>´{Nƺ‡âÕe'ö¾µ!³Œãí¥glžÏ|k¨ýqoÆ›™y\xâœeKŠR°ë¹»V¼ÁÛalPú²ÿš�Â÷YÆ3Ç^~ ×ÐFÜŒ€8}9®âcáÓÆ¶êÍÊC“ž ^œñæ›ô+ÎÚª'M†šŠŠ“o¿ýÕôÅËþxoüÉÌ•Ûrµê˜ é‰h„Ž[´lÉäH…®`û’µ™ú1éo®yeñ¢„0>òù„²¬µ+³Ãæ¼´èÞŠ¼ÝYçtH¬«j¸cÑ+ëÖ­JO,Ë|7»U¡rÍà‘C”V¡"àĉrÿ**dLeÏ#õÔG�ÊŒ»=œþ©‚M�jͦ^üÎú‰H_=¼àð™Ë�''%Ňf^ˆŸ” `¦#ï~ÌØ�¯<;›qÔ ÖKŒ�z>ó¥õ�ãGp¹9›>þ*ÿÊãIšØ»A45™¸°@Æþ%A1ñ‘¥…]z%nF@ÉE�ùûø°ÁÎ ¥îȦÕog3µšql�¯ùv~m=6¹¯ókM71=àÀ†‚qÑN é0ôÒPÏnŸ<¼  _Ûßò»V®å–|æð¶DúCÜ–€8 Ku0¢—ùYA?µ¹™ogU/~óý]›7Ï gU7O«|̃$õ¬þfº°§ ïÏXU�­„­ÓŒÐ[ÿy«víÚµ"}�­¡Ž†é˜üÒÄ‘œ<êŽ'YÙî«÷æj�FLΕæ\ýÖ!í�ÚÖ§¡üÌAD"¿ºÚvÅ”íÚsBg2•Ÿøp7Çî?˜çà£bÜ¥‹ZÎÄcSg2.kõöZŽ+/>Slo:ŽªI°gâ´Åû2ËгÓñ ¦ÒcŸ+Ðò6i#nFÀÜÒs²\Š»–¿ÃÞYû^æ'3ÛLÅß»xðØ»ÙʵÏ-`êÄ™“e}±vÛíç¶vú.üsí‚Ý|ÎÄôeÄó­ÂØIse®]±pþÌeOúý+óêVl]»}:ÆædlŽ�h±ä�Ú¶P_¦Ž¾o\ø{[—ÏßÊÆÍœ¬ÎÊzîOÁ›×ßurÓ{»§DOI@g‘6"à^d---�”»êÑ“wLœNkl’3ou FÑ&g£NgRkÔ8âtœB£6ïš¿ü»Œ÷×ÄštF¹ZÝž�/ÆYŒLaNB¼4™ä7�í—ÔÈ阂¿�‘ã,.¶Ÿ™R‰€ àÉ;½ØU�Ê'Wk": z@ š¶~„kÆÆ:ÆÊô&†Ìƒ,äe™¤€ÚÌ}>‡Q¨5Â9ih#îMÀ²‚»¨¤êÛî_µjz$©ÃE¼é6îE@Éegg÷Ô­ÒÒâž^âÊüÓ§Owåíè^}‡€8’¯´´´[†ÚG}déK'OÄ´ÌÖÇ÷{ÔçïˬD“Ü­ ÑQWøÖöºûÞÑ3å»ÏJœy¹îß�r�>N€$×Ç+¹ïj$9W§ûõq$¹>^È}WøËYô½î†®ººš«çššš¼½½Õ~êààà À ¥R)“Éœñ²²ª²R[Y¥µX#Ýj.4$4,$,,4Ìãt-�š€È’knn®ÑÕ\º|éȱ#?œýáÚõk£A©Pö‹è—4:iêä©1c‚4A^^½�®ÐÛê·VŸ+9×Íð¸áKŸ]J’뀅Ý�€È’«ãêŠ/¿³ù�‚sM¦&_…¯·—·Î¨»^y½ð|a~AþÂù G €ï“ÚÙ„†�Ä+Ä7èíü�ç‘‘ÍŽJ"nL@dÉi«µû?ÝÿCþþjÿÔÔÔQ#G¡IÉq\~Qþ�ÏœÎ?½ÿŸû##"J®5‚W—ñ zCD63[¡ai>¤"àžÄ–\�6¯ Í˹ÌMýMjDx„¯�oCcÃÈá#¡½�Û6âluMõàAƒíâ"NÙvÕ:ä‡À ·I&uH§C"àæD–“è¼ù©üâcã#Â"°ÿår9´74N¥R SSû÷T�`ƒá™ ?]ðññÁ`L `HpH`` Â×êkŽZloKC/¶L(E""KÎßß@ÿèh•”– ‹ U Ê]¯¸^r¡>DGE¨œwrÚ°u4æ#÷8`à”Û§¤LH‰ê…Xj6îh ÅœÁ¼CC/f´#5‘%‡p1kÆ,4 ³g544@u|_®ž+*.Â&:`8‹<Îx�Á‚¶F‹ˆÚÀt:]ùÏåg‹Î^¾zùá=È×?xIpS"K3ocF�ÁÜÀÞOönزA­V }9ŒdFEF=�ú¦ 4š¶o”Ú6ü0)ôâ:ÌX6üBCC'Oœÿòó/Ž}�œê¯Ä�æõrÓ¶IázX‹;uÊÔ“§N–])»Q{Ãl×6ŠšOuرŒ¢NÑ!—€È’C˜‚®jëjç¥ÏK�™ � Ë„á ½ÜºsëG¿ÀÈ~ÿH^r¶ ?´'ßpªGsn*¥*$(ã¢õúzSÛÏìñ”l£(Ÿjo³Œ¢öÎS�€È’«¯¯GlA3/þ¶xL XN éU†P|,β]Ÿ%´'{4çƒUÕU�¦Æà ` ®ˆÆ†  ˆ,9L `Èá˾ÐÅ2G9LÿXŒI9~ÚÀ U 7ˆ_¦EwQ Q(èÑ!¢þté§ì/³1|b¹®Å6Š:HÃ'ŽÈPºèD–âÌè£1^ÿážñF�-,øÂ¢,øÂjæÄ„DL[÷Ú 4\wîÙ‰uÒC ÖCr˜!@cõû¾¸cÒ–ÆíFÑ^ßš.$¢Yr¨ñèÂ].¿ŒEÌÐÆ¾ƒû 3 RbI Z•ÐÎB–½.:ºjˆi˜j‡è –1ÏŽDŒ©<˜úà¸1ã,£\¯ïBéˆ,9ŒO¢ÏöÜÂç=ŒÈ†ö¤±ÁˆÙ³¸Ø¸ÄQ‰Ó~= _ÞAGþ �‡^Î =:Ûl�VÚýiÅj/,¡nni ��‰�’2ó�øŠZ¶¶WQ p"K1 �Cˆýûõç¿¢ª¿ùU *b§:ùŠªÐûóÜZR˜Ÿ�<½D¬±DäD ƒž¡CDN¬5s¦—ØáFtH$" ²äPJ(ʯuÊǞºËÞD…‰8üï©eÊOÜ„@ï¿�í&P1ˆ€g ÉyÖûE¥õx$9� ÉÏ"@’ó¬÷‹JëñHrÿ’žE€$çYï•Öã �ä<þ-$<‹IγÞ/*­Çm*¼Ãc=Œµôü4ktÔ{âHîÖ~Ì/=´÷õ‹®´!@ K$”@¤$@’“’.Ù&6Hr6H(�HI€$'%]²Ml�äl�P�’INJºd›Ø ÉÙ ¡" %’œ”tÉ6°!@’³AB D@J$9)é’m"`C€$gƒ„ˆ€”Ä‘\ii) I¯D /pR�2üd'&°‚žõv‡N»ðÑ“œœl÷”8QήiJ$DÀ–IΖ ¥ �ä$„K¦‰€-’œ-J!ç[á�L{�šš�N‡Çk655u>2ç¶ná¡x0á„§Ö‰ÿô ’œÛ¾õV°ÊÊÊ+W®|óÍ7W¯^åŸø§Ñtòˆ%wö Ÿ7nÜÀ£ç �’’&b�Ir"Â컦JJJ>ùäˆmêÔ©³fÍ8p $‡'Ÿy"<'�úòåËùùù;v|п?x/–/4/'ɾkçìÙ³Û¶mKHH¸÷¾{c‡ÄÞJ „�’¢¢¢G}tÔ¨QÝw­“y9ŠrÝÇH9;@@Àb‰M›6Í�;wÆŒ¾ _ƒÁ€ö¤ÌKÆZ˜ ÿz9¢FJ Ehh(¦°°¡^š»@Ø)=}˜±WG‡ 9f…èÚW+û|afNáø°š÷祌_pá¯3ôºKÛÿ¼‘{5óÔ©ç×�ý;‚‡þtçS‹‡oÿtÚ¶žþ7�wã¹òcßï‡-;9Çhju Œ†Ö;5Vä úÆÆŠ•cf+Þ;ziFÐG _Ø�´ñ¾¡å0�Ë9RÊ1Ÿö$tЊŠ.þãƒi ý¾×~7ª€ƒ8‰q ¯f&xOá/¼Æ)g¢I®�:ýí!„5 ' `B�Pñ*7ÉØ/^‹VfM‡þ>bÙ�_ÇhÓ<šñ—W§}zåµ»å¦:¶`Ǻ¿fÌ7‚Õ*åreXt ‰ŒˆÔ 'ó–˼¼yk¶9åroˆ½õ,cr¥Œ5zËÙµ³g,åË÷לQb‡9나¸ªò+Z¦h/¼¨¬âÇ{.j¹&¹\Ñ‚±^up ­eŒ¥ m‰õ(hRÂëö‹zù×H/�Ðe}Žª#6¥R‰º(HNPĆ 55:y ûÏ «ÿ0ÙjåF£^Ïd*2™·—’1´9½›¼d#û…·&¶@ª­í8/ìÀP{N¤b×nN¨]&S 9½LEüåh Âþ¬ûÓ&²ÆÔÔ层�ö÷I�2ç)íÂß"Ó�Âñûÿñô”_ð¯Ð|AûxU©TÂ)ëK{vD}¹žñ¢ÜÔK|êã�7^ í‚ Òñ–8s!+œ3÷�ª&>gWxô赦€I¿�W¸|G‰iÆ#[^disGøÈ½±®‘¿›·Œ!œùø¨Â£Ø×u�B¢9ƒyÇ"'yaÎyNfÌÝüìrÆüäò€¸qÏ3–SÒ8ÃÉIQÁ*ÁN'¯±i›ö<7PxË ž ‡è š½v¦�䜡×w¯EåCÇí1Œ €*¥emæUñ¯—]6øïL€^P䀡w®øòàk;_(�ƒ´˜ù¥/|ó?B *‚X›F;ùÝ€”ßÿ…­º'4ô©£�R…N¿ÔØ^NŸQwÿG§‹†FƬª¼ã©áò!/ˆ|ùžHÜ'4ò±�,‹gw?¸»ºô1pá5|wæ�§_ÎÐë»×¢}±�°"@’³ÂAD@j]/øÂòp© Aö‰@ß!ÐÅoŸôä)p jXº†3Ý…´ ÉQU .%@’s)nº ÉQ .%@’s)nº ÉQ .%ðÿåÍ¥CN/IEND®B`‚django-simple-captcha-0.7.0/docs/_static/random_chars.png000066400000000000000000000177101523260570200234040ustar00rootroot00000000000000‰PNG  IHDR0‘Ë®s[oiCCPICC Profilexí—{4T{Ç÷9Ì yRI5ò(Ly¥¢"„ŠƒÑãQŒif�I¥wòH¢šèNÉ#º)¤ôBé¡B/• ½‹¨\æþ&w´î­ûÏý/{­³~Ÿ³Ï÷·¿söù­½6€R6�Íà œÅãÐlÈ^ÞËȤfÀ€²€ƒ �Áe[S©NHò ë¹�Ôȧ‰c�_çcï`R&ääkŸmö+üÅ$‰[‘ƒÀ(È¡<ÈóÅ0ÈîbŽæ±yH"fF=q,b Ç�f‹ø(bÅàA.sÀ ׈9Š,žÛ @Tf†²Ho[2¹ ôX¼® —Ž8q}xxНä…x*ƒÍAs•âk‹¿ ‘ñfÌÕÀ³~ú|EEÞc[út¡í J³úºh?¾¦v“dj"Ž˜¼ á‰HÔ¥‹ö– п[$êˉú�H=¸ƈäDýÐ"5và¿îßùŸR(9âËÂD°„`È€&l%ÕB0&öȼ–¡°DéਯªsÔÖŒMT_§a5áé¤õšc&Óž§Ó0…©‡é )ÖÓÚ ãŒÇšdÌP6‹žÙ0{Œ¹‡Eüœ’¹O-IVó]­cmNÚ~°›mŸêðfe±»c¤SÂ’ÎB—LjºkmƒÛZw¡GÕÒçž=ÞØ2i̧×÷Íò'~·ü¯¬(¡ä3N–1ë‚:BäB)+ Wi…©†Â{Xí-ìë«Ïq ¸Bž R%ŒÎ_SSÌÏ[›°Ž²ž¸þã†çq�«6•lÎÛ²ëÎmüí«v,�wÙi“`š¨“¤–LLîÝÕ™Ò´»$5z�E)íIzÕÞŠ}W÷?ôe(gôeVÜö‡«PK8pèÙáëYeGò³÷çÄçòóBŽºæÏ86òXÛñ¢« L ±Â΢–“uVœ**>t:õÌæ’ÈRŸ2ƒ²wg‹ÊùçœÏëUÈWt_x~±ñRåå’+Ù•{ªb«ý¯Z\S¸Ö\#¨õ¹nP§R/]ßs££¡åfÝ­ó· ïwÝ�½G¿oÙ¤ÜÔÚ|´%ò�ýCò#üчÇOŸÜn­zzæYîó}mñ/ø/Ã^9·+¶gvuÔv²_OzÝô&í­ë;…wõï >d|Üù‰÷Ù«kf·|÷ý/‚ž¥½J½µ_㿹|×øÞÕw÷¯òþœ�="oú;‡ó?œÿáóÿûžÿÁÚ'® DT¿²·x´8^,Ð�  ‚êUÀÝpSÓ¡KR?|!’àTÀxß0EL3Ãb¾Û‚ °B¬{€}ÂeqM|î‰sñT¼o¤t¥\¤b¤r¤îHƒ´¡´¿tŠôeé.‚Á‹�H¸Dè&êˆÄ{$�”Nz(£-³Z抬š,K¶vÄ”ÛG¼–s‘;#?^>Nþ…‚½Bž"A‘¡X­d¤”=rüÈý£ÔGe)+W«,Wù¦ºw´ÆèÃjjåc¨cÞŽM7kÜKõ}ã5p�ò œ‰zOJ"[�Û4ã'OŸÜ¬•©½RÇBWA÷Ù”Ò©»ôVêÛh PîNË�m¸ÐHÓ˜dÜoª8ÃÈÌdæÔYäÙêæjªsT玞§nI¶Ò�ohmncoë± Øn­}ŠCîÂúņŽINMÎò.úÔY®437}w+�O²W‚÷+ß5ËKýºW˜Ñ#J"&-¨8D'´`•SØG–[D>{€ãÁ-ˆ”‰ Œ®ŒÑâgÆ.]?zÃÅ�ž›Z·øm½·}ᎊ�Æ IÉ%)λû÷T§çïK8�iu°Vè}¨#+&[.'+Ïâèãci'…Ž'ç�Ò;�Ÿ©)åŸÕ*¿yžAïâƒËI•ó«z®¯É­Kkð¸u£QãžSnKçÚÇé­áϨmv/ií›:Þš¿ÏýøµËô µ×ë›[Ÿm¿Ù�ú1œÿáüŸÿßöüöKâú„Ð%tÔ;ý¿)‰©„"˳œ]Ð(ƒ®*n”›�„ƒBíI8�¾ÀQÂü[g qìi^I_L•0“åá&a6ÏfHÃäÚ ùù!îžͪÇ!M sÁÐXaÎNM(o‘¸Gý±O…%@Æ`‰¼@ �‰ú[€ŠÏ~âá_Æc®Aý%€m;†Â#[£šI!/b1¦SÈ&FF³áoTÄ*­Ÿ‹�Œ pHYs  šœÿIDATxí� XTåºÇ¿��aîW¹ˆŠ bæõ˜í,·˜ÑŲrŸvi»Lëtt?åN;zôxzÚZí2É+¥–úèVÓ3,/€ ¢¢ŒÎ03Ìç¿X0ŽÃ0 :k†aÞÅ<ÃZßúnïo­ÿ¼ßmf‰šššmD€t n]£T "@8$Hºˆ@"@‚ìBƒªBH�t�.D€Ù….U…� é ]ˆ ² ] ª  AÒ=@º±•uÉÏÏ·2&E#DÀ˜@jjªñ¡å}k‰\âãã-çEg‰0!PZZjbù�š¬–ùÐY"`W$H»â¦Âˆ€e$HË|è,°+¤]qSaDÀ2¤e>t–Ø• Ò®¸©0"`™ Ò2:KìJ€iWÜT°L€i™�%v%@‚´+n*ŒX&@‚´Ì‡Î» AÚ7F, AZæCg‰€] � 튛 #– � -ó¡³DÀ®H�vÅM…ËH�–ùÐY"`W$H»â¦Âˆ€e$HË|è,°+Á©UɯËår…J«·µAÚ“{wä_×Ú:[Ê�8ž@'~äÊêÊ*¯ûûßwŸº?&+{åTÙíã{ÜÓŸ^›£\865Lr�Qr"ÐÕØ\�ªïÍ^_"ËzkåääH¦Uݨ,+¾"îT1åß/™`ÈÆ•·+8óì\–] ;Õ‡˜'Ð)¥˜ÏÂ8T[~p} ˘»|jj.ö�ŒOŽŒÇž6oÓŠUßrnS–0éíÏ'ø3mùÞyËOffÆnÿx{ciY _Ÿš¬-Ù1=¢�zæñí™KV?ž +ÏÛñá?rÊU\~YK²§&0OÆêo�߱䃜SªÐ¤Ì… �”°ëù[/çòa,&káNMbLµgÉœ#‰o¿‡4´g `ã>¤ªª’±Ðq)Íj¼Ã~½šÅÍ[òÁKþì]²ûŸ'*pR¯©­ª:þñLJÆÏ[øç) Çs–nÈ—Ëb‡e%¡y›6wáüŒp‰¢`ÓüU9ê!YK>Xùμ¹‰!œ×ôf»W-Ý’ùÖÜ)U§¶ï>«@ òFýè¹ï¬^½,+©<çÓ}ÍúûÇ ìâuGEè€ta6ö�W‹ “š³W6æé§¡A­Š�eû¯bc"›£Éæ}²f8œÙð¸+ûgï?yéùÔää„àœ ±C“%LàÓoûΫS9‰GÆ4'ÑêÔ,tÒ[kžÇB«2×}{èôåç“ýã'> O¬×«Bü»ÊWA2üéÃù]z'Î@ÀÆ‚Œrûv¿ÆÌÀªâÀºï.a2S±OC¹Þ¾-=E±§7ón×35Ð! SU•¡d2 T_ÇFdôoÁëÝò¿"oëÒU\“•‹ÚHÿˆ€s°q“UO¦ÊÙS`BAžŸóñîšylÜš�ýl(»qû´ÔÃ0tSÇên‡ó{^¡ŒÝP¶x½îÎ0mÔñì²­[·.Ίi›‘iÆtLº$ R9úÅ$V¾}ñŠùr­“‘¥ù߯øp¯üæ­æ¦¬¦âä÷ðbÞÊ[­z*ßúMžB¯¯Èûj»Š=44Ž£ä!eª‹er•žIR™ÄT»WlÊ“«T%'KÌM?r2ÖóùéUò’�9åèQ*¸}éá=‡ ä\ž´g `h:Úª²’‰‹>aŸ¬Z›³üxNKž SæÅ¥LLbKW½6›É’&eÄìþת #>›ÑÜÙ¼ð«foçb&e-üc×ÞŒ™“³jñK³&-üìùä™ï<«\¼~ÕKèK2–¹$;!¬y”•;jÙ‚==˜,úѴеëÍZÏÒ&eÈvï~í/�Ùk&_·vû¨èQ‰è¤ÒFœ€€¨©©Éšjâqt�zú•^¥�kÄÌ]æç/iQ½V¡ÐËüe8R)T™¶dë¬E?/Ù¸2^¯ÐŠe²Öx\}0ú£eC|­^/¾}l¾ÊZ•‚I¸â´*•Qbó‘)”Ø�ž~%Ôãè:U{±Ì?Ìd(Zñoé/B–ÈM«S2V®Ö3D6õÔŸq�Z4ô5Û­ˆDæÏŸ“`èˆ6"à„Œïz{W_Öç±eËÆ‡“vì žÊëºl,È}ûöuÖÖÒÒ’Î&±CüñãÇÛ¡*‚˜°± ‘ûôéÓMÊpºÃmÛ¶×¹³Á5Në:û�bp,�µÔö‚ìl œ"~§úåNa‘m+‰1?Ûfè²¹ÙxÒe9ÚÙp‘Hdç©8û AÚ‡3•B¬"@‚´ E"ö!@‚´g*…XE@ðA�ÆÆFµF­¸©¨©­©««Ó7èÝÝÝeRY`` ¿Ÿ¿ÔKêæfú¡Ð’D¡¨QÜNâ-õ à’`Ç8Iõ�êjy5l 1k4ç†üöšv±»ØËË â%ó–gh6 $ö! ¬ ëuõ�AiY鑼#gŠÎTVUj5Z‰DžØ?qÔˆQñqñÁAX‹ŠßhÙ´õZè‡ORTRTy½R£Ñxzz†…† ì7pä°‘}âû@x^’–¯C�+>\�Ä ^]Ю ›ãœ=w–/#"È080x@€ c&$ôI€˜ñ1ÑZúOF@@Aêtºk•×~:öÓæm›¯]¿†E³n"7­N{£æÄ•>5ý)h,"<§À ¾¾þ굫�üjçW�%kbb±îK¯Ö_¼t±üryÞñ¼i�N?z|td4Ï ‚ç•fìMpòqŠÏCxÐ?j¢V«Ïל?ÿûù3gÏÌ™='%)%À?À$ûJ�¸éå5ò£Ç�~’ý Ú«½b{�Éß+�Õ:uÝ…² ,½XгpM£ÓGÃ6±&¸»C¹‡Ön\‹Æm¿„~Ã2b{ÆúÈ|ôzýõêëEÅE¿�ù 2~ÿÝü Ô/Ú¿oÔ u(>W¼ãûçKϷ绸˜8¤ýo>*±-¡©Ñj.^¾˜óuŽB¡vÿ°YOΊ�‰ ð €'„çLINš24{sö±ücˆ¹úùùA'eåeÛvnƒZÆ=8OÀ úùú!IcS#|Úð´áˆ€Ö¬¡½ÚÖ !ð�P#2 ‚+FXâ)Yóù(³¶¶–ÅZHJ§ˆ€�%È›·nÂ=V\«ˆŽŠžùÄLô}||øél´B1 âãí3sÆÌ+W¯àuôç£1Ñ1M�MØ�'ìßwÆÔèày{·þ>cègúúúÂËÝßý÷8-ŽäèÇ¢mCÿ:½ÎN¼©"`‘€é§ÅÈ�8‰1UŒâ464>�þ×ìlU#Ÿ$�øLŒë b">ÆT ‹ á¾Ò‡¦GED«‘O…þ'&^¾>¾�¨J›¨(?f€æñÆÈ-ôÙ&ÊCªð‹W+à 1(ŠîY[Ÿ†„Ã")â£yµò*&$à¸ü}[¾Ù$m'-Ðø„«loLµ=Іá^ñlÉÙ]»wyxz¤ Iƒ·l/…{J�˜ðPª” …êÚëòÁ/á,â &<©ªSA¨~>~˜–0Pà'6 “GWÐÂ$‡!¡ÉŸ7ÊÊštõ: 5á³ }XúCãÂl¤Id:$! ” ïŦ½äðrÐÎZ˜äh/­!�de�½\è059ãIXl`8K;DÀ�„$Æ`0]Ù`”#®>bŸ¶F¢#W«¨ÅºÄÄP*tˆL–ܼyËà¾ø$üt¯@øI~@ÛÜ: áóé×§&Q.W\F{µäBɾƒû IL„b½N‡9P" 4¡)“É"{Db�͹Òsi)i¸ÝMº‘èÈA�8 y &âÃ/bçzÕuÌR¦)Òàµø$è+wÑæ¼;(ü´FŒÐ^íÕsåê•e—ÊöØÛ#¼‡¡¸»ËœR›j”­ÁA¹¹»>z‹l”J%h¨1ör±ü"Î"b">^‰Ñ¥D å ciœÄ�öw røÞ À þ ý{ø1‡µD—®\‚¿Çœ)9¸wB “#ÒFÀãá^ßðÕ†‚⬞Ãä>ü!ÖšÃs"áh:b†1ùù ¸¯ˆð†/7œ.<�)JÈIàÓоł;ÌŽ`àçÞ͆,¡LiöéÕ£¸y¿ä!g!ôïU¥\Š€PMVŒ¬ÆFÇfMËúèó�Žýr¬ªªêÁQöŽë�–!¦°ÚæÀáX:‡9À¬?fÅDÅð#±Xðä´'?Z÷Ö¬B�Ã3 ŒÄbyRaRÎúDˆÉT$z˜ÆÃ°†KÈO��wÐËEKÓ¤h!ÿ”÷Óè‘£kwŒãÐ>°3¡ „f!\œÛ–í[ʯ”¯ÏYï!ö@£8Xƒð�X\Žqˆ‰CXŽùÀCG0CLNnݱÍK$Á†ü†<™�f÷*‘Ð0¥a‚�Ÿ 1 4b)œäží�øOüvŸ XRk8K;DÀþ„$,ÁÀ)\ИQc°Rž­åëWZ-¼ÿõ+x'þëWˆÉ[Î% ë�µæXí�ûsnAQd‰V+¤—ˆ¥­ƒÆ"Xì@Ã|~¨Æ28CìÇÄlgTdÔ”ISöÿ¸Ÿ›M¡ß©1¦CûŽ   aš…á¡áèöŠé%¯•£Ù ‡‰ö'®AAþþf¾  MÂM! Z¼è×aÂkDL„%5¾2_tü°¢’†Dy\Æ“"mB�ˆ€p,$À;¿oˆ†L�áı‡¥ÃòZÔÖpŠvˆ€C+H˜„Ö&æ<ðŠŒàŸÐÚ±™Ð‰´yƒ#í0¶É¤H{ñMš¸†høÉø[¼ !´CH@¨QVšDEç%@‚tÞkG5ï†H�Ýð¢’IÎK€é¼×ŽjÞ � »áE%“œ— Òy¯Õ¼ AvËJ&9/¤S^;Zï”—ÍŠJÛ~a€ÉÓN­¨ƒD¡ç:ÁEêU´± »å“ÀéÙÀÝâVw#¨Éê׉jé"H�.r¡ÉLç @‚tŽëDµt$H¹Ðd¦s A:Çu¢Zº¤‹\h2Ó9� �ã:Q-]„ ÒE.4™éH�Îq�¨–.B€é"šÌt$Hç¸NTK!`cA–––½W Äg„ÈÊ/òàë´ÆZˆ @yvoø`JMMµÞF{Hë ¦˜D€´%@‚lË„Bˆ€Ã� †ž &m � Û2¡"à06þŇÙAw1µµµ …JÂ㕬8ìb0šÚª“J}}|¼œâ¶C•‡9ØihÐÁ W¹sçNìxz ò¼m§lÜ[£PL;À8jnn.4‰‘¼kµ„`ÓþÕ­žåÛ;662,ÈÏûñüÚæÓˆÑÉWþJoï¥y�Mu—ñaoÞ5j ” 1tŒ!+„ÁÖ)>ª„0œò´ Ü£jµíÕÁƒcn@«Ñbª^ÅÝ]Œ”á&ªüïÈÔ÷ç:÷frˆ—FY]|º,ÄÃúûY³ù©_ŽÏÝ5k rë5ýç\}˜õ‰ïÂH�RéåツM¢&†+k‚,ëëë!HìÀFô!ýõW´d2™ÁÿßEAf“�‡4‹…;A@¥Rݺu ß=à(b¸wUÊ:~ƒV«N|û>c›7Íï+k¬S×5º{÷½o ¿®®N-?òÅ_ðšüfvI BêNmY0gõÎmï?Á…g­¦›¾ˆKŽf&òŸüØô‘~L÷È#‹Ââù|Z¯�,=óOé­üÿ"ý®Â¡ÿüú•a¨?oÞ¡FØ‚¾+öñ.•JùSw&µÁõ!mÑ•³À] ��wt9¡´npDÇ{HÒ¤—XaæŒå…7¸˜ ªÂ¬lðù‡g m>§E˜öÀo²é3úzˆÝ±~”KˆÍ]Äà =<¤¡‘ì'¥Ž4D0ìÅä’æ«DÚüìW1æ-ûöM{�±Üsº�„IMŽ ”òùXx�Ÿ¾î›×2•7Þ`ˆŽ±Áj›_z¤Í‘ºV†¸5Ñ¡BKã:°·¬ñ½ÎJÞ/ýqaÜÿŽMŒBï+<ª÷ØÅÇ ¯^ÓÞûò�ÒQ±‹�UúÆÑÿ™­H¥p€-9H1BËíú¦Ïü[öppðËZ/):sÜ‚os1==ôï‰?Ìí»¬zôˉ|ò^oì ûáp”þÜ—Œ«gv?0"¬jy5ÂF çÀ^X Ûm~±iéœÍ‘ºV†h¹AŠÇ�Ç{ZZZhh¨¡3 —†»VYS¥Ö3±Ô7Ð Ô–M¯Qjôb,h hç?†PÐ-´fA—#k“¡^‰P±W§–$À4Ô�UÆᢪªª'N ‰c}}} ÖµSoî÷¥àšÛ;Û6œú�m™PH'àŽD‡ ‹È�9RQQ�ïCðN’oºgh|ܼïáOØñ_Öq¤ææsô´®œ¶…ÔÈÏF^»v šÄòXÝ¡ÛæÖa ²CD¡P`�=úöí[\\Œïv`…€¡u×AJg8�±T“_ŠP^^^TTÔ»woØ …¨¾ ™ QQʳË€£À´G¿~ýй’¸¬½è~uuêÔ)4Vƒ‚‚`)ìÂ=Â:¤£.q·*n�ÕáÇcíõ±æ w-¶èèht)�×TL6âËÖ%%%pþ°bÀ€høùùñnS»H�BPuÅ@2/`). Note that this makes the image slightly more readable on e.g. HiDPI screens, but could also make the CAPTCHA potentially easier to break by a bot. Defaults to: True CAPTCHA_ANIMATED ---------------- Renders an animated CAPTCHA, each frame containing an individual character from the challenge. By default returns an AVIF animation if the installed Pillow version supports it, otherwise falls back to animated GIFs. Defaults to: False Rendering +++++++++ ``CaptchaTextInput`` supports the widget rendering using template introduced in Django 1.11. To change the output HTML, change the ``template_name`` to a custom template or modify ``get_context`` method to provide further context. See https://docs.djangoproject.com/en/dev/ref/forms/renderers/ for description of rendering API. Keep in mind that ``CaptchaTextInput`` is a subclass of ``MultiWidget`` which affects the context, see https://docs.djangoproject.com/en/2.0/ref/forms/widgets/#multiwidget. For example, you would:: class CustomCaptchaTextInput(CaptchaTextInput): template_name = 'custom_field.html' class CaptchaForm(forms.Form): captcha = CaptchaField(widget=CustomCaptchaTextInput) And then have a ``custom_field.html`` template:: {% load i18n %} {% spaceless %} {% endspaceless %} .. note:: For this to work, you MUST add ``django.forms`` to your ``INSTALLED_APPS`` and set ``FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'`` in your settings.py. (See here_ for an explanation) .. _here: https://docs.djangoproject.com/en/2.0/ref/forms/renderers/#django.forms.renderers.TemplatesSetting Context ------- The following context variables are passed to the three "individual" templates: * ``image``: The URL of the rendered CAPTCHA image * ``name``: name of the field (i.e. the name of your form field) * ``key``: the hashed value (identifier) of this CAPTCHA: this is stored and passed in the hidden input * ``id``: the HTML ``id`` attribute to be used The ``captcha/field.html`` template receives the following context: * ``image``: the rendered (HTML) image and optionally audio elements * ``hidden_field``: the rendered hidden input * ``text_field``: the rendered text input Note: these elements have been marked as safe, you can render them straight into your template. .. _generators_ref: Generators and modifiers ++++++++++++++++++++++++ Random chars ------------ .. image:: _static/random_chars.png Classic captcha that picks four random chars. This is case insensitive. :: CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' Simple Math ------------ .. image:: _static/math.png Another classic, that challenges the user to resolve a simple math challenge by randomly picking two numbers between one and nine, and a random operator among plus, minus, times. :: CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' Dictionary Word ---------------- .. image:: _static/dict.png Picks a random word from a dictionary file. Note, you must define ``CAPTCHA_WORDS_DICTIONARY`` in your configuration to use this generator. :: CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.word_challenge' Roll your own ------------- To have your own challenge generator, simply point ``CAPTCHA_CHALLENGE_FUNCT`` to a function that returns a tuple of strings: the first one (the challenge) will be rendered in the captcha, the second is the valid response to the challenge, e.g. ``('5+10=', '15')``, ``('AAAA', 'aaaa')`` This sample generator that returns six random digits:: import random def random_digit_challenge(): ret = u'' for i in range(6): ret += str(random.randint(0,9)) return ret, ret django-simple-captcha-0.7.0/docs/changes.rst000077700000000000000000000000001523260570200221452../CHANGESustar00rootroot00000000000000django-simple-captcha-0.7.0/docs/conf.py000066400000000000000000000146061523260570200201100ustar00rootroot00000000000000# Django Simple Captcha documentation build configuration file, created by # sphinx-quickstart on Sun Jul 10 12:35:54 2011. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # import sys # import os # sys.path.insert(0, '..') # import captcha # print captcha.get_version() # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.append(os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8' # The master toctree document. master_doc = "index" # General information about the project. project = "Django Simple Captcha" copyright = "2011-2026 Marco Bonetti and contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = "0.7.0" # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. # unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = "sphinx_book_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_use_modindex = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = "DjangoSimpleCaptchadoc" # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, # documentclass [howto/manual]). latex_documents = [ ( "index", "DjangoSimpleCaptcha.tex", "Django Simple Captcha Documentation", "Marco Bonetti", "manual", ) ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_use_modindex = True django-simple-captcha-0.7.0/docs/index.rst000066400000000000000000000017141523260570200204460ustar00rootroot00000000000000********************* Django Simple Captcha ********************* .. image:: https://travis-ci.org/mbi/django-simple-captcha.png?branch=master Django Simple Captcha is an extremely simple, yet highly customizable Django application to add captcha images to any Django form. .. image:: _static/captcha3.png Features ++++++++ * Very simple to setup and deploy, yet very configurable * Can use custom challenges (e.g. random chars, simple maths, dictionary word, ...) * Custom generators, noise and filter functions alter the look of the generated image * Supports text-to-speech audio output of the challenge text, for improved accessibility * Ajax refresh Requirements ++++++++++++ * Django 4.2+, Python3.9+ * A recent version of Pillow compiled with FreeType support * Flite is required for text-to-speech (audio) output, but not mandatory ****************** Contents: ****************** .. toctree:: :maxdepth: 2 usage.rst advanced.rst changes.rst django-simple-captcha-0.7.0/docs/requirements.txt000066400000000000000000000000221523260570200220600ustar00rootroot00000000000000sphinx-book-theme django-simple-captcha-0.7.0/docs/usage.rst000066400000000000000000000134011523260570200204370ustar00rootroot00000000000000Using django-simple-captcha =========================== Installation +++++++++++++ 1. Install ``django-simple-captcha`` via pip_: ``pip install django-simple-captcha`` 2. Add ``captcha`` to the ``INSTALLED_APPS`` in your ``settings.py`` 3. Run ``python manage.py migrate`` 4. Add an entry to your ``urls.py`` (import ``include`` from ``django.urls``):: urlpatterns += [ path('captcha/', include('captcha.urls')), ] .. _pip: http://pypi.python.org/pypi/pip Note: Pillow requires that image libraries are installed on your system. On e.g. Debian or Ubuntu, you'd need these packages to compile and install Pillow:: apt-get -y install libz-dev libjpeg-dev libfreetype6-dev python-dev Adding to a Form +++++++++++++++++ Using a ``CaptchaField`` is quite straight-forward: Define the Form ---------------- To embed a CAPTCHA in your forms, simply add a ``CaptchaField`` to the form definition:: from django import forms from captcha.fields import CaptchaField class CaptchaTestForm(forms.Form): myfield = AnyOtherField() captcha = CaptchaField() …or, in a ``ModelForm``:: from django import forms from captcha.fields import CaptchaField class CaptchaTestModelForm(forms.ModelForm): captcha = CaptchaField() class Meta: model = MyModel Validate the Form ----------------- In your view, validate the form as usual. If the user didn't provide a valid response to the CAPTCHA challenge, the form will raise a ``ValidationError`` and display an error message to the user:: def some_view(request): if request.POST: form = CaptchaTestForm(request.POST) # Validate the form: the captcha field will automatically # check the input if form.is_valid(): human = True else: form = CaptchaTestForm() return render(request, 'template.html', {'form': form}) Passing arguments to the field ------------------------------ ```` takes a few optional arguments: * ``id_prefix`` Optional prefix that will be added to the ID attribute in the generated fields and labels, to be used when e.g. several Captcha fields are being displayed on a same page. (added in version 0.4.4) * ``generator`` Optional callable or module path to callable that will be used to generate the challenge and the response, e.g. ``generator='path.to.generator_function'`` or ``generator=lambda: ('LOL', 'LOL')``, see also :ref:`generators_ref`. Defaults to whatever is defined in ``settings.CAPTCHA_CHALLENGE_FUNCT``. Example usage for ajax form --------------------------- An example CAPTCHA validation in AJAX:: from django.views.generic.edit import CreateView from captcha.models import CaptchaStore from captcha.helpers import captcha_image_url from django.http import HttpResponse import json class AjaxExampleForm(CreateView): template_name = '' form_class = AjaxForm def form_invalid(self, form): if self.request.is_ajax(): to_json_response = dict() to_json_response['status'] = 0 to_json_response['form_errors'] = form.errors to_json_response['new_cptch_key'] = CaptchaStore.generate_key() to_json_response['new_cptch_image'] = captcha_image_url(to_json_response['new_cptch_key']) return HttpResponse(json.dumps(to_json_response), content_type='application/json') def form_valid(self, form): form.save() if self.request.is_ajax(): to_json_response = dict() to_json_response['status'] = 1 to_json_response['new_cptch_key'] = CaptchaStore.generate_key() to_json_response['new_cptch_image'] = captcha_image_url(to_json_response['new_cptch_key']) return HttpResponse(json.dumps(to_json_response), content_type='application/json') And in javascript your must update the image and hidden input in form Example usage ajax refresh button --------------------------------- # html::
{{ form }}
# javascript:: $('.js-captcha-refresh').click(function(){ $form = $(this).parents('form'); $.getJSON($(this).data('url'), {}, function(json) { // This should update your captcha image src and captcha hidden input }); return false; }); Example usage ajax refresh --------------------------------- # javascript:: $('.captcha').click(function () { $.getJSON("/captcha/refresh/", function (result) { $('.captcha').attr('src', result['image_url']); $('#id_captcha_0').val(result['key']) }); }); Example usage in Django REST Framework --------------------------------- To use CAPTCHA in a serializer, simply have your serializer inherit from ``CaptchaSerializer``:: from captcha.serializers import CaptchaSerializer from rest_framework import serializers class CheckCaptchaSerializer(CaptchaSerializer): email = serializers.EmailField() …or use ``CaptchaModelSerializer``:: from captcha.serializers import CaptchaModelSerializer from rest_framework import serializers class CheckCaptchaModelSerializer(CaptchaModelSerializer): sender = serializers.EmailField() class Meta: model = User fields = ("captcha_code", "captcha_hashkey", "sender") ``CaptchaSerializer`` and ``CaptchaModelSerializer`` implement the ``captcha_code`` and ``captcha_hashkey`` fields. In case of invalid captcha or hash code an exception is raised:: raise exceptions.ValidationError({"error": gettext_lazy("Invalid CAPTCHA")}) django-simple-captcha-0.7.0/pyproject.toml000066400000000000000000000013571523260570200205740ustar00rootroot00000000000000[tool.isort] profile = "black" lines_after_imports = 2 multi_line_output = 3 order_by_type = true known_django = "django" known_django_third_party = "django_*" known_first_party = "apps,captcha" sections = "FUTURE,STDLIB,THIRDPARTY,DJANGO,DJANGO_THIRD_PARTY,FIRSTPARTY,LOCALFOLDER" skip_glob= "**/migrations/**" [tool.black] line-length = 88 target-version = ['py38'] exclude = ''' ( /( \.eggs # exclude a few common directories in the | \.git # root of the project | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist | node_modules )/ | foo.py # also separately exclude a file named foo.py in # the root of the project ) ''' django-simple-captcha-0.7.0/setup.cfg000066400000000000000000000000341523260570200174700ustar00rootroot00000000000000[bdist_wheel] universal = 1 django-simple-captcha-0.7.0/setup.py000066400000000000000000000043321523260570200173660ustar00rootroot00000000000000import sys from setuptools import find_packages, setup from setuptools.command.test import test as test_command from captcha import get_version as get_captcha_version class Tox(test_command): user_options = [("tox-args=", "a", "Arguments to pass to tox")] def initialize_options(self): test_command.initialize_options(self) self.tox_args = None def finalize_options(self): test_command.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): # import here, cause outside the eggs aren't loaded import shlex import tox args = self.tox_args if args: args = shlex.split(self.tox_args) errno = tox.cmdline(args=args) sys.exit(errno) install_requires = [ "Django >= 4.2", "Pillow >=6.2.0", "django-ranged-response == 0.2.0", ] EXTRAS_REQUIRE = {"test": ("testfixtures",)} with open("README.rst") as readme: long_description = readme.read() setup( name="django-simple-captcha", version=get_captcha_version(), description="A very simple, yet powerful, Django captcha application", long_description=long_description, author="Marco Bonetti", author_email="mbonetti@gmail.com", url="https://github.com/mbi/django-simple-captcha", license="MIT", packages=find_packages(exclude=["testproject", "testproject.*"]), classifiers=[ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Security", "Topic :: Internet :: WWW/HTTP", "Framework :: Django", ], include_package_data=True, zip_safe=False, install_requires=install_requires, extras_require=EXTRAS_REQUIRE, tests_require=["tox>=4.31", "tox-uv>=1.23"], cmdclass={"test": Tox}, ) django-simple-captcha-0.7.0/testproject/000077500000000000000000000000001523260570200202205ustar00rootroot00000000000000django-simple-captcha-0.7.0/testproject/.coveragerc000066400000000000000000000002511523260570200223370ustar00rootroot00000000000000[run] branch = True source = captcha omit = ../captcha/migrations/* ../captcha/tests/* ../captcha/management/* [report] precision = 2 fail_under = 80.0 django-simple-captcha-0.7.0/testproject/__init__.py000066400000000000000000000000001523260570200223170ustar00rootroot00000000000000django-simple-captcha-0.7.0/testproject/coverage.sh000066400000000000000000000002701523260570200223460ustar00rootroot00000000000000#!/bin/bash export CAPTCHA_FLITE_PATH=`which flite` export CAPTCHA_SOX_PATH=`which sox` coverage run --rcfile .coveragerc manage.py test --failfast captcha coverage xml coverage html django-simple-captcha-0.7.0/testproject/drf_settings.py000066400000000000000000000001351523260570200232640ustar00rootroot00000000000000from testproject.settings import * # noqa INSTALLED_APPS.append("rest_framework") # noqa django-simple-captcha-0.7.0/testproject/forms.py000066400000000000000000000005421523260570200217210ustar00rootroot00000000000000from django import forms from captcha.fields import CaptchaField, CaptchaTextInput class CustomCaptchaTextInput(CaptchaTextInput): template_name = "captcha_test/custom_captcha_field.html" class CaptchaForm(forms.Form): captcha = CaptchaField() class CustomCaptchaForm(forms.Form): captcha = CaptchaField(widget=CustomCaptchaTextInput) django-simple-captcha-0.7.0/testproject/jinja2_settings.py000066400000000000000000000001351523260570200236660ustar00rootroot00000000000000from testproject.settings import * # noqa FORM_RENDERER = "django.forms.renderers.Jinja2" django-simple-captcha-0.7.0/testproject/manage.py000066400000000000000000000017631523260570200220310ustar00rootroot00000000000000#!/usr/bin/env python import os import sys try: from django.core.management import execute_manager OLD_DJANGO = True except ImportError: from django.core.management import execute_from_command_line OLD_DJANGO = False if OLD_DJANGO: try: import settings # Assumed to be in the same directory. except ImportError: sys.stderr.write( "Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__ ) sys.exit(1) BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, BASEDIR) if __name__ == "__main__": os.environ["DJANGO_SETTINGS_MODULE"] = "testproject.settings" if OLD_DJANGO: execute_manager(settings) else: execute_from_command_line(sys.argv) django-simple-captcha-0.7.0/testproject/settings.py000066400000000000000000000042401523260570200224320ustar00rootroot00000000000000import os import sys SITE_ID = 1 PROJECT_PATH = os.path.abspath(os.path.dirname(__file__)) PYTHON_VERSION = "%s.%s" % sys.version_info[:2] DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(PROJECT_PATH, "django-simple-captcha.db"), } } # TEST_DATABASE_CHARSET = "utf8" # TEST_DATABASE_COLLATION = "utf8_general_ci" DATABASE_SUPPORTS_TRANSACTIONS = True INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.admin", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.sites", "django.forms", "django.contrib.messages", # "rest_framework", "captcha", ] FORM_RENDERER = "django.forms.renderers.TemplatesSetting" LANGUAGE_CODE = "en" LANGUAGES = ( ("en", "English"), # ('ja', u('日本語')), ) FIXTURE_DIRS = (os.path.join(PROJECT_PATH, "fixtures"),) ROOT_URLCONF = "testproject.urls" DEBUG = True TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True, "OPTIONS": { "debug": False, "context_processors": ( "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.static", "django.template.context_processors.tz", "django.contrib.messages.context_processors.messages", ), }, "DIRS": ("templates",), } ] USE_TZ = True SECRET_KEY = "empty" MIDDLEWARE = ( "django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", ) CAPTCHA_FLITE_PATH = os.environ.get("CAPTCHA_FLITE_PATH", None) CAPTCHA_SOX_PATH = os.environ.get("CAPTCHA_SOX_PATH", None) CAPTCHA_BACKGROUND_COLOR = "transparent" CAPTCHA_LETTER_COLOR_FUNCT = "captcha.helpers.random_letter_color_challenge" # CAPTCHA_ANIMATED = True django-simple-captcha-0.7.0/testproject/templates/000077500000000000000000000000001523260570200222165ustar00rootroot00000000000000django-simple-captcha-0.7.0/testproject/templates/captcha_test/000077500000000000000000000000001523260570200246605ustar00rootroot00000000000000django-simple-captcha-0.7.0/testproject/templates/captcha_test/custom_captcha_field.html000066400000000000000000000010101523260570200316760ustar00rootroot00000000000000{% load i18n %} {% spaceless %}
{% endspaceless %} django-simple-captcha-0.7.0/testproject/templates/captcha_test/image.html000066400000000000000000000003661523260570200266350ustar00rootroot00000000000000{% load i18n %} {% spaceless %} {% if audio %}{% endif %}captcha-template-test{% if audio %}{% endif %} {% endspaceless %} django-simple-captcha-0.7.0/testproject/templates/captcha_test/image_html5_audio.html000066400000000000000000000007131523260570200311230ustar00rootroot00000000000000{% load i18n %} {% spaceless %} {% if audio %} play audio open audio {% endif %} {% endspaceless %} django-simple-captcha-0.7.0/testproject/templates/home.html000066400000000000000000000023411523260570200240340ustar00rootroot00000000000000 Captcha test
{% csrf_token %} {% if 'ok' in request.GET %}

Correct answer

{% endif %} {{ form.captcha.errors }} {{ form.captcha }}
django-simple-captcha-0.7.0/testproject/urls.py000066400000000000000000000002721523260570200215600ustar00rootroot00000000000000from django.urls import include from django.urls import re_path as url from .views import home urlpatterns = [ url(r"^$", home), url(r"^captcha/", include("captcha.urls")), ] django-simple-captcha-0.7.0/testproject/views.py000066400000000000000000000006221523260570200217270ustar00rootroot00000000000000from forms import CaptchaForm # CustomCaptchaForm from django.http import HttpResponseRedirect from django.shortcuts import render def home(request): if request.POST: form = CaptchaForm(request.POST) if form.is_valid(): return HttpResponseRedirect(request.path + "?ok") else: form = CaptchaForm() return render(request, "home.html", {"form": form}) django-simple-captcha-0.7.0/tox.ini000066400000000000000000000074711523260570200171760ustar00rootroot00000000000000[tox] requires = tox>=4.31 tox-uv>=1.23 envlist = py{39,310,311}-django42, py{310,311,312}-django{50,51}, py{312,313}-django52, py{39,310}-django42-jinja2, py{310,311}-django{42,50}-jinja2, py312-django{50,51}-jinja2, py313-django{52,60,61} py313-django52-drf gettext,flake8,docs,coverage [gh-actions] python = 3.13: py313-django52, py313-django52-drf, py313-django60, py313-django61 3.12: py312-django50, py312-django51, py312-django52, py312-django51-jinja2 3.11: p311-django-51, p311-django-50, p311-django-50-jinjia, p311-django-42, p311-django-42-jinjia 3.10: py310-django42, py310-django42-jinja2, py310-django50, py310-django51, py310-django50-jinja2 3.9: py39-django42, py39-django42-jinja2 [testenv] changedir = testproject commands = python -Wd manage.py test captcha setenv = PYTHONDONTWRITEBYTECODE=1 deps = django42: Django>=4.2a,<4.3 django50: Django>=5.0a,<5.1 django51: Django>=5.1,<5.2 django52: Django>=5.2a1,<5.3 django60: Django>=6.0,<6.1 django61: Django>=6.1a1,<6.2 py{39,310,311,312, 313}-django{42,50,51,52,60,61}: python3-memcached py313-django52-drf: djangorestframework jinja2 Pillow extras = test [testenv:py39-django42-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py310-django42-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py310-django50-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py311-django50-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py312-django50-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py313-django52-drf] commands = python -Wd manage.py test captcha --settings drf_settings [testenv:gettext] basepython = python3.12 changedir = captcha/locale/ allowlist_externals = msgfmt commands = msgfmt -c -o bg/LC_MESSAGES/django.mo bg/LC_MESSAGES/django.po msgfmt -c -o cs/LC_MESSAGES/django.mo cs/LC_MESSAGES/django.po msgfmt -c -o de/LC_MESSAGES/django.mo de/LC_MESSAGES/django.po msgfmt -c -o en/LC_MESSAGES/django.mo en/LC_MESSAGES/django.po msgfmt -c -o es/LC_MESSAGES/django.mo es/LC_MESSAGES/django.po msgfmt -c -o fa/LC_MESSAGES/django.mo fa/LC_MESSAGES/django.po msgfmt -c -o fr/LC_MESSAGES/django.mo fr/LC_MESSAGES/django.po msgfmt -c -o it/LC_MESSAGES/django.mo it/LC_MESSAGES/django.po msgfmt -c -o ja/LC_MESSAGES/django.mo ja/LC_MESSAGES/django.po msgfmt -c -o nl/LC_MESSAGES/django.mo nl/LC_MESSAGES/django.po msgfmt -c -o pl/LC_MESSAGES/django.mo pl/LC_MESSAGES/django.po msgfmt -c -o pt_BR/LC_MESSAGES/django.mo pt_BR/LC_MESSAGES/django.po msgfmt -c -o ru/LC_MESSAGES/django.mo ru/LC_MESSAGES/django.po msgfmt -c -o sk/LC_MESSAGES/django.mo sk/LC_MESSAGES/django.po msgfmt -c -o sv/LC_MESSAGES/django.mo sv/LC_MESSAGES/django.po msgfmt -c -o tr/LC_MESSAGES/django.mo tr/LC_MESSAGES/django.po msgfmt -c -o uk/LC_MESSAGES/django.mo uk/LC_MESSAGES/django.po msgfmt -c -o zh_CN/LC_MESSAGES/django.mo zh_CN/LC_MESSAGES/django.po msgfmt -c -o zh_Hans/LC_MESSAGES/django.mo zh_Hans/LC_MESSAGES/django.po [testenv:flake8] basepython = python3.12 deps = flake8 commands= flake8 {toxinidir}/captcha [testenv:docs] basepython = python3.12 deps = sphinx sphinx-book-theme changedir = docs commands= sphinx-build -b html . _build/html [testenv:coverage] changedir = testproject passenv = CAPTCHA_FLITE_PATH CAPTCHA_SOX_PATH deps = coverage djangorestframework commands= # coverage debug config coverage run --rcfile=.coveragerc manage.py test captcha --settings drf_settings coverage report --rcfile=.coveragerc