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"
    )
Example #2
0
    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')
Example #4
0
    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'))
Example #5
0
    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'))
Example #6
0
    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}
Example #7
0
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")
Example #8
0
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')
Example #9
0
    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}
Example #10
0
    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 }
Example #11
0
    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 }
Example #12
0
    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 }
Example #13
0
    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}
Example #14
0
    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}