Permalink
Browse files

initial!

  • Loading branch information...
0 parents commit 66f95762f43a7d089418ae2d91ea57fe0b890b82 @bryanhelmig bryanhelmig committed Mar 20, 2012
Showing with 211 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +24 −0 LICENSE
  3. +7 −0 requirements.txt
  4. +7 −0 runserver.py
  5. +33 −0 tests.py
  6. +4 −0 validemail/__init__.py
  7. +13 −0 validemail/tests.py
  8. +116 −0 validemail/utils.py
  9. +6 −0 validemail/views.py
@@ -0,0 +1 @@
+*.pyc
24 LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2012, Bryan Helmig
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,7 @@
+Flask==0.8
+Jinja2==2.6
+Werkzeug==0.8.3
+gevent==0.13.6
+greenlet==0.3.4
+pydns==2.3.6
+wsgiref==0.1.2
@@ -0,0 +1,7 @@
+from gevent.wsgi import WSGIServer
+
+from validemail import app
+
+
+http_server = WSGIServer(('', 5000), app)
+http_server.serve_forever()
@@ -0,0 +1,33 @@
+import unittest
+
+from validemail import utils
+
+
+class TestParse(unittest.TestCase):
+ def test_good_email(self):
+ validator = utils.EmailChecker('bryan@bryanhelmig.com')
+ errors = validator.validate()
+
+ self.assertFalse(errors)
+
+ def test_invalid_email(self):
+ validator = utils.EmailChecker('sdahjsdfh.asdofh')
+ errors = validator.validate()
+
+ self.assertTrue(errors)
+
+ def test_invalid_mx_email(self):
+ validator = utils.EmailChecker('bryan@example.com')
+ errors = validator.validate()
+
+ self.assertTrue(errors)
+
+ def test_invalid_domain(self):
+ validator = utils.EmailChecker('bryan@asdahsdfgasdfgyadfiuyadsfguy.com')
+ errors = validator.validate()
+
+ self.assertTrue(errors)
+
+
+if __name__ == '__main__':
+ unittest.main()
@@ -0,0 +1,4 @@
+from flask import Flask
+app = Flask(__name__)
+
+import validemail.views
@@ -0,0 +1,13 @@
+import unittest
+
+from validemail import utils
+
+
+class TestParse(unittest.TestCase):
+ def test_bad_email(self):
+ validator = utils.EmailChecker('bryan@bryanhelmig.com')
+ validator.validate()
+
+
+if __name__ == '__main__':
+ unittest.main()
@@ -0,0 +1,116 @@
+import re
+
+import gevent
+from gevent import monkey
+
+
+def mxlookup(domain):
+ from DNS import Base
+ from DNS.Base import ServerError
+
+ def dnslookup(name, qtype):
+ """convenience routine to return just answer data for any query type"""
+ if Base.defaults['server'] == []: Base.DiscoverNameServers()
+ result = Base.DnsRequest(name=name, qtype=qtype, timout=5).req()
+ if result.header['status'] != 'NOERROR':
+ raise ServerError("DNS query status: %s" % result.header['status'],
+ result.header['rcode'])
+ elif len(result.answers) == 0 and Base.defaults['server_rotate']:
+ # check with next DNS server
+ result = Base.DnsRequest(name=name, qtype=qtype, timout=5).req()
+ if result.header['status'] != 'NOERROR':
+ raise ServerError("DNS query status: %s" % result.header['status'],
+ result.header['rcode'])
+ return [x['data'] for x in result.answers]
+
+ def _mxlookup(name):
+ """
+ convenience routine for doing an MX lookup of a name. returns a
+ sorted list of (preference, mail exchanger) records
+ """
+ l = dnslookup(name, qtype='mx')
+ l.sort()
+ return l
+
+ return _mxlookup(domain)
+
+
+class EmailChecker(object):
+ """
+ Given an email address, run a variety of checks on that email address.
+ """
+
+ def __init__(self, email, _gevent=True):
+ self.email = email
+ self.errors = []
+
+ self._gevent = _gevent
+
+ @property
+ def checks(self):
+ """
+ Collects all functions that start with `check_`.
+ """
+ out = []
+ for name in dir(self):
+ if name.startswith('check_'):
+ out.append(getattr(self, name))
+ return out
+
+ def validate(self):
+ """
+ 1. Run each check, fill up self.jobs.
+ 2. Join all jobs together.
+ 3. Each job returns a list of errors.
+ 4. Condense and return each error.
+ """
+ if self._gevent:
+ monkey.patch_all()
+ self.results = [gevent.spawn(check) for check in self.checks]
+ gevent.joinall(self.results)
+
+ for result in self.results:
+ self.errors += result.value
+ else:
+ self.results = [check() for check in self.checks]
+
+ for result in self.results:
+ self.errors += result
+
+ return self.errors
+
+
+ ############
+ ## CHECKS ##
+ ############
+
+ def check_valid_email_string(self):
+ from django.core.validators import validate_email
+ from django.core.exceptions import ValidationError
+
+ try:
+ validate_email(self.email)
+ return []
+ except:
+ return [dict(
+ severity=10,
+ message='Invalid email address.'
+ )]
+
+ def check_valid_mx_records(self):
+ error = dict(
+ severity=5,
+ message='No MX records found for the domain.'
+ )
+
+ try:
+ domain = self.email.split('@')[1]
+ except IndexError:
+ return [error]
+
+ mx_hosts = mxlookup(domain)
+
+ if len(mx_hosts) == 0:
+ return [error]
+
+ return []
@@ -0,0 +1,6 @@
+from validemail import app, utils
+
+
+@app.route('/v1/check', methods=['GET'])
+def check():
+ return 'Hello World!'

0 comments on commit 66f9576

Please sign in to comment.