formspree/formspree/forms/helpers.py

165 lines
4.4 KiB
Python

import werkzeug.datastructures
import requests
import hashlib
import hashids
import uuid
import json
from urllib.parse import urljoin, urlparse
from flask import request, g
from formspree import settings
from formspree.stuff import redis_store, DB
from flask import jsonify
from flask_login import current_user
CAPTCHA_URL = 'https://www.google.com/recaptcha/api/siteverify'
CAPTCHA_VAL = 'g-recaptcha-response'
HASH = lambda x, y: hashlib.md5(x.encode('utf-8')+y.encode('utf-8')+settings.NONCE_SECRET).hexdigest()
KEYS_NOT_STORED = {'_gotcha', '_format', '_language', CAPTCHA_VAL, '_host_nonce'}
KEYS_EXCLUDED_FROM_EMAIL = KEYS_NOT_STORED.union({'_subject', '_cc', '_next'})
REDIS_COUNTER_KEY = 'monthly_{form_id}_{month}'.format
REDIS_HOSTNAME_KEY = 'hostname_{nonce}'.format
REDIS_FIRSTSUBMISSION_KEY = 'first_{nonce}'.format
HASHIDS_CODEC = hashids.Hashids(alphabet='abcdefghijklmnopqrstuvwxyz',
min_length=8,
salt=settings.HASHIDS_SALT)
def ordered_storage(f):
'''
By default Flask doesn't maintain order of form arguments, pretty crazy
From: https://gist.github.com/cbsmith/5069769
'''
def decorator(*args, **kwargs):
request.parameter_storage_class = werkzeug.datastructures.ImmutableOrderedMultiDict
return f(*args, **kwargs)
return decorator
def referrer_to_path(r):
if not r:
return ''
parsed = urlparse(r)
n = parsed.netloc + parsed.path
return n
def referrer_to_baseurl(r):
if not r:
return ''
parsed = urlparse(r)
n = parsed.netloc
return n
def http_form_to_dict(data):
'''
Forms are ImmutableMultiDicts,
convert to json-serializable version
'''
ret = {}
ordered_keys = []
for elem in data.items(multi=True):
if not elem[0] in ret.keys():
ret[elem[0]] = []
ordered_keys.append(elem[0])
ret[elem[0]].append(elem[1])
for k, v in ret.items():
ret[k] = ', '.join(v)
return ret, ordered_keys
def remove_www(host):
if host.startswith('www.'):
return host[4:]
return host
def sitewide_file_check(url, email):
if not url.startswith('http://') and not url.startswith('https://'):
url = 'http://' + url
url = urljoin(url, '/formspree-verify.txt')
g.log = g.log.bind(url=url, email=email)
try:
res = requests.get(url, timeout=3, headers={
'User-Agent': 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/55.0.2883.87 Chrome/55.0.2883.87 Safari/537.36'
})
if not res.ok:
g.log.debug('Sitewide file not found.', contents=res.text[:100])
return False
for line in res.text.splitlines():
line = line.strip(u'\xef\xbb\xbf ')
if line == email:
g.log.debug('Email found in sitewide file.')
return True
except requests.exceptions.ConnectionError:
pass
return False
def verify_captcha(form_data, request):
if not CAPTCHA_VAL in form_data:
return False
r = requests.post(CAPTCHA_URL, data={
'secret': settings.RECAPTCHA_SECRET,
'response': form_data[CAPTCHA_VAL],
'remoteip': request.remote_addr,
}, timeout=2)
return r.ok and r.json().get('success')
def assign_ajax(form, sent_using_ajax):
if form.uses_ajax is None:
form.uses_ajax = sent_using_ajax
DB.session.add(form)
DB.session.commit()
def temp_store_hostname(hostname, referrer):
nonce = uuid.uuid4()
key = REDIS_HOSTNAME_KEY(nonce=nonce)
redis_store.set(key, hostname+','+referrer)
redis_store.expire(key, 300000)
return nonce
def get_temp_hostname(nonce):
key = REDIS_HOSTNAME_KEY(nonce=nonce)
value = redis_store.get(key)
if value is None:
raise KeyError("no temp_hostname stored.")
redis_store.delete(key)
values = value.decode('utf-8').split(',')
if len(values) != 2:
raise ValueError("temp_hostname value is invalid: " + value)
else:
return values
def store_first_submission(nonce, data):
key = REDIS_FIRSTSUBMISSION_KEY(nonce=nonce)
redis_store.set(key, json.dumps(data))
redis_store.expire(key, 300000)
def fetch_first_submission(nonce):
key = REDIS_FIRSTSUBMISSION_KEY(nonce=nonce)
jsondata = redis_store.get(key)
try:
return json.loads(jsondata.decode('utf-8'))
except:
return None