def test_next_url(client): # thanks route should have the referrer as its 'next' assert "/thanks?next=http%3A%2F%2Ffun.io" == next_url(referrer="http://fun.io") # No referrer and relative next url should result in proper relative next url. assert "/thank-you" == next_url(next="/thank-you") # No referrer and absolute next url should result in proper absolute next url. assert "http://somesite.org/thank-you" == next_url( next="http://somesite.org/thank-you" ) # Referrer set and relative next url should result in proper absolute next url. assert "http://fun.io/" == next_url(referrer="http://fun.io", next="/") assert "http://fun.io/thanks.html" == next_url( referrer="http://fun.io", next="thanks.html" ) assert "http://fun.io/thanks.html" == next_url( referrer="http://fun.io", next="/thanks.html" ) # Referrer set and absolute next url should result in proper absolute next url. assert "https://morefun.net/awesome.php" == next_url( referrer="https://fun.io", next="//morefun.net/awesome.php" ) assert "http://morefun.net/awesome.php" == next_url( referrer="http://fun.io", next="//morefun.net/awesome.php" )
def test_next_url(self): # thanks route should have the referrer as its 'next' self.assertEqual('/thanks?next=http%3A%2F%2Ffun.io', next_url(referrer='http://fun.io')) # No referrer and relative next url should result in proper relative next url. self.assertEqual('/thank-you', next_url(next='/thank-you')) # No referrer and absolute next url should result in proper absolute next url. self.assertEqual('http://somesite.org/thank-you', next_url(next='http://somesite.org/thank-you')) # Referrer set and relative next url should result in proper absolute next url. self.assertEqual('http://fun.io/', next_url(referrer='http://fun.io', next='/')) self.assertEqual( 'http://fun.io/thanks.html', next_url(referrer='http://fun.io', next='thanks.html')) self.assertEqual( 'http://fun.io/thanks.html', next_url(referrer='http://fun.io', next='/thanks.html')) # Referrer set and absolute next url should result in proper absolute next url. self.assertEqual( 'https://morefun.net/awesome.php', next_url(referrer='https://fun.io', next='//morefun.net/awesome.php')) self.assertEqual( 'http://morefun.net/awesome.php', next_url(referrer='http://fun.io', next='//morefun.net/awesome.php'))
def test_next_url(client): # thanks route should have the referrer as its 'next' assert '/thanks?next=http%3A%2F%2Ffun.io' == next_url(referrer='http://fun.io') # No referrer and relative next url should result in proper relative next url. assert '/thank-you' == next_url(next='/thank-you') # No referrer and absolute next url should result in proper absolute next url. assert 'http://somesite.org/thank-you' == next_url(next='http://somesite.org/thank-you') # Referrer set and relative next url should result in proper absolute next url. assert 'http://fun.io/' == next_url(referrer='http://fun.io', next='/') assert 'http://fun.io/thanks.html' == next_url(referrer='http://fun.io', next='thanks.html') assert 'http://fun.io/thanks.html' == next_url(referrer='http://fun.io', next='/thanks.html') # Referrer set and absolute next url should result in proper absolute next url. assert 'https://morefun.net/awesome.php' == next_url(referrer='https://fun.io', next='//morefun.net/awesome.php') assert 'http://morefun.net/awesome.php' == next_url(referrer='http://fun.io', next='//morefun.net/awesome.php')
def test_next_url(self): # No referrer and no next should default to thanks route. self.assertEqual('/thanks', next_url()) # Referrer set but no next should default to thanks route. self.assertEqual('/thanks', next_url(referrer='http://fun.io')) # No referrer and relative next url should result in proper relative next url. self.assertEqual('/thank-you', next_url(next='/thank-you')) # No referrer and absolute next url should result in proper absolute next url. self.assertEqual('http://somesite.org/thank-you', next_url(next='http://somesite.org/thank-you')) # Referrer set and relative next url should result in proper absolute next url. self.assertEqual('http://fun.io/', next_url(referrer='http://fun.io', next='/')) self.assertEqual('http://fun.io/thanks.html', next_url(referrer='http://fun.io', next='thanks.html')) self.assertEqual('http://fun.io/thanks.html', next_url(referrer='http://fun.io', next='/thanks.html')) # Referrer set and absolute next url should result in proper absolute next url. self.assertEqual('//morefun.net/awesome.php', next_url(referrer='http://fun.io', next='//morefun.net/awesome.php'))
def test_next_url(self): # thanks route should have the referrer as its 'next' self.assertEqual('/thanks?next=http%3A%2F%2Ffun.io', next_url(referrer='http://fun.io')) # No referrer and relative next url should result in proper relative next url. self.assertEqual('/thank-you', next_url(next='/thank-you')) # No referrer and absolute next url should result in proper absolute next url. self.assertEqual('http://somesite.org/thank-you', next_url(next='http://somesite.org/thank-you')) # Referrer set and relative next url should result in proper absolute next url. self.assertEqual('http://fun.io/', next_url(referrer='http://fun.io', next='/')) self.assertEqual('http://fun.io/thanks.html', next_url(referrer='http://fun.io', next='thanks.html')) self.assertEqual('http://fun.io/thanks.html', next_url(referrer='http://fun.io', next='/thanks.html')) # Referrer set and absolute next url should result in proper absolute next url. self.assertEqual('//morefun.net/awesome.php', next_url(referrer='http://fun.io', next='//morefun.net/awesome.php'))
def submit(self, data, keys, referrer): """ Submits a form. Ensure data is well-formed. Create a new submission. Dispatch to worker to process submission. """ keys = [k for k in keys if k not in KEYS_EXCLUDED_FROM_EMAIL] next = next_url(referrer, data.get("_next")) # prevent submitting empty form if not any(data.values()): return {"code": Form.STATUS_SUBMISSION_EMPTY} # return a fake success for spam if data.get("_gotcha", None): g.log.info("Submission rejected.", gotcha=data.get("_gotcha")) return {"code": Form.STATUS_SUBMISSION_ENQUEUED, "next": next} # validate reply_to, if it is not a valid email address, reject reply_to = get_replyto(data) if reply_to and not is_valid_email(reply_to): g.log.info("Submission rejected. Reply-To is invalid.", reply_to=reply_to) return { "code": Form.STATUS_REPLYTO_ERROR, "address": reply_to, "referrer": referrer, } # the Submission object submission = Submission(self.id) submission.data = {key: data[key] for key in data if key not in KEYS_NOT_STORED} submission.host = referrer DB.session.add(submission) DB.session.commit() process_and_commit_submission.delay(submission.id, list(keys)) g.log.info( "Submission enqueued.", form_id=self.id, submission_id=submission.id, email=self.email, ) return {"code": Form.STATUS_SUBMISSION_ENQUEUED, "next": next}
def test_next_url(client): # thanks route should have the referrer as its 'next' assert "/thanks?next=http%3A%2F%2Ffun.io" == next_url( referrer="http://fun.io") # No referrer and relative next url should result in proper relative next url. assert "/thank-you" == next_url(next="/thank-you") # No referrer and absolute next url should result in proper absolute next url. assert "http://somesite.org/thank-you" == next_url( next="http://somesite.org/thank-you") # Referrer set and relative next url should result in proper absolute next url. assert "http://fun.io/" == next_url(referrer="http://fun.io", next="/") assert "http://fun.io/thanks.html" == next_url(referrer="http://fun.io", next="thanks.html") assert "http://fun.io/thanks.html" == next_url(referrer="http://fun.io", next="/thanks.html") # Referrer set and absolute next url should result in proper absolute next url. assert "https://morefun.net/awesome.php" == next_url( referrer="https://fun.io", next="//morefun.net/awesome.php") assert "http://morefun.net/awesome.php" == next_url( referrer="http://fun.io", next="//morefun.net/awesome.php")
def test_next_url(client): # thanks route should have the referrer as its 'next' assert '/thanks?next=http%3A%2F%2Ffun.io' == next_url( referrer='http://fun.io') # No referrer and relative next url should result in proper relative next url. assert '/thank-you' == next_url(next='/thank-you') # No referrer and absolute next url should result in proper absolute next url. assert 'http://somesite.org/thank-you' == next_url( next='http://somesite.org/thank-you') # Referrer set and relative next url should result in proper absolute next url. assert 'http://fun.io/' == next_url(referrer='http://fun.io', next='/') assert 'http://fun.io/thanks.html' == next_url(referrer='http://fun.io', next='thanks.html') assert 'http://fun.io/thanks.html' == next_url(referrer='http://fun.io', next='/thanks.html') # Referrer set and absolute next url should result in proper absolute next url. assert 'https://morefun.net/awesome.php' == next_url( referrer='https://fun.io', next='//morefun.net/awesome.php') assert 'http://morefun.net/awesome.php' == next_url( referrer='http://fun.io', next='//morefun.net/awesome.php')
def send(self, data, keys, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' subject = data.get('_subject') or \ 'New submission from %s' % referrer_to_path(referrer) reply_to = (data.get('_replyto', data.get('email', data.get('Email'))) or '').strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return {'code': Form.STATUS_EMAIL_EMPTY} # return a fake success for spam if spam: g.log.info('Submission rejected.', gotcha=spam) return {'code': Form.STATUS_EMAIL_SENT, 'next': next} # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): g.log.info('Submission rejected. Reply-To is invalid.', reply_to=reply_to) return { 'code': Form.STATUS_REPLYTO_ERROR, 'address': reply_to, 'referrer': referrer } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 # if submission storage is disabled and form is upgraded, don't store submission if self.disable_storage and self.upgraded: pass else: DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = { key: data[key] for key in data if key not in KEYS_NOT_STORED } DB.session.add(sub) # commit changes DB.session.commit() # sometimes we'll delete all archived submissions over the limit if random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY: records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT total_records = DB.session.query(func.count(Submission.id)) \ .filter_by(form_id=self.id) \ .scalar() if total_records > records_to_keep: newest = self.submissions.with_entities( Submission.id).limit(records_to_keep) DB.engine.execute( delete(table('submissions')). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT and not self.upgraded: overlimit = True if monthly_counter == int(settings.MONTHLY_SUBMISSIONS_LIMIT * 0.9) and not self.upgraded: # send email notification send_email(to=self.email, subject="[WARNING] Approaching submission limit", text=render_template('email/90-percent-warning.txt'), html=render_template('email/90-percent-warning.html'), sender=settings.DEFAULT_SENDER) now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now) else: html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: g.log.info('Submission rejected. Form over quota.', monthly_counter=monthly_counter) # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return {'code': Form.STATUS_OVERLIMIT} text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) # if emails are disabled and form is upgraded, don't send email notification if self.disable_email and self.upgraded: return {'code': Form.STATUS_NO_EMAIL, 'next': next} else: result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc, headers={ 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click', 'List-Unsubscribe': '<' + url_for('unconfirm_form', form_id=self.id, digest=self.unconfirm_digest(), _external=True) + '>' }) if not result[0]: g.log.warning('Failed to send email.', reason=result[1], code=result[2]) if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR, 'address': reply_to, 'referrer': referrer } return { 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return {'code': Form.STATUS_EMAIL_SENT, 'next': next}
def send(self, http_form, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' data, keys = http_form_to_dict(http_form) subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', None))) cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) # prevent submitting empty form if not any(data.values()): return { 'code': Form.STATUS_EMAIL_EMPTY } # return a fake success for spam if spam: return { 'code': Form.STATUS_EMAIL_SENT, 'next': next } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT: overlimit = True if self.controllers: for c in self.controllers: if c.upgraded: overlimit = False break now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return { 'code': Form.STATUS_EMAIL_FAILED } text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc) if not result[0]: return{ 'code': Form.STATUS_EMAIL_FAILED } return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
def send(self, submitted_data, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' if type(submitted_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict): data, keys = http_form_to_dict(submitted_data) else: data, keys = submitted_data, submitted_data.keys() subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', ''))).strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return { 'code': Form.STATUS_EMAIL_EMPTY } # return a fake success for spam if spam: g.log.info('Submission rejected.', gotcha=spam) return { 'code': Form.STATUS_EMAIL_SENT, 'next': next } # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): g.log.info('Submission rejected. Reply-To is invalid.', reply_to=reply_to) return { 'code': Form.STATUS_REPLYTO_ERROR, 'error-message': '"%s" is not a valid email address.' % reply_to } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT: overlimit = True if self.controllers: for c in self.controllers: if c.upgraded: overlimit = False break now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now) else: html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: g.log.info('Submission rejected. Form over quota.', monthly_counter=monthly_counter) # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return { 'code': Form.STATUS_OVERLIMIT } text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email( to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc ) if not result[0]: g.log.warning('Failed to send email.', reason=result[1], code=result[2]) if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR} return{ 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
def send(self, submitted_data, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' if type(submitted_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict): data, keys = http_form_to_dict(submitted_data) else: data, keys = submitted_data, submitted_data.keys() subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', ''))).strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return { 'code': Form.STATUS_EMAIL_EMPTY } # return a fake success for spam if spam: return { 'code': Form.STATUS_EMAIL_SENT, 'next': next } # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): return { 'code': Form.STATUS_REPLYTO_ERROR, 'error-message': '"%s" is not a valid email address.' % reply_to } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT: overlimit = True if self.controllers: for c in self.controllers: if c.upgraded: overlimit = False break now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now) else: html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return { 'code': Form.STATUS_OVERLIMIT } text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc) if not result[0]: if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR} return{ 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
def send(self, http_form, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' data, keys = http_form_to_dict(http_form) subject = data.get( '_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', None))) cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) # prevent submitting empty form if not any(data.values()): return {'code': Form.STATUS_EMAIL_EMPTY} # return a fake success for spam if spam: return {'code': Form.STATUS_EMAIL_SENT, 'next': next} # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities( Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False if self.get_monthly_counter( basedate=request_date) > settings.MONTHLY_SUBMISSIONS_LIMIT: if not self.owner or not self.owner.upgraded: overlimit = True now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc) if not result[0]: return {'code': Form.STATUS_EMAIL_FAILED} return {'code': Form.STATUS_EMAIL_SENT, 'next': next}
def send(self, data, keys, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' subject = data.get('_subject') or \ 'New submission from %s' % referrer_to_path(referrer) reply_to = (data.get( '_replyto', data.get('email', data.get('Email')) ) or '').strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return {'code': Form.STATUS_EMAIL_EMPTY} # return a fake success for spam if spam: g.log.info('Submission rejected.', gotcha=spam) return {'code': Form.STATUS_EMAIL_SENT, 'next': next} # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): g.log.info('Submission rejected. Reply-To is invalid.', reply_to=reply_to) return { 'code': Form.STATUS_REPLYTO_ERROR, 'address': reply_to, 'referrer': referrer } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 # if submission storage is disabled and form is upgraded, don't store submission if self.disable_storage and self.upgraded: pass else: DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = {key: data[key] for key in data if key not in KEYS_NOT_STORED} DB.session.add(sub) # commit changes DB.session.commit() # sometimes we'll delete all archived submissions over the limit if random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY: records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT total_records = DB.session.query(func.count(Submission.id)) \ .filter_by(form_id=self.id) \ .scalar() if total_records > records_to_keep: newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete(table('submissions')). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # url to request_unconfirm_form page unconfirm = url_for('request_unconfirm_form', form_id=self.id, _external=True) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() monthly_limit = settings.MONTHLY_SUBMISSIONS_LIMIT \ if self.id > settings.FORM_LIMIT_DECREASE_ACTIVATION_SEQUENCE \ else settings.GRANDFATHER_MONTHLY_LIMIT if monthly_counter > monthly_limit and not self.upgraded: overlimit = True if monthly_counter == int(monthly_limit * 0.9) and not self.upgraded: # send email notification send_email( to=self.email, subject="Formspree Notice: Approaching submission limit.", text=render_template('email/90-percent-warning.txt', unconfirm_url=unconfirm, limit=monthly_limit ), html=render_template_string( TEMPLATES.get('90-percent-warning.html'), unconfirm_url=unconfirm, limit=monthly_limit ), sender=settings.DEFAULT_SENDER ) now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: g.log.info('Submitted.') text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now, unconfirm_url=unconfirm) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now, unconfirm_url=unconfirm) else: html = render_template_string(TEMPLATES.get('form.html'), data=data, host=self.host, keys=keys, now=now, unconfirm_url=unconfirm) else: g.log.info('Submission rejected. Form over quota.', monthly_counter=monthly_counter) # send an overlimit notification for the first x overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. if monthly_counter <= monthly_limit + settings.OVERLIMIT_NOTIFICATION_QUANTITY: subject = 'Formspree Notice: Your submission limit has been reached.' text = render_template('email/overlimit-notification.txt', host=self.host, unconfirm_url=unconfirm, limit=monthly_limit) html = render_template_string(TEMPLATES.get('overlimit-notification.html'), host=self.host, unconfirm_url=unconfirm, limit=monthly_limit) else: return {'code': Form.STATUS_OVERLIMIT} # if emails are disabled and form is upgraded, don't send email notification if self.disable_email and self.upgraded: return {'code': Form.STATUS_NO_EMAIL, 'next': next} else: result = send_email( to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc, headers={ 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click', 'List-Unsubscribe': '<' + url_for( 'unconfirm_form', form_id=self.id, digest=self.unconfirm_digest(), _external=True ) + '>' } ) if not result[0]: g.log.warning('Failed to send email.', reason=result[1], code=result[2]) if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR, 'address': reply_to, 'referrer': referrer } return { 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return {'code': Form.STATUS_EMAIL_SENT, 'next': next}