Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Write parser/validator for homepage form
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Sep 8, 2017
1 parent 75e8816 commit 41dfd1a
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ git:
addons:
postgresql: 9.6
firefox: latest-esr
env:
- PYTHONDONTWRITEBYTECODE=1
before_install:
- git branch -vv | grep '^*'
- pwd
Expand Down
104 changes: 104 additions & 0 deletions gratipay/homepage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
"""This is the Python library behind gratipay.com.
"""
from __future__ import absolute_import, division, print_function, unicode_literals

from gratipay import utils


def pay_for_open_source(app, raw):
parsed, errors = _parse(raw)
if not errors:
transaction_id = _charge_card(app, parsed)
if not transaction_id:
errors.append('charge_card')
if not errors:
message_id = None
if parsed['email_address']:
message_id = _send_receipt(app, parsed['email_address'])
if not message_id:
errors.append('send_receipt')
_store_info(parsed, transaction_id, message_id)
parsed = {}
return {'parsed': parsed, 'errors': errors}


def _parse(raw):
"""Given a POST request.body, return (parsed<dict>, errors<list>).
"""

errors = []
x = lambda f: raw.get(f, '').strip()

# amount
amount = x('amount') or '0'
if (not amount.isdigit()) or (int(amount) < 50):
errors.append('amount')
amount = ''.join(x for x in amount.split('.')[0] if x.isdigit())

# TODO credit card token?

# name
name = x('name')
if len(name) > 256:
name = name[:256]
errors.append('name')

# email address
email_address = x('email_address')
if email_address and not utils.is_valid_email_address(email_address):
email_address = email_address[:255]
errors.append('email_address')

# follow_up
follow_up = x('follow_up')
if follow_up not in ('monthly', 'quarterly', 'yearly', 'never'):
follow_up = 'monthly'
errors.append('follow_up')

promotion_name = x('promotion_name')
if len(promotion_name) > 32:
promotion_name = promotion_name[:32]
errors.append('promotion_name')

promotion_url = x('promotion_url')
is_link = lambda x: (x.startswith('http://') or x.startswith('https://')) and '.' in x
if len(promotion_url) > 256 or not is_link(promotion_url):
promotion_url = promotion_url[:256]
errors.append('promotion_url')

promotion_twitter = x('promotion_twitter')
if len(promotion_twitter) > 32:
promotion_twitter = promotion_twitter[:32]
# TODO What are Twitter's rules?
errors.append('promotion_twitter')

promotion_message = x('promotion_message')
if len(promotion_message) > 128:
promotion_message = promotion_message[:128]
errors.append('promotion_message')

parsed = { 'amount': amount
, 'name': name
, 'email_address': email_address
, 'follow_up': follow_up
, 'promotion_name': promotion_name
, 'promotion_url': promotion_url
, 'promotion_twitter': promotion_twitter
, 'promotion_message': promotion_message
}
return parsed, errors


def _charge_card(app, parsed):
raise NotImplementedError


def _send_receipt(app, parsed):
raise NotImplementedError
app.email_queue.put()


def _store_info(parsed, transaction_id, message_id):
raise NotImplementedError

6 changes: 6 additions & 0 deletions gratipay/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import fnmatch
import random
import os
import re
from base64 import urlsafe_b64encode, urlsafe_b64decode
from datetime import datetime, timedelta

Expand All @@ -20,6 +21,11 @@
# card is considered as expiring
EXPIRING_DELTA = timedelta(days = 30)

_email_re = re.compile(r'^[^@]+@[^@]+\.[^@]+$')
# exactly one @, and at least one . after @ -- simple validation, send to be sure
def is_valid_email_address(email_address):
return len(email_address) < 255 and _email_re.match(email_address)


def dict_to_querystring(mapping):
if not mapping:
Expand Down
52 changes: 52 additions & 0 deletions tests/py/test_homepage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals

from gratipay.homepage import _parse
from gratipay.testing import Harness


class Parse(Harness):

def test_good_values_survive(self):
parsed, errors = _parse({ 'amount': '1000'
, 'name': 'Alice Liddell'
, 'email_address': '[email protected]'
, 'follow_up': 'monthly'
, 'promotion_name': 'Wonderland'
, 'promotion_url': 'http://www.example.com/'
, 'promotion_twitter': 'thebestbutter'
, 'promotion_message': 'Love me! Love me! Say that you love me!'
})
assert parsed == { 'amount': '1000'
, 'name': 'Alice Liddell'
, 'email_address': '[email protected]'
, 'follow_up': 'monthly'
, 'promotion_name': 'Wonderland'
, 'promotion_url': 'http://www.example.com/'
, 'promotion_twitter': 'thebestbutter'
, 'promotion_message': 'Love me! Love me! Say that you love me!'
}
assert errors == []


def test_bad_values_get_scrubbed_and_flagged(self):
parsed, errors = _parse({ 'amount': '1,000'
, 'name': 'Alice Liddell' * 20
, 'email_address': 'alice' * 100 + '@example.com'
, 'follow_up': 'cheese'
, 'promotion_name': 'Wonderland' * 100
, 'promotion_url': 'http://www.example.com/' + 'cheese' * 100
, 'promotion_twitter': 'thebestbutter' * 10
, 'promotion_message': 'Love me!' * 50
})
assert parsed == { 'amount': '1000'
, 'name': 'Alice Liddell' * 19 + 'Alice Lid'
, 'email_address': 'alice' * 51
, 'follow_up': 'monthly'
, 'promotion_name': 'WonderlandWonderlandWonderlandWo'
, 'promotion_url': 'http://www.example.com/' + 'cheese' * 38 + 'chees'
, 'promotion_twitter': 'thebestbutterthebestbutterthebes'
, 'promotion_message': 'Love me!' * 16
}
assert errors == ['amount', 'name', 'email_address', 'follow_up', 'promotion_name',
'promotion_url', 'promotion_twitter', 'promotion_message']
9 changes: 2 additions & 7 deletions www/~/%username/emails/modify.json.spt
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
"""
Manages the authenticated user's email addresses.
"""
import re

from aspen import Response
from gratipay.utils import get_participant
from gratipay.utils import get_participant, is_valid_email_address
from gratipay.models.package import Package

# exactly one @, and at least one . after @
email_re = re.compile(r'^[^@]+@[^@]+\.[^@]+$')

[-----------------------------------------]

request.allow("POST")
Expand All @@ -20,7 +15,7 @@ address = request.body['address']
show_address_in_message = bool(request.body.get('show_address_in_message', ''))

# Basic checks. The real validation will happen when we send the email.
if (len(address) > 254) or not email_re.match(address):
if not is_valid_email_address(address):
raise Response(400, _("Invalid email address."))

if not participant.email_lang:
Expand Down

0 comments on commit 41dfd1a

Please sign in to comment.