def form_submissions(hashid): if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_with_hashid(hashid) if not form.controlled_by(current_user): if request_wants_json(): return jsonerror(403, {'error': "You do not control this form."}) else: return redirect(url_for('dashboard')) submissions = form.submissions if request_wants_json(): return jsonify({'submissions': [s.data for s in submissions]}) else: fields = set() for s in submissions: fields.update(s.data.keys()) fields -= set(EXCLUDE_KEYS) return render_template('forms/submissions.html', form=form, fields=sorted(fields), submissions=submissions)
def form_submissions(hashid): if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_with_hashid(hashid) if not form.controlled_by(current_user): if request_wants_json(): return jsonerror(403, {'error': "You do not control this form."}) else: return redirect(url_for('dashboard')) submissions = form.submissions if request_wants_json(): return jsonify({ 'submissions': [s.data for s in submissions] }) else: fields = set() for s in submissions: fields.update(s.data.keys()) fields -= set(EXCLUDE_KEYS) return render_template('forms/submissions.html', form=form, fields=sorted(fields), submissions=submissions )
def custom_template_set(hashid): form = Form.get_with_hashid(hashid) if not form: return jsonerror(404, {'error': "Form not found."}) if not form.controlled_by(current_user): return jsonerror(401, {'error': "You do not control this form."}) template = form.template if not template: template = EmailTemplate(form_id=form.id) template.from_name = request.get_json()['from_name'] template.subject = request.get_json()['subject'] template.style = request.get_json()['style'] template.body = request.get_json()['body'] try: template.sample() except Exception as e: print(e) return jsonerror(406, {'error': "Failed to render. The template has errors."}) DB.session.add(template) DB.session.commit() return jsonify({'ok': True})
def custom_template_set(hashid): form = Form.get_with_hashid(hashid) if not form: return jsonerror(404, {'error': "Form not found."}) if not form.controlled_by(current_user): return jsonerror(401, {'error': "You do not control this form."}) template = form.template if not template: template = EmailTemplate(form_id=form.id) template.from_name = request.get_json()['from_name'] template.subject = request.get_json()['subject'] template.style = request.get_json()['style'] template.body = request.get_json()['body'] try: template.sample() except Exception as e: print(e) return jsonerror( 406, {'error': "Failed to render. The template has errors."}) DB.session.add(template) DB.session.commit() return jsonify({'ok': True})
def update(hashid): # check that this request came from user dashboard to prevent XSS and CSRF referrer = referrer_to_baseurl(request.referrer) service = referrer_to_baseurl(settings.SERVICE_URL) if referrer != service: return jsonerror(400, {'error': 'Improper request.'}) form = Form.get_with_hashid(hashid) if not form: return jsonerror(400, {'error': 'Not a valid form.'}) if form.owner_id != current_user.id and form not in current_user.forms: return jsonerror(401, {'error': 'Wrong user.'}) patch = request.get_json() for attr in [ 'disable_storage', 'disabled', 'disable_email', 'captcha_disabled' ]: if attr in patch: setattr(form, attr, patch[attr]) DB.session.add(form) DB.session.commit() return jsonify({'ok': True})
def form_submissions(random_like_string): if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_form_by_random_like_string(random_like_string) submissions = form.submissions if request_wants_json(): if current_user.id != form.owner_id: return jsonerror(403, {'error': "You're not the owner of this form."}) return jsonify({'submissions': [s.data for s in submissions]}) else: if current_user.id != form.owner_id: return redirect(url_for('dashboard')) fields = set() for s in submissions: fields.update(s.data.keys()) fields -= set(EXCLUDE_KEYS) return render_template('forms/submissions.html', form=form, fields=sorted(fields), submissions=submissions)
def form_submissions(random_like_string): if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_form_by_random_like_string(random_like_string) submissions = form.submissions if request_wants_json(): if current_user.id != form.owner_id: return jsonerror(403, {'error': "You're not the owner of this form."}) return jsonify({ 'submissions': [s.data for s in submissions] }) else: if current_user.id != form.owner_id: return redirect(url_for('dashboard')) fields = set() for s in submissions: fields.update(s.data.keys()) fields -= set(EXCLUDE_KEYS) return render_template('forms/submissions.html', form=form, fields=sorted(fields), submissions=submissions )
def forms(): if request.method == 'GET': if request_wants_json(): return jsonerror( 501, { 'error': "This endpoint may return the list of forms for the logged user." }) else: return redirect(url_for('dashboard')) # Create a new form if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') else: email = request.form.get('email') if not IS_VALID_EMAIL(email): if request_wants_json(): return jsonerror( 400, {'error': "The email you sent is not a valid email."}) else: flash('The email you sent is not a valid email.', 'error') return redirect(url_for('dashboard')) form = Form(email, owner=current_user) DB.session.add(form) DB.session.commit() # A unique identifier for the form that maps to its id, # but doesn't seem like a sequential integer random_like_string = form.get_random_like_string() if request_wants_json(): return jsonify({ 'ok': True, 'random_like_string': random_like_string, 'submission_url': settings.API_ROOT + '/' + random_like_string }) else: return redirect(url_for('dashboard'))
def resend_confirmation(email): g.log = g.log.bind(email=email, host=request.form.get('host')) g.log.info('Resending confirmation.') # the first thing to do is to check the captcha r = requests.post('https://www.google.com/recaptcha/api/siteverify', data={ 'secret': settings.RECAPTCHA_SECRET, 'response': request.form['g-recaptcha-response'], 'remoteip': request.remote_addr }) if r.ok and r.json().get('success'): # then proceed to check if this email is listed on SendGrid's bounces r = requests.get('https://api.sendgrid.com/api/bounces.get.json', params={ 'email': email, 'api_user': settings.SENDGRID_USERNAME, 'api_key': settings.SENDGRID_PASSWORD } ) if r.ok and len(r.json()) and 'reason' in r.json()[0]: # tell the user to verify his mailbox reason = r.json()[0]['reason'] g.log.info('Email is blocked on SendGrid. Telling the user.') if request_wants_json(): resp = jsonify({'error': "Verify your mailbox, we can't reach it.", 'reason': reason}) else: resp = make_response(render_template('info.html', title='Verify the availability of your mailbox', text="We encountered an error when trying to deliver the confirmation message to <b>" + email + "</b> at the first time we tried. For spam reasons, we will not try again until we are sure the problem is fixed. Here's the reason:</p><p><center><i>" + reason + "</i></center></p><p>Please make sure this problem is not happening still, then go to <a href='/unblock/" + email + "'>this page</a> to unblock your address.</p><p>After you have unblocked the address, please try to resend the confirmation again.</p>" )) return resp # ~~~ # if there's no bounce, we proceed to resend the confirmation. form = Form.query.filter_by(hash=HASH(email, request.form['host'])).first() if not form: if request_wants_json(): return jsonerror(400, {'error': "This form does not exist."}) else: return render_template('error.html', title='Check email address', text='This form does not exist.'), 400 form.confirm_sent = False status = form.send_confirmation() if status['code'] == Form.STATUS_CONFIRMATION_SENT: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=request.form['host'], resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED ) # fallback response -- should happen only when the recaptcha is failed. g.log.warning('Failed to resend confirmation.') return render_template('error.html', title='Unable to send email', text='Please make sure you pass the <i>reCaptcha</i> test before submitting.'), 500
def get(hashid): if not current_user.has_feature('dashboard'): return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_with_hashid(hashid) if not form: return jsonerror(404, {'error': "Form not found."}) if not form.controlled_by(current_user): return jsonerror(401, {'error': "You do not control this form."}) submissions, fields = form.submissions_with_fields() ret = form.serialize() ret['submissions'] = submissions ret['fields'] = fields return jsonify(ret)
def malformed_replyto_error(status): if request_wants_json(): return jsonerror(500, {'error': "_replyto or email field has not been sent correctly"}) return render_template( 'error.html', title='Invalid email address', text=u'You entered <span class="code">{address}</span>. That is an invalid email address. Please correct the form and try to submit again <a href="{back}">here</a>.<p style="font-size: small">This could also be a problem with the form. For example, there could be two fields with <span class="code">_replyto</span> or <span class="code">email</span> name attribute. If you suspect the form is broken, please contact the form owner and ask them to investigate</p>'''.format(address=status['address'], back=status['referrer']) ), 400
def bad_method_error(): if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) return render_template('info.html', title='Form should POST', text='Make sure your form has the <span ' 'class="code"><strong>method="POST"' '</strong></span> attribute'), 405
def empty_form_error(referrer): if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) return render_template( 'error.html', title='Can\'t send an empty form', text=u'<p>Make sure you have placed the <a href="http://www.w3schools.com/tags/att_input_name.asp" target="_blank"><code>"name"</code> attribute</a> in all your form elements. Also, to prevent empty form submissions, take a look at the <a href="http://www.w3schools.com/tags/att_input_required.asp" target="_blank"><code>"required"</code> property</a>.</p><p>This error also happens when you have an <code>"enctype"</code> attribute set in your <code><form></code>, so make sure you don\'t.</p><p><a href="{}">Return to form</a></p>'.format(referrer) ), 400
def empty_form_error(referrer): if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) return render_template( 'error.html', title='Can\'t send an empty form', text= u'<p>Make sure you have placed the <a href="http://www.w3schools.com/tags/att_input_name.asp" target="_blank"><code>"name"</code> attribute</a> in all your form elements. Also, to prevent empty form submissions, take a look at the <a href="http://www.w3schools.com/tags/att_input_required.asp" target="_blank"><code>"required"</code> property</a>.</p><p>This error also happens when you have an <code>"enctype"</code> attribute set in your <code><form></code>, so make sure you don\'t.</p><p><a href="{}">Return to form</a></p>' .format(referrer)), 400
def generic_send_error(status): # error fallback -- shouldn't happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) return render_template( 'error.html', title='Unable to send email', text=u'Unable to send email. If you can, please send the link to your form and the error information to <b>{email}</b>. And send them the following: <p><pre><code>{message}</code></pre></p>'.format(message=json.dumps(status), email=settings.CONTACT_EMAIL) ), 500
def delete(hashid): # check that this request came from user dashboard to prevent XSS and CSRF referrer = referrer_to_baseurl(request.referrer) service = referrer_to_baseurl(settings.SERVICE_URL) if referrer != service: return jsonerror(400, {'error': 'Improper request.'}) form = Form.get_with_hashid(hashid) if not form: return jsonerror(400, {'error': 'Not a valid form.'}) if form.owner_id != current_user.id and form not in current_user.forms: return jsonerror(401, {'error': 'Wrong user.'}) for submission in form.submissions: DB.session.delete(submission) DB.session.delete(form) DB.session.commit() return jsonify({'ok': True})
def generic_send_error(status): # error fallback -- shouldn't happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) return render_template( 'error.html', title='Unable to send email', text= u'Unable to send email. If you can, please send the link to your form and the error information to <b>{email}</b>. And send them the following: <p><pre><code>{message}</code></pre></p>' .format(message=json.dumps(status), email=settings.CONTACT_EMAIL)), 500
def disabled_error(): # owner has disabled the form, so it should not receive any submissions g.log.info('submission rejected. Form is disabled.') if request_wants_json(): return jsonerror(403, {'error': 'Form not active'}) return render_template( 'error.html', title='Form not active', text='The owner of this form has disabled this form and it is no longer accepting submissions. Your submissions was not accepted' ), 403
def bad_method_error(): if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) return render_template( 'info.html', title='Form should POST', text='Make sure your form has the <span ' 'class="code"><strong>method="POST"' '</strong></span> attribute' ), 405
def get(hashid): if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_with_hashid(hashid) if not form: return jsonerror(404, {'error': "Form not found."}) for cont in form.controllers: if cont.id == current_user.id: break else: return jsonerror(401, {'error': "You do not control this form."}) submissions, fields = form.submissions_with_fields() ret = form.serialize() ret['submissions'] = submissions ret['fields'] = fields return jsonify(ret)
def bad_hashid_error(email_or_string): # no form row found. it is an error. g.log.info('Submission rejected. No form found for this target.') if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) return render_template( 'error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string) ), 400
def mismatched_host_error(host, form): g.log.info('Submission rejected. From a different host than confirmed.') if request_wants_json(): return jsonerror(403, {'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host}) return render_template( 'error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for address "%s"' % (host, form.host) ), 403
def disabled_error(): # owner has disabled the form, so it should not receive any submissions g.log.info('submission rejected. Form is disabled.') if request_wants_json(): return jsonerror(403, {'error': 'Form not active'}) return render_template( 'error.html', title='Form not active', text= 'The owner of this form has disabled this form and it is no longer accepting submissions. Your submissions was not accepted' ), 403
def malformed_replyto_error(status): if request_wants_json(): return jsonerror( 500, {'error': "_replyto or email field has not been sent correctly"}) return render_template( 'error.html', title='Invalid email address', text= u'You entered <span class="code">{address}</span>. That is an invalid email address. Please correct the form and try to submit again <a href="{back}">here</a>.<p style="font-size: small">This could also be a problem with the form. For example, there could be two fields with <span class="code">_replyto</span> or <span class="code">email</span> name attribute. If you suspect the form is broken, please contact the form owner and ask them to investigate</p>' ''.format(address=status['address'], back=status['referrer'])), 400
def forms(): if request.method == 'GET': if request_wants_json(): return jsonerror(501, {'error': "This endpoint may return the list of forms for the logged user."}) else: return redirect(url_for('dashboard')) # Create a new form if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') else: email = request.form.get('email') if not IS_VALID_EMAIL(email): if request_wants_json(): return jsonerror(400, {'error': "The email you sent is not a valid email."}) else: flash('The email you sent is not a valid email.', 'error') return redirect(url_for('dashboard')) form = Form(email, owner=current_user) DB.session.add(form) DB.session.commit() # A unique identifier for the form that maps to its id, # but doesn't seem like a sequential integer random_like_string = form.get_random_like_string() if request_wants_json(): return jsonify({ 'ok': True, 'random_like_string': random_like_string, 'submission_url': settings.API_ROOT + '/' + random_like_string }) else: return redirect(url_for('dashboard'))
def mismatched_host_error(host, form): g.log.info('Submission rejected. From a different host than confirmed.') if request_wants_json(): return jsonerror( 403, { 'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host }) return render_template( 'error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for address "%s"' % (host, form.host)), 403
def update(hashid): # check that this request came from user dashboard to prevent XSS and CSRF referrer = referrer_to_baseurl(request.referrer) service = referrer_to_baseurl(settings.SERVICE_URL) if referrer != service: return jsonerror(400, {'error': 'Improper request.'}) form = Form.get_with_hashid(hashid) if not form: return jsonerror(400, {'error': 'Not a valid form.'}) if form.owner_id != current_user.id and form not in current_user.forms: return jsonerror(401, {'error': 'Wrong user.'}) patch = request.get_json() for attr in ['disable_storage', 'disabled', 'disable_email', 'captcha_disabled']: if attr in patch: setattr(form, attr, patch[attr]) DB.session.add(form) DB.session.commit() return jsonify({'ok': True})
def no_referrer_error(): g.log.info('Invalid Referrer.') if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referer\" header"}) return render_template( 'error.html', title='Unable to submit form', text='<p>Make sure you open this page through a web server, ' 'Formspree will not work in pages browsed as HTML files. ' 'Also make sure that you\'re posting to <b>{host}{path}</b>.</p>' '<p>For geeks: could not find the "Referer" header.</p>'.format( host=settings.SERVICE_URL, path=request.path)), 400
def resend_confirmation(email): # I'm not sure if this should be available for forms created on the dashboard. form = Form.query.filter_by(hash=HASH(email, request.form['host'])).first() if not form: if request_wants_json(): return jsonerror(400, {'error': "This form does not exists"}) else: return render_template('error.html', title='Check email address', text='This form does not exists'), 400 r = requests.post('https://www.google.com/recaptcha/api/siteverify', data={ 'secret': settings.RECAPTCHA_SECRET, 'response': request.form['g-recaptcha-response'], 'remoteip': request.remote_addr }) if r.ok and r.json()['success']: form.confirm_sent = False status = form.send_confirmation() if status['code'] == Form.STATUS_CONFIRMATION_SENT: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=request.form['host'], resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED) # fallback response -- should never happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email'), 500
def resend_confirmation(email): # I'm not sure if this should be available for forms created on the dashboard. form = Form.query.filter_by(hash=HASH(email, request.form['host'])).first() if not form: if request_wants_json(): return jsonerror(400, {'error': "This form does not exists"}) else: return render_template('error.html', title='Check email address', text='This form does not exists'), 400 r = requests.post('https://www.google.com/recaptcha/api/siteverify', data={ 'secret': settings.RECAPTCHA_SECRET, 'response': request.form['g-recaptcha-response'], 'remoteip': request.remote_addr }) if r.ok and r.json()['success']: form.confirm_sent = False status = form.send_confirmation() if status['code'] == Form.STATUS_CONFIRMATION_SENT: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=request.form['host'], resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED ) # fallback response -- should never happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email'), 500
def no_referrer_error(): g.log.info('Invalid Referrer.') if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referer\" header"}) return render_template( 'error.html', title='Unable to submit form', text='<p>Make sure you open this page through a web server, ' 'Formspree will not work in pages browsed as HTML files. ' 'Also make sure that you\'re posting to <b>{host}{path}</b>.</p>' '<p>For geeks: could not find the "Referer" header.</p>'.format( host=settings.SERVICE_URL, path=request.path ) ), 400
def export_submissions(hashid, format=None): if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_with_hashid(hashid) for cont in form.controllers: if cont.id == current_user.id: break else: return abort(401) submissions, fields = form.submissions_with_fields() if format == 'json': return Response( json.dumps({ 'host': form.host, 'email': form.email, 'fields': fields, 'submissions': submissions }, sort_keys=True, indent=2), mimetype='application/json', headers={ 'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.json' \ % (hashid, datetime.datetime.now().isoformat().split('.')[0]) } ) elif format == 'csv': out = io.BytesIO() w = csv.DictWriter(out, fieldnames=['id'] + fields, encoding='utf-8') w.writeheader() for sub in submissions: w.writerow(sub) return Response( out.getvalue(), mimetype='text/csv', headers={ 'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.csv' \ % (hashid, datetime.datetime.now().isoformat().split('.')[0]) } )
def export_submissions(hashid, format=None): if not current_user.has_feature('dashboard'): return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_with_hashid(hashid) if not form.controlled_by(current_user): return abort(401) submissions, fields = form.submissions_with_fields() if format == 'json': return Response( json.dumps({ 'host': form.host, 'email': form.email, 'fields': fields, 'submissions': submissions }, sort_keys=True, indent=2), mimetype='application/json', headers={ 'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.json' \ % (hashid, datetime.datetime.now().isoformat().split('.')[0]) } ) elif format == 'csv': out = io.BytesIO() w = csv.DictWriter(out, fieldnames=['id'] + fields, encoding='utf-8') w.writeheader() for sub in submissions: w.writerow(sub) return Response( out.getvalue(), mimetype='text/csv', headers={ 'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.csv' \ % (hashid, datetime.datetime.now().isoformat().split('.')[0]) } )
def get_or_create_form(email, host): ''' Gets the form if it already exits, otherwise checks to ensure that this is a valid new form submission. If so, creates a new form. ''' form = Form.query.filter_by(hash=HASH(email, host)).first() if not form: if request_wants_json(): # Can't create a new ajax form unless from the dashboard ajax_error_str = "To prevent spam, only " + \ settings.UPGRADED_PLAN_NAME + \ " accounts may create AJAX forms." raise SubmitFormError(jsonerror(400, {'error': ajax_error_str})) if url_domain(settings.SERVICE_URL) in host: # Bad user is trying to submit a form spoofing formspree.io g.log.info( 'User attempting to create new form spoofing SERVICE_URL. Ignoring.' ) raise SubmitFormError( (render_template('error.html', title='Unable to submit form', text='Sorry'), 400)) # all good, create form form = Form(email, host) # Check if it has been assigned using AJAX or not assign_ajax(form, request_wants_json()) if form.disabled: raise SubmitFormError(errors.disabled_error()) return form
def get_or_create_form(email, host): ''' Gets the form if it already exits, otherwise checks to ensure that this is a valid new form submission. If so, creates a new form. ''' form = Form.query.filter_by(hash=HASH(email, host)).first() if not form: if request_wants_json(): # Can't create a new ajax form unless from the dashboard ajax_error_str = "To prevent spam, only " + \ settings.UPGRADED_PLAN_NAME + \ " accounts may create AJAX forms." raise SubmitFormError(jsonerror(400, {'error': ajax_error_str})) if url_domain(settings.SERVICE_URL) in host: # Bad user is trying to submit a form spoofing formspree.io g.log.info('User attempting to create new form spoofing SERVICE_URL. Ignoring.') raise SubmitFormError((render_template( 'error.html', title='Unable to submit form', text='Sorry'), 400)) # all good, create form form = Form(email, host) # Check if it has been assigned using AJAX or not assign_ajax(form, request_wants_json()) if form.disabled: raise SubmitFormError(errors.disabled_error()) return form
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template('info.html', title='Form should POST', text='Make sure your form has the <span class="code"><strong>method="POST"</strong></span> attribute'), 405 host = referrer_to_path(flask.request.referrer) if not host: if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template('error.html', title='Unable to submit form', text='Make sure you open this page through a web server, Formspree will not work in pages browsed as HTML files. For geeks: could not find the "Referrer" header.'), 400 if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() elif form.host != host: # if the form submission came from a different host, it is an error if request_wants_json(): return jsonerror(403, {'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host}) else: return render_template('error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for the address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() \ or Form(email, host) # or create it if it doesn't exists # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: status = form.send(request.form, request.referrer) else: status = form.send_confirmation(with_data=request.form) # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({ 'success': "email sent", 'next': status['next'] }) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template('error.html', title='Can\'t send an empty form', text=str('<p>Make sure you have placed the <a href="http://www.w3schools.com/tags/att_input_name.asp" target="_blank">"name" attribute</a> in all your form elements. Also, to prevent empty form submissions, take a look at the <a href="http://www.w3schools.com/tags/att_input_required.asp" target="_blank">"required" property</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input" target="_blank">see more HTML form customization info</a>.</p><p><a href="%s">Return to form</a></p>' % request.referrer)), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=host, resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED ) elif status['code'] == Form.STATUS_OVERLIMIT: if request_wants_json(): return jsonify({'error': "form over quota"}) else: return render_template('error.html', title='Form over quota', text='It looks like this form is getting a lot of submissions and ran out of its quota. Try contacting this website through other means or try submitting again later.' ) # error fallback -- shouldn't happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email. If you can, please report this immediately to <b>[email protected]</b>. And send them the following: <p><pre><code>' + json.dumps(status) + '</code></pre></p>'), 500
def forms(): if request.method == 'GET': ''' A reminder: this is the /forms endpoint, but for GET requests it is also the /dashboard endpoint. The /dashboard endpoint, the address gave by url_for('dashboard'), is the target of a lot of redirects around the app, but it can be changed later to point to somewhere else. ''' # grab all the forms this user controls if current_user.upgraded: forms = current_user.forms.order_by(Form.id.desc()).all() else: forms = [] if request_wants_json(): return jsonify({ 'ok': True, 'forms': [{ 'email': f.email, 'host': f.host, 'confirm_sent': f.confirm_sent, 'confirmed': f.confirmed, 'is_public': bool(f.hash), 'url': '{S}/{E}'.format( S=settings.SERVICE_URL, E=f.hashid ) } for f in forms] }) else: return render_template('forms/list.html', forms=forms) elif request.method == 'POST': # create a new form if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') else: email = request.form.get('email') if not IS_VALID_EMAIL(email): if request_wants_json(): return jsonerror(400, {'error': "The email you sent is not a valid email."}) else: flash('The email you provided is not a valid email.', 'error') return redirect(url_for('dashboard')) form = Form(email, owner=current_user) DB.session.add(form) DB.session.commit() if request_wants_json(): return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid }) else: flash('Your new form endpoint was created!', 'success') return redirect(url_for('dashboard') + '#view-code-' + form.hashid)
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' g.log = g.log.bind(target=email_or_string) if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template('info.html', title='Form should POST', text='Make sure your form has the <span ' 'class="code"><strong>method="POST"' '</strong></span> attribute'), 405 if request.form: received_data, sorted_keys = http_form_to_dict(request.form) else: received_data = request.get_json() or {} sorted_keys = received_data.keys() sorted_keys = [k for k in sorted_keys if k not in KEYS_EXCLUDED_FROM_EMAIL] # NOTE: host in this function generally refers to the referrer hostname. try: # Get stored hostname from redis (from captcha) host, referrer = get_temp_hostname(received_data['_host_nonce']) except KeyError: host, referrer = referrer_to_path(request.referrer), request.referrer except ValueError as err: g.log.error('Invalid hostname stored on Redis.', err=err) return render_template( 'error.html', title='Unable to submit form', text='<p>We had a problem identifying to whom we should have submitted this form. Please try submitting again. If it fails once more, please let us know at {email}</p>'.format( email=settings.CONTACT_EMAIL, ) ), 500 if not host: if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template( 'error.html', title='Unable to submit form', text='<p>Make sure you open this page through a web server, Formspree will not work in pages browsed as HTML files. Also make sure that you\'re posting to <b>{host}{path}</b>.</p><p>For geeks: could not find the "Referrer" header.</p>'.format( host=settings.SERVICE_URL, path=request.path ) ), 400 g.log = g.log.bind(host=host, wants='json' if request_wants_json() else 'html') if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: # Check if it has been assigned about using AJAX or not assign_ajax(form, request_wants_json()) if form.disabled: # owner has disabled the form, so it should not receive any submissions if request_wants_json(): return jsonerror(403, {'error': 'Form not active'}) else: return render_template('error.html', title='Form not active', text='The owner of this form has disabled this form and it is no longer accepting submissions. Your submissions was not accepted'), 403 email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() # it is an error when # form is not sitewide, and submission came from a different host # form is sitewide, but submission came from a host rooted somewhere else, or elif (not form.sitewide and # ending slashes can be safely ignored here: form.host.rstrip('/') != host.rstrip('/')) or \ (form.sitewide and \ # removing www from both sides makes this a neutral operation: not remove_www(host).startswith(remove_www(form.host)) ): g.log.info('Submission rejected. From a different host than confirmed.') if request_wants_json(): return jsonerror(403, { 'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host }) else: return render_template('error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. g.log.info('Submission rejected. No form found for this target.') if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string.lower() # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() # or create it if it doesn't exist if not form: if request_wants_json(): # Can't create a new ajax form unless from the dashboard ajax_error_str = "To prevent spam, only " + \ settings.UPGRADED_PLAN_NAME + \ " accounts may create AJAX forms." return jsonerror(400, {'error': ajax_error_str}) elif url_domain(settings.SERVICE_URL) in host: # Bad user is trying to submit a form spoofing formspree.io g.log.info('User attempting to create new form spoofing SERVICE_URL. Ignoring.') return render_template( 'error.html', title='Unable to submit form', text='Sorry'), 400 else: # all good, create form form = Form(email, host) # Check if it has been assigned using AJAX or not assign_ajax(form, request_wants_json()) if form.disabled: g.log.info('submission rejected. Form is disabled.') if request_wants_json(): return jsonerror(403, {'error': 'Form not active'}) else: return render_template('error.html', title='Form not active', text='The owner of this form has disabled this form and it is no longer accepting submissions. Your submissions was not accepted'), 403 # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: captcha_verified = verify_captcha(received_data, request) needs_captcha = not (request_wants_json() or captcha_verified or settings.TESTING) # if form is upgraded check if captcha is disabled if form.upgraded: needs_captcha = needs_captcha and not form.captcha_disabled if needs_captcha: data_copy = received_data.copy() # Temporarily store hostname in redis while doing captcha nonce = temp_store_hostname(form.host, request.referrer) data_copy['_host_nonce'] = nonce action = urljoin(settings.API_ROOT, email_or_string) try: if '_language' in received_data: return render_template( 'forms/captcha_lang/{}.html'.format(received_data['_language']), data=data_copy, sorted_keys=sorted_keys, action=action, lang=received_data['_language'] ) except TemplateNotFound: g.log.error('Requested language not found for reCAPTCHA page, defaulting to English', referrer=request.referrer, lang=received_data['_language']) pass return render_template('forms/captcha.html', data=data_copy, sorted_keys=sorted_keys, action=action, lang=None) status = form.send(received_data, sorted_keys, referrer) else: status = form.send_confirmation(store_data=received_data) # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({'success': "email sent", 'next': status['next']}) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_NO_EMAIL: if request_wants_json(): return jsonify({'success': "no email sent, access submission archive on {} dashboard".format(settings.SERVICE_NAME), 'next': status['next']}) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template( 'error.html', title='Can\'t send an empty form', text=u'<p>Make sure you have placed the <a href="http://www.w3schools.com/tags/att_input_name.asp" target="_blank"><code>"name"</code> attribute</a> in all your form elements. Also, to prevent empty form submissions, take a look at the <a href="http://www.w3schools.com/tags/att_input_required.asp" target="_blank"><code>"required"</code> property</a>.</p><p>This error also happens when you have an <code>"enctype"</code> attribute set in your <code><form></code>, so make sure you don\'t.</p><p><a href="{}">Return to form</a></p>'.format(referrer) ), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=host, resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED ) elif status['code'] == Form.STATUS_OVERLIMIT: if request_wants_json(): return jsonify({'error': "form over quota"}) else: return render_template('error.html', title='Form over quota', text='It looks like this form is getting a lot of submissions and ran out of its quota. Try contacting this website through other means or try submitting again later.'), 402 elif status['code'] == Form.STATUS_REPLYTO_ERROR: if request_wants_json(): return jsonerror(500, {'error': "_replyto or email field has not been sent correctly"}) else: return render_template( 'error.html', title='Invalid email address', text=u'You entered <span class="code">{address}</span>. That is an invalid email address. Please correct the form and try to submit again <a href="{back}">here</a>.<p style="font-size: small">This could also be a problem with the form. For example, there could be two fields with <span class="code">_replyto</span> or <span class="code">email</span> name attribute. If you suspect the form is broken, please contact the form owner and ask them to investigate</p>'''.format(address=status['address'], back=status['referrer']) ), 400 # error fallback -- shouldn't happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template( 'error.html', title='Unable to send email', text=u'Unable to send email. If you can, please send the link to your form and the error information to <b>{email}</b>. And send them the following: <p><pre><code>{message}</code></pre></p>'.format(message=json.dumps(status), email=settings.CONTACT_EMAIL) ), 500
def create_form(): # create a new form if not current_user.upgraded: g.log.info('Failed to create form from dashboard. User is not upgraded.') return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') url = request.get_json().get('url') sitewide = request.get_json().get('sitewide') else: email = request.form.get('email') url = request.form.get('url') sitewide = request.form.get('sitewide') g.log = g.log.bind(email=email, url=url, sitewide=sitewide) if not IS_VALID_EMAIL(email): g.log.info('Failed to create form from dashboard. Invalid address.') if request_wants_json(): return jsonerror(400, {'error': "The provided email address is not valid."}) else: flash(u'The provided email address is not valid.', 'error') return redirect(url_for('dashboard')) g.log.info('Creating a new form from the dashboard.') email = email.lower() # case-insensitive form = Form(email, owner=current_user) if url: url = 'http://' + url if not url.startswith('http') else url form.host = referrer_to_path(url) # sitewide forms, verified with a file at the root of the target domain if sitewide: if sitewide_file_check(url, email): form.host = remove_www(referrer_to_path(urljoin(url, '/'))[:-1]) form.sitewide = True else: return jsonerror(403, { 'error': u"Couldn't verify the file at {}.".format(url) }) DB.session.add(form) DB.session.commit() if form.host: # when the email and url are provided, we can automatically confirm the form # but only if the email is registered for this account for email in current_user.emails: if email.address == form.email: g.log.info('No need for email confirmation.') form.confirmed = True DB.session.add(form) DB.session.commit() break else: # in case the email isn't registered for this user # we automatically send the email confirmation form.send_confirmation() if request_wants_json(): return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid, 'confirmed': form.confirmed }) else: flash(u'Your new form endpoint was created!', 'success') return redirect(url_for('dashboard', new=form.hashid) + '#form-' + form.hashid)
def forms(): if request.method == 'GET': ''' A reminder: this is the /forms endpoint, but for GET requests it is also the /dashboard endpoint. The /dashboard endpoint, the address gave by url_for('dashboard'), is the target of a lot of redirects around the app, but it can be changed later to point to somewhere else. ''' # grab all the forms this user controls if current_user.upgraded: forms = current_user.forms.order_by(Form.id.desc()).all() else: forms = [] if request_wants_json(): return jsonify({ 'ok': True, 'forms': [{ 'email': f.email, 'host': f.host, 'confirm_sent': f.confirm_sent, 'confirmed': f.confirmed, 'is_public': bool(f.hash), 'url': '{S}/{E}'.format(S=settings.SERVICE_URL, E=f.hashid) } for f in forms] }) else: return render_template('forms/list.html', forms=forms) elif request.method == 'POST': # create a new form if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') else: email = request.form.get('email') if not IS_VALID_EMAIL(email): if request_wants_json(): return jsonerror( 400, {'error': "The email you sent is not a valid email."}) else: flash('The email you provided is not a valid email.', 'error') return redirect(url_for('dashboard')) form = Form(email, owner=current_user) DB.session.add(form) DB.session.commit() if request_wants_json(): return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid }) else: flash('Your new form endpoint was created!', 'success') return redirect(url_for('dashboard') + '#view-code-' + form.hashid)
def resend_confirmation(email): # the first thing to do is to check the captcha r = requests.post('https://www.google.com/recaptcha/api/siteverify', data={ 'secret': settings.RECAPTCHA_SECRET, 'response': request.form['g-recaptcha-response'], 'remoteip': request.remote_addr }) if r.ok and r.json().get('success'): # then proceed to check for bounced addresses if request.form.get('bounce_problem_solved') == 'true': # delete bounce from SendGrid then proceed requests.delete('https://api.sendgrid.com/v3/suppression/bounces/', auth=(settings.SENDGRID_USERNAME, settings.SENDGRID_PASSWORD), data={'emails': [email]}) else: # check if this email is listed on SendGrid's bounces r = requests.get('https://api.sendgrid.com/v3/suppression/bounces/' + email, auth=(settings.SENDGRID_USERNAME, settings.SENDGRID_PASSWORD)) if r.ok and len(r.json()) and 'reason' in r.json()[0]: # tell the user to verify his mailbox # and, at the same time, let him mark the checkbox that says he has already # made his mailbox available in the next time he tries to resend # confirmation. reason = r.json()[0]['reason'] if request_wants_json(): resp = jsonify({'error': "Verify your mailbox, we can't reach it.", 'reason': reason}) else: resp = make_response(render_template('info.html', title='Verify the availability of your mailbox', text="We encountered an error when trying to deliver the confirmation message to <b>" + email + "</b> at the first time we tried. For spam reasons, we will not try again until we are sure the problem is fixed. Here's the reason:</p><p><center><i>" + reason + "</i></center></p><p>Please make sure this problem is not happening still, then come here and try to resend your confirmation message again." )) resp.set_cookie('has_been_notified_of_bounce', 'true', max_age=345600) return resp # ~~~ # if there's no bounce or the bounce problem has been solved, we proceed to resend # the confirmation. # I'm not sure if this should be available for forms created on the dashboard. form = Form.query.filter_by(hash=HASH(email, request.form['host'])).first() if not form: if request_wants_json(): return jsonerror(400, {'error': "This form does not exists"}) else: return render_template('error.html', title='Check email address', text='This form does not exists'), 400 form.confirm_sent = False status = form.send_confirmation() if status['code'] == Form.STATUS_CONFIRMATION_SENT: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=request.form['host'], resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED ) # fallback response -- should never happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email'), 500
def create(): # check that this request came from user dashboard to prevent XSS and CSRF referrer = referrer_to_baseurl(request.referrer) service = referrer_to_baseurl(settings.SERVICE_URL) if referrer != service: return jsonerror(400, {'error': 'Improper request.'}) if not current_user.has_feature('dashboard'): g.log.info('Failed to create form from dashboard. Forbidden.') return jsonerror(402, {'error': "Please upgrade your account."}) email = request.get_json().get('email') url = request.get_json().get('url') sitewide = request.get_json().get('sitewide') g.log = g.log.bind(email=email, url=url, sitewide=sitewide) if not IS_VALID_EMAIL(email): g.log.info('Failed to create form from dashboard. Invalid address.') return jsonerror(400, {'error': "The provided email address is not valid."}) g.log.info('Creating a new form from the dashboard.') email = email.lower().strip() # case-insensitive form = Form(email, owner=current_user) if url: url = 'http://' + url if not url.startswith('http') else url form.host = referrer_to_path(url) # sitewide forms, verified with a file at the root of the target domain if sitewide: if sitewide_file_check(url, email): form.host = remove_www( referrer_to_path(urljoin(url, '/'))[:-1]) form.sitewide = True else: return jsonerror( 403, {'error': u"Couldn't verify the file at {}.".format(url)}) DB.session.add(form) DB.session.commit() if form.host: # when the email and url are provided, we can automatically confirm the form # but only if the email is registered for this account for email in current_user.emails: if email.address == form.email: g.log.info('No need for email confirmation.') form.confirmed = True DB.session.add(form) DB.session.commit() break else: # in case the email isn't registered for this user # we automatically send the email confirmation form.send_confirmation() return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid, 'confirmed': form.confirmed })
def create(): # check that this request came from user dashboard to prevent XSS and CSRF referrer = referrer_to_baseurl(request.referrer) service = referrer_to_baseurl(settings.SERVICE_URL) if referrer != service: return jsonerror(400, {'error': 'Improper request.'}) if not current_user.has_feature('dashboard'): g.log.info('Failed to create form from dashboard. Forbidden.') return jsonerror(402, {'error': "Please upgrade your account."}) email = request.get_json().get('email') url = request.get_json().get('url') sitewide = request.get_json().get('sitewide') g.log = g.log.bind(email=email, url=url, sitewide=sitewide) if not IS_VALID_EMAIL(email): g.log.info('Failed to create form from dashboard. Invalid address.') return jsonerror(400, {'error': "The provided email address is not valid."}) g.log.info('Creating a new form from the dashboard.') email = email.lower().strip() # case-insensitive form = Form(email, owner=current_user) if url: url = 'http://' + url if not url.startswith('http') else url form.host = referrer_to_path(url) # sitewide forms, verified with a file at the root of the target domain if sitewide: if sitewide_file_check(url, email): form.host = remove_www(referrer_to_path(urljoin(url, '/'))[:-1]) form.sitewide = True else: return jsonerror(403, { 'error': u"Couldn't verify the file at {}.".format(url) }) DB.session.add(form) DB.session.commit() if form.host: # when the email and url are provided, we can automatically confirm the form # but only if the email is registered for this account for email in current_user.emails: if email.address == form.email: g.log.info('No need for email confirmation.') form.confirmed = True DB.session.add(form) DB.session.commit() break else: # in case the email isn't registered for this user # we automatically send the email confirmation form.send_confirmation() return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid, 'confirmed': form.confirmed })
def create_form(): # create a new form if not current_user.upgraded: g.log.info( 'Failed to create form from dashboard. User is not upgraded.') return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') url = request.get_json().get('url') sitewide = request.get_json().get('sitewide') else: email = request.form.get('email') url = request.form.get('url') sitewide = request.form.get('sitewide') g.log = g.log.bind(email=email, url=url, sitewide=sitewide) if not IS_VALID_EMAIL(email): g.log.info('Failed to create form from dashboard. Invalid address.') if request_wants_json(): return jsonerror( 400, {'error': "The provided email address is not valid."}) else: flash(u'The provided email address is not valid.', 'error') return redirect(url_for('dashboard')) g.log.info('Creating a new form from the dashboard.') email = email.lower() # case-insensitive form = Form(email, owner=current_user) if url: url = 'http://' + url if not url.startswith('http') else url form.host = referrer_to_path(url) # sitewide forms, verified with a file at the root of the target domain if sitewide: if sitewide_file_check(url, email): form.host = remove_www( referrer_to_path(urljoin(url, '/'))[:-1]) form.sitewide = True else: return jsonerror( 403, {'error': u"Couldn't verify the file at {}.".format(url)}) DB.session.add(form) DB.session.commit() if form.host: # when the email and url are provided, we can automatically confirm the form # but only if the email is registered for this account for email in current_user.emails: if email.address == form.email: g.log.info('No need for email confirmation.') form.confirmed = True DB.session.add(form) DB.session.commit() break else: # in case the email isn't registered for this user # we automatically send the email confirmation form.send_confirmation() if request_wants_json(): return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid, 'confirmed': form.confirmed }) else: flash(u'Your new form endpoint was created!', 'success') return redirect( url_for('dashboard', new=form.hashid) + '#form-' + form.hashid)
def resend_confirmation(email): # the first thing to do is to check the captcha r = requests.post('https://www.google.com/recaptcha/api/siteverify', data={ 'secret': settings.RECAPTCHA_SECRET, 'response': request.form['g-recaptcha-response'], 'remoteip': request.remote_addr }) if r.ok and r.json().get('success'): # then proceed to check for bounced addresses if request.form.get('bounce_problem_solved') == 'true': # delete bounce from SendGrid then proceed requests.delete('https://api.sendgrid.com/v3/suppression/bounces/', auth=(settings.SENDGRID_USERNAME, settings.SENDGRID_PASSWORD), data={'emails': [email]}) else: # check if this email is listed on SendGrid's bounces r = requests.get( 'https://api.sendgrid.com/v3/suppression/bounces/' + email, auth=(settings.SENDGRID_USERNAME, settings.SENDGRID_PASSWORD)) if r.ok and len(r.json()) and 'reason' in r.json()[0]: # tell the user to verify his mailbox # and, at the same time, let him mark the checkbox that says he has already # made his mailbox available in the next time he tries to resend # confirmation. reason = r.json()[0]['reason'] if request_wants_json(): resp = jsonify({ 'error': "Verify your mailbox, we can't reach it.", 'reason': reason }) else: resp = make_response( render_template( 'info.html', title='Verify the availability of your mailbox', text= "We encountered an error when trying to deliver the confirmation message to <b>" + email + "</b> at the first time we tried. For spam reasons, we will not try again until we are sure the problem is fixed. Here's the reason:</p><p><center><i>" + reason + "</i></center></p><p>Please make sure this problem is not happening still, then come here and try to resend your confirmation message again." )) resp.set_cookie('has_been_notified_of_bounce', 'true', max_age=345600) return resp # ~~~ # if there's no bounce or the bounce problem has been solved, we proceed to resend # the confirmation. # I'm not sure if this should be available for forms created on the dashboard. form = Form.query.filter_by( hash=HASH(email, request.form['host'])).first() if not form: if request_wants_json(): return jsonerror(400, {'error': "This form does not exists"}) else: return render_template('error.html', title='Check email address', text='This form does not exists'), 400 form.confirm_sent = False status = form.send_confirmation() if status['code'] == Form.STATUS_CONFIRMATION_SENT: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=request.form['host'], resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED) # fallback response -- should never happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email'), 500
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template( 'info.html', title='Form should POST', text= 'Make sure your form has the <span class="code"><strong>method="POST"</strong></span> attribute' ), 405 host = referrer_to_path(flask.request.referrer) if not host: if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template( 'error.html', title='Unable to submit form', text= 'Make sure you open this page through a web server, Formspree will not work in pages browsed as HTML files. For geeks: could not find the "Referrer" header.' ), 400 if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() elif form.host != host: # if the form submission came from a different host, it is an error if request_wants_json(): return jsonerror( 403, { 'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host }) else: return render_template( 'error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for the address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() \ or Form(email, host) # or create it if it doesn't exists # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: status = form.send(request.form, request.referrer) else: status = form.send_confirmation(with_data=request.form) # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({'success': "email sent", 'next': status['next']}) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template( 'error.html', title='Can\'t send an empty form', text=str( '<p>Make sure you have placed the <a href="http://www.w3schools.com/tags/att_input_name.asp" target="_blank">"name" attribute</a> in all your form elements. Also, to prevent empty form submissions, take a look at the <a href="http://www.w3schools.com/tags/att_input_required.asp" target="_blank">"required" property</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input" target="_blank">see more HTML form customization info</a>.</p><p><a href="%s">Return to form</a></p>' % request.referrer)), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template( 'forms/confirmation_sent.html', email=email, host=host, resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED) elif status['code'] == Form.STATUS_OVERLIMIT: if request_wants_json(): return jsonify({'error': "form over quota"}) else: return render_template( 'error.html', title='Form over quota', text= 'It looks like this form is getting a lot of submissions and ran out of its quota. Try contacting this website through other means or try submitting again later.' ) elif status['code'] == Form.STATUS_REPLYTO_ERROR: if request_wants_json(): return jsonerror(500, { 'error': "_replyto or email field has not been sent correctly" }) else: return render_template( 'error.html', title='Unable to send email', text= 'Unable to send email. The field with a name attribute _replyto or email was not set correctly. This may be the result of you have multiple _replyto or email fields. If you cannot find your error, please contact <b>[email protected]</b> with a link to your form and this error message: <p><pre><code>' + status['error-message'] + '</code></pre></p>'), 500 # error fallback -- shouldn't happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template( 'error.html', title='Unable to send email', text= 'Unable to send email. If you can, please send the link to your form and the error information to <b>[email protected]</b>. And send them the following: <p><pre><code>' + json.dumps(status) + '</code></pre></p>'), 500
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template('info.html', title='Form should POST', text='Make sure your form has the <span class="code"><strong>method="POST"</strong></span> attribute'), 405 host = referrer_to_path(flask.request.referrer) if not host: if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template('error.html', title='Unable to submit form', text='Make sure your form is running on a proper server. For geeks: could not find the "Referrer" header.'), 400 if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() elif form.host != host: # if the form submission came from a different host, it is an error if request_wants_json(): return jsonerror(403, {'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host}) else: return render_template('error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for the address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() \ or Form(email, host) # or create it if it doesn't exists # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: status = form.send(request.form, request.referrer) else: status = form.send_confirmation() # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({ 'success': "email sent", 'next': status['next'] }) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template('error.html', title='Can\'t send an empty form', text=str('<a href="%s">Return to form</a>' % request.referrer)), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=host) if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email'), 500
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' g.log = g.log.bind(target=email_or_string) if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template('info.html', title='Form should POST', text='Make sure your form has the <span class="code"><strong>method="POST"</strong></span> attribute'), 405 host = referrer_to_path(request.referrer) if not host: if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template('error.html', title='Unable to submit form', text='<p>Make sure you open this page through a web server, Formspree will not work in pages browsed as HTML files. Also make sure that you\'re posting to <b>https://</b>{host}.</p><p>For geeks: could not find the "Referrer" header.</p>'.format(host=request.url.split('//')[1])), 400 g.log = g.log.bind(host=host, wants='json' if request_wants_json() else 'html') g.log.info('Received submission.') if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: if form.disabled: # owner has disabled the form, so it should not receive any submissions if request_wants_json(): return jsonerror(403, {'error': 'Form not active'}) else: return render_template('error.html', title='Form not active', text='The owner of this form has disabled this form and it is no longer accepting submissions. Your submissions was not accepted'), 403 email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() # it is an error when # form is sitewide, but submission came from a host rooted somewhere else, or # form is not sitewide, and submission came from a different host elif (not form.sitewide and form.host != host) or ( form.sitewide and ( not host.startswith(form.host) and \ not remove_www(host).startswith(form.host) ) ): g.log.info('Submission rejected. From a different host than confirmed.') if request_wants_json(): return jsonerror(403, { 'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host }) else: return render_template('error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. g.log.info('Submission rejected. No form found for this target.') if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string.lower() # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() \ or Form(email, host) # or create it if it doesn't exists if form.disabled: g.log.info('submission rejected. Form is disabled.') if request_wants_json(): return jsonerror(403, {'error': 'Form not active'}) else: return render_template('error.html', title='Form not active', text='The owner of this form has disabled this form and it is no longer accepting submissions. Your submissions was not accepted'), 403 # If form exists and is confirmed, send email # otherwise send a confirmation email received_data = request.form or request.get_json() or {} if form.confirmed: status = form.send(received_data, request.referrer) else: status = form.send_confirmation(received_data) # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({'success': "email sent", 'next': status['next']}) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template('error.html', title='Can\'t send an empty form', text=str('<p>Make sure you have placed the <a href="http://www.w3schools.com/tags/att_input_name.asp" target="_blank">"name" attribute</a> in all your form elements. Also, to prevent empty form submissions, take a look at the <a href="http://www.w3schools.com/tags/att_input_required.asp" target="_blank">"required" property</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input" target="_blank">see more HTML form customization info</a>.</p><p><a href="%s">Return to form</a></p>' % request.referrer)), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=host, resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED ) elif status['code'] == Form.STATUS_OVERLIMIT: if request_wants_json(): return jsonify({'error': "form over quota"}) else: return render_template('error.html', title='Form over quota', text='It looks like this form is getting a lot of submissions and ran out of its quota. Try contacting this website through other means or try submitting again later.'), 402 elif status['code'] == Form.STATUS_REPLYTO_ERROR: if request_wants_json(): return jsonerror(500, {'error': "_replyto or email field has not been sent correctly"}) else: return render_template('error.html', title='Invalid email address', text='You entered <span class="code">{address}</span>. That is an invalid email address. Please correct the form and try to submit again <a href="{back}">here</a>.<p style="font-size: small">This could also be a problem with the form. For example, there could be two fields with <span class="code">_replyto</span> or <span class="code">email</span> name attribute. If you suspect the form is broken, please contact the form owner and ask them to investigate</p>'''.format(address=status['address'], back=status['referrer'])), 400 # error fallback -- shouldn't happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email. If you can, please send the link to your form and the error information to <b>{email}</b>. And send them the following: <p><pre><code>{message}</code></pre></p>'.format(message=json.dumps(status), email=settings.CONTACT_EMAIL)), 500
def form_submissions(hashid, format=None): if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_with_hashid(hashid) for cont in form.controllers: if cont.id == current_user.id: break else: if request_wants_json(): return jsonerror(403, {'error': "You do not control this form."}) else: return redirect(url_for('dashboard')) submissions = form.submissions if not format: # normal request. if request_wants_json(): return jsonify({ 'host': form.host, 'email': form.email, 'submissions': [dict(s.data, date=s.submitted_at.isoformat()) for s in submissions] }) else: fields = set() for s in submissions: fields.update(s.data.keys()) fields -= set(EXCLUDE_KEYS) return render_template('forms/submissions.html', form=form, fields=sorted(fields), submissions=submissions ) elif format: # an export request, format can be json or csv if format == 'json': return Response( json.dumps({ 'host': form.host, 'email': form.email, 'submissions': [dict(s.data, date=s.submitted_at.isoformat()) for s in submissions] }, sort_keys=True, indent=2), mimetype='application/json', headers={ 'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.json' \ % (hashid, datetime.datetime.now().isoformat().split('.')[0]) } ) elif format == 'csv': out = io.BytesIO() fieldnames = set(field for sub in submissions for field in sub.data.keys()) fieldnames = ['date'] + sorted(fieldnames) w = csv.DictWriter(out, fieldnames=fieldnames, encoding='utf-8') w.writeheader() for sub in submissions: w.writerow(dict(sub.data, date=sub.submitted_at.isoformat())) return Response( out.getvalue(), mimetype='text/csv', headers={ 'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.csv' \ % (hashid, datetime.datetime.now().isoformat().split('.')[0]) } )
def create_form(): # create a new form if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') url = request.get_json().get('url') sitewide = request.get_json().get('sitewide') else: email = request.form.get('email') url = request.form.get('url') sitewide = request.form.get('sitewide') if not IS_VALID_EMAIL(email): if request_wants_json(): return jsonerror(400, {'error': "The provided email address is not valid."}) else: flash('The provided email address is not valid.', 'error') return redirect(url_for('dashboard')) form = Form(email, owner=current_user) if url: url = 'http://' + url if not url.startswith('http') else url form.host = referrer_to_path(url) # sitewide forms, verified with a file at the root of the target domain if sitewide: if sitewide_file_exists(url, email): form.host = remove_www(referrer_to_path(urljoin(url, '/'))[:-1]) form.sitewide = True else: return jsonerror(403, {'error': "Couldn't find the verification file."}) DB.session.add(form) DB.session.commit() if form.host: # when the email and url are provided, we can automatically confirm the form # but only if the email is registered for this account for email in current_user.emails: if email.address == form.email: form.confirmed = True DB.session.add(form) DB.session.commit() break else: # in case the email isn't registered for this user # we automatically send the email confirmation form.send_confirmation() if request_wants_json(): return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid, 'confirmed': form.confirmed }) else: flash('Your new form endpoint was created!', 'success') return redirect(url_for('dashboard') + '#view-code-' + form.hashid)
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' g.log = g.log.bind(target=email_or_string) if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template('info.html', title='Form should POST', text='Make sure your form has the <span ' 'class="code"><strong>method="POST"' '</strong></span> attribute'), 405 if request.form: received_data, sorted_keys = http_form_to_dict(request.form) else: received_data = request.get_json() or {} sorted_keys = received_data.keys() try: # Get stored hostname from redis (from captcha) host, referrer = get_temp_hostname(received_data['_host_nonce']) except KeyError: host, referrer = referrer_to_path(request.referrer), request.referrer if not host or host == 'www.google.com': if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template( 'error.html', title='Unable to submit form', text= '<p>Make sure you open this page through a web server, Formspree will not work in pages browsed as HTML files. Also make sure that you\'re posting to <b>{host}{path}</b>.</p><p>For geeks: could not find the "Referrer" header.</p>' .format(host=settings.SERVICE_URL, path=request.path)), 400 g.log = g.log.bind(host=host, wants='json' if request_wants_json() else 'html') g.log.info('Received submission.') if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: # Check if it has been assigned about using AJAX or not assign_ajax(form, request_wants_json()) if form.disabled: # owner has disabled the form, so it should not receive any submissions if request_wants_json(): return jsonerror(403, {'error': 'Form not active'}) else: return render_template( 'error.html', title='Form not active', text= 'The owner of this form has disabled this form and it is no longer accepting submissions. Your submissions was not accepted' ), 403 email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() # it is an error when # form is sitewide, but submission came from a host rooted somewhere else, or # form is not sitewide, and submission came from a different host elif (not form.sitewide and form.host != host) or ( form.sitewide and ( not host.startswith(form.host) and \ not remove_www(host).startswith(form.host) ) ): g.log.info( 'Submission rejected. From a different host than confirmed.' ) if request_wants_json(): return jsonerror( 403, { 'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host }) else: return render_template( 'error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. g.log.info('Submission rejected. No form found for this target.') if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string.lower() # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() \ or Form(email, host) # or create it if it doesn't exists # Check if it has been assigned about using AJAX or not assign_ajax(form, request_wants_json()) if form.disabled: g.log.info('submission rejected. Form is disabled.') if request_wants_json(): return jsonerror(403, {'error': 'Form not active'}) else: return render_template( 'error.html', title='Form not active', text= 'The owner of this form has disabled this form and it is no longer accepting submissions. Your submissions was not accepted' ), 403 # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: captcha_verified = verify_captcha(received_data, request) needs_captcha = not (request_wants_json() or captcha_verified or settings.TESTING) # if form is upgraded check if captcha is disabled if form.upgraded: needs_captcha = needs_captcha and not form.captcha_disabled if needs_captcha: data_copy = received_data.copy() # Temporarily store hostname in redis while doing captcha nonce = temp_store_hostname(form.host, request.referrer) data_copy['_host_nonce'] = nonce action = urljoin(settings.API_ROOT, email_or_string) return render_template('forms/captcha.html', data=data_copy, sorted_keys=sorted_keys, action=action) status = form.send(received_data, sorted_keys, referrer) else: status = form.send_confirmation() # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({'success': "email sent", 'next': status['next']}) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template( 'error.html', title='Can\'t send an empty form', text= u'<p>Make sure you have placed the <a href="http://www.w3schools.com/tags/att_input_name.asp" target="_blank"><code>"name"</code> attribute</a> in all your form elements. Also, to prevent empty form submissions, take a look at the <a href="http://www.w3schools.com/tags/att_input_required.asp" target="_blank"><code>"required"</code> property</a>.</p><p>This error also happens when you have an <code>"enctype"</code> attribute set in your <code><form></code>, so make sure you don\'t.</p><p><a href="{}">Return to form</a></p>' .format(referrer)), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template( 'forms/confirmation_sent.html', email=email, host=host, resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED) elif status['code'] == Form.STATUS_OVERLIMIT: if request_wants_json(): return jsonify({'error': "form over quota"}) else: return render_template( 'error.html', title='Form over quota', text= 'It looks like this form is getting a lot of submissions and ran out of its quota. Try contacting this website through other means or try submitting again later.' ), 402 elif status['code'] == Form.STATUS_REPLYTO_ERROR: if request_wants_json(): return jsonerror(500, { 'error': "_replyto or email field has not been sent correctly" }) else: return render_template( 'error.html', title='Invalid email address', text= u'You entered <span class="code">{address}</span>. That is an invalid email address. Please correct the form and try to submit again <a href="{back}">here</a>.<p style="font-size: small">This could also be a problem with the form. For example, there could be two fields with <span class="code">_replyto</span> or <span class="code">email</span> name attribute. If you suspect the form is broken, please contact the form owner and ask them to investigate</p>' ''.format(address=status['address'], back=status['referrer'])), 400 # error fallback -- shouldn't happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template( 'error.html', title='Unable to send email', text= u'Unable to send email. If you can, please send the link to your form and the error information to <b>{email}</b>. And send them the following: <p><pre><code>{message}</code></pre></p>' .format(message=json.dumps(status), email=settings.CONTACT_EMAIL)), 500
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template( 'info.html', title='Form should POST', text= 'Make sure your form has the <span class="code"><strong>method="POST"</strong></span> attribute' ), 405 host = referrer_to_path(flask.request.referrer) if not host: if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template( 'error.html', title='Unable to submit form', text= 'Make sure your form is running on a proper server. For geeks: could not find the "Referrer" header.' ), 400 if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() elif form.host != host: # if the form submission came from a different host, it is an error if request_wants_json(): return jsonerror( 403, { 'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host }) else: return render_template( 'error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for the address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() \ or Form(email, host) # or create it if it doesn't exists # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: status = form.send(request.form, request.referrer) else: status = form.send_confirmation() # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({'success': "email sent", 'next': status['next']}) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template('error.html', title='Can\'t send an empty form', text=str('<a href="%s">Return to form</a>' % request.referrer)), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=host) if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email'), 500
def form_submissions(hashid, format=None): if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) form = Form.get_with_hashid(hashid) for cont in form.controllers: if cont.id == current_user.id: break else: if request_wants_json(): return jsonerror(403, {'error': "You do not control this form."}) else: return redirect(url_for('dashboard')) submissions = form.submissions if not format: # normal request. if request_wants_json(): return jsonify({ 'host': form.host, 'email': form.email, 'submissions': [ dict(s.data, date=s.submitted_at.isoformat()) for s in submissions ] }) else: fields = set() for s in submissions: fields.update(s.data.keys()) fields -= EXCLUDE_KEYS return render_template('forms/submissions.html', form=form, fields=sorted(fields), submissions=submissions) elif format: # an export request, format can be json or csv if format == 'json': return Response( json.dumps({ 'host': form.host, 'email': form.email, 'submissions': [dict(s.data, date=s.submitted_at.isoformat()) for s in submissions] }, sort_keys=True, indent=2), mimetype='application/json', headers={ 'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.json' \ % (hashid, datetime.datetime.now().isoformat().split('.')[0]) } ) elif format == 'csv': out = io.BytesIO() fieldnames = set(field for sub in submissions for field in sub.data.keys()) fieldnames = ['date'] + sorted(fieldnames) w = csv.DictWriter(out, fieldnames=fieldnames, encoding='utf-8') w.writeheader() for sub in submissions: w.writerow(dict(sub.data, date=sub.submitted_at.isoformat())) return Response( out.getvalue(), mimetype='text/csv', headers={ 'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.csv' \ % (hashid, datetime.datetime.now().isoformat().split('.')[0]) } )