<commit_before>from __future__ import absolute_import, unicode_literals

import email
import logging
from email.utils import formataddr
from collections import defaultdict

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.mail import EmailMultiAlternatives
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import six, timezone
from django.utils.datastructures import MultiValueDict
from django.utils.six.moves.urllib.parse import urljoin
from djblets.siteconfig.models import SiteConfiguration
from djblets.auth.signals import user_registered

from reviewboard.admin.server import get_server_url
from reviewboard.reviews.models import Group, ReviewRequest, Review
from reviewboard.reviews.signals import (review_request_published,
                                         review_published, reply_published,
                                         review_request_closed)
from reviewboard.reviews.views import build_diff_comment_fragments


# A mapping of signals to EmailHooks.
_hooks = defaultdict(set)


def _ensure_unicode(text):
    """Return a unicode object for the given text.

    Args:
        text (bytes or unicode):
            The text to decode.

    Returns:
        unicode: The decoded text.
    """
    if isinstance(text, bytes):
        text = text.decode('utf-8')

    return text


def register_email_hook(signal, handler):
    """Register an e-mail hook

    Args:
        signal (django.signals.signal):
            The signal that will trigger the e-mail to be sent. This is one of
            ``review_request_published``, ``review_request_closed``,
            ``review_published``, or ``reply_published.

        handler (reviewboard.extensions.hooks.EmailHook):
            The ``EmailHook`` that will be triggered when an e-mail of the
            chosen type is about to be sent.
    """
    assert signal in (review_request_published, review_request_closed,
                      review_published, reply_published), (
        'Invalid signal %r' % signal)

    _hooks[signal].add(handler)


def unregister_email_hook(signal, handler):
    """Unregister an e-mail hook.

    Args:
        signal (django.signals.signal):
            The signal that will trigger the e-mail to be sent. This is one of
            ``review_request_published``, ``review_request_closed``,
            ``review_published``, or ``reply_published.

        handler (reviewboard.extensions.hooks.EmailHook):
            The ``EmailHook`` that will be triggered when an e-mail of the
            chosen type is about to be sent.
    """
    assert signal in (review_request_published, review_request_closed,
                      review_published, reply_published), (
        'Invalid signal %r' % signal)

    _hooks[signal].discard(handler)


def review_request_closed_cb(sender, user, review_request, type, **kwargs):
    """Send e-mail when a review request is closed.

    Listens to the ``review_request_closed`` signal and sends an e-mail if this
    type of notification is enabled (through the
    ``mail_send_review_close_mail`` site configuration setting).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_review_close_mail'):
        mail_review_request(review_request, user, close_type=type)


def review_request_published_cb(sender, user, review_request, changedesc,
                                **kwargs):
    """Send e-mail when a review request is published.

    Listens to the ``review_request_published`` signal and sends an e-mail if
    this type of notification is enabled through the ``mail_send_review_mail``
    site configuration setting).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_review_mail'):
        mail_review_request(review_request, user, changedesc)


def review_published_cb(sender, user, review, **kwargs):
    """Send e-mail when a review is published.

    Listens to the ``review_published`` signal and sends e-mail if this type of
    notification is enabled through the ``mail_send_review_mail`` site
    configuration setting).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_review_mail'):
        mail_review(review, user)


def reply_published_cb(sender, user, reply, **kwargs):
    """Send e-mail when a review reply is published.

    Listens to the ``reply_published`` signal and sends an e-mail if this type
    of notification is enabled (through ``mail_send_review_mail`` site
    configuration).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_review_mail'):
        mail_reply(reply, user)


def user_registered_cb(user, **kwargs):
    """Send e-mail when a user is registered.

    Listens for new user registrations and sends a new user registration
    e-mail to administrators, if this type of notification is enabled (through
    ``mail_send_new_user_mail`` site configuration).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_new_user_mail'):
        mail_new_user(user)


def connect_signals():
    """Connect e-mail callbacks to signals."""
    review_request_published.connect(review_request_published_cb,
                                     sender=ReviewRequest)
    review_published.connect(review_published_cb, sender=Review)
    reply_published.connect(reply_published_cb, sender=Review)
    review_request_closed.connect(review_request_closed_cb,
                                  sender=ReviewRequest)
    user_registered.connect(user_registered_cb)


def build_email_address(fullname, email):
    """Build an e-mail address for the name and e-mail address.

    Args:
        fullname (unicode):
            The full name associated with the e-mail address (or ``None``).

        email (unicode):
            The e-mail address.

    Returns:
        unicode: A properly formatted e-mail addresss.
    """
    return formataddr((fullname, email))


def get_email_address_for_user(user):
    """Build an e-mail address for the given user.

    Args:
        user (django.contrib.auth.models.User):
            The user.

    Returns:
        unicode: A properly formatted e-mail address for the user.
    """
    return build_email_address(user.get_full_name(), user.email)


def get_email_addresses_for_group(group):
    """Build a list of e-mail addresses for the group.

    Args:
        group (reviewboard.reviews.models.Group):
            The review group to build the e-mail addresses for.

    Returns:
        list: A list of properly formatted e-mail addresses for all users in
        the review group.
    """
    if group.mailing_list:
        if ',' not in group.mailing_list:
            # The mailing list field has only one e-mail address in it,
            # so we can just use that and the group's display name.
            return [build_email_address(group.display_name,
                                        group.mailing_list)]
        else:
            # The mailing list field has multiple e-mail addresses in it.
            # We don't know which one should have the group's display name
            # attached to it, so just return their custom list as-is.
            return group.mailing_list.split(',')
    else:
        users_q = Q(is_active=True)

        local_site = group.local_site

        if local_site:
            users_q = users_q & (Q(local_site=local_site) |
                                 Q(local_site_admins=local_site))

        users = group.users.filter(users_q)

        return [get_email_address_for_user(u)
                for u in users
                if u.should_send_email()]


class SpiffyEmailMessage(EmailMultiAlternatives):
    """An EmailMessage subclass with improved header and message ID support.

    This also knows about several headers (standard and variations),
    including Sender/X-Sender, In-Reply-To/References, and Reply-To.

    The generated Message-ID header from the e-mail can be accessed
    through the :py:attr:`message_id` attribute after the e-mail is sent.

    This class also supports repeated headers.
    """

    def __init__(self, subject, text_body, html_body, from_email, sender,
                 to, cc=None, in_reply_to=None, headers=None):
        siteconfig = SiteConfiguration.objects.get_current()

        headers = headers or MultiValueDict()

        if (isinstance(headers, dict) and
            not isinstance(headers, MultiValueDict)):
            # Instantiating a MultiValueDict from a dict does not ensure that
            # the values are lists, so we have to do that ourselves.
            self._headers = MultiValueDict(dict(
                (key, [value])
                for key, value in six.iteritems(headers)
            ))

        if sender:
            headers['Sender'] = sender
            headers['X-Sender'] = sender

        if in_reply_to:
            headers['In-Reply-To'] = in_reply_to
            headers['References'] = in_reply_to

        headers['Reply-To'] = from_email

        # If enabled (through 'mail_enable_autogenerated_header' site
        # configuration), mark the mail as 'auto-generated' (according to
        # RFC 3834) to hopefully avoid auto replies.
        if siteconfig.get("mail_enable_autogenerated_header"):
            headers['Auto-Submitted'] = 'auto-generated'<commit_msg>Fix external email headers from a dict.<commit_after>from __future__ import absolute_import, unicode_literals

import email
import logging
from email.utils import formataddr
from collections import defaultdict

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.mail import EmailMultiAlternatives
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import six, timezone
from django.utils.datastructures import MultiValueDict
from django.utils.six.moves.urllib.parse import urljoin
from djblets.siteconfig.models import SiteConfiguration
from djblets.auth.signals import user_registered

from reviewboard.admin.server import get_server_url
from reviewboard.reviews.models import Group, ReviewRequest, Review
from reviewboard.reviews.signals import (review_request_published,
                                         review_published, reply_published,
                                         review_request_closed)
from reviewboard.reviews.views import build_diff_comment_fragments


# A mapping of signals to EmailHooks.
_hooks = defaultdict(set)


def _ensure_unicode(text):
    """Return a unicode object for the given text.

    Args:
        text (bytes or unicode):
            The text to decode.

    Returns:
        unicode: The decoded text.
    """
    if isinstance(text, bytes):
        text = text.decode('utf-8')

    return text


def register_email_hook(signal, handler):
    """Register an e-mail hook

    Args:
        signal (django.signals.signal):
            The signal that will trigger the e-mail to be sent. This is one of
            ``review_request_published``, ``review_request_closed``,
            ``review_published``, or ``reply_published.

        handler (reviewboard.extensions.hooks.EmailHook):
            The ``EmailHook`` that will be triggered when an e-mail of the
            chosen type is about to be sent.
    """
    assert signal in (review_request_published, review_request_closed,
                      review_published, reply_published), (
        'Invalid signal %r' % signal)

    _hooks[signal].add(handler)


def unregister_email_hook(signal, handler):
    """Unregister an e-mail hook.

    Args:
        signal (django.signals.signal):
            The signal that will trigger the e-mail to be sent. This is one of
            ``review_request_published``, ``review_request_closed``,
            ``review_published``, or ``reply_published.

        handler (reviewboard.extensions.hooks.EmailHook):
            The ``EmailHook`` that will be triggered when an e-mail of the
            chosen type is about to be sent.
    """
    assert signal in (review_request_published, review_request_closed,
                      review_published, reply_published), (
        'Invalid signal %r' % signal)

    _hooks[signal].discard(handler)


def review_request_closed_cb(sender, user, review_request, type, **kwargs):
    """Send e-mail when a review request is closed.

    Listens to the ``review_request_closed`` signal and sends an e-mail if this
    type of notification is enabled (through the
    ``mail_send_review_close_mail`` site configuration setting).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_review_close_mail'):
        mail_review_request(review_request, user, close_type=type)


def review_request_published_cb(sender, user, review_request, changedesc,
                                **kwargs):
    """Send e-mail when a review request is published.

    Listens to the ``review_request_published`` signal and sends an e-mail if
    this type of notification is enabled through the ``mail_send_review_mail``
    site configuration setting).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_review_mail'):
        mail_review_request(review_request, user, changedesc)


def review_published_cb(sender, user, review, **kwargs):
    """Send e-mail when a review is published.

    Listens to the ``review_published`` signal and sends e-mail if this type of
    notification is enabled through the ``mail_send_review_mail`` site
    configuration setting).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_review_mail'):
        mail_review(review, user)


def reply_published_cb(sender, user, reply, **kwargs):
    """Send e-mail when a review reply is published.

    Listens to the ``reply_published`` signal and sends an e-mail if this type
    of notification is enabled (through ``mail_send_review_mail`` site
    configuration).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_review_mail'):
        mail_reply(reply, user)


def user_registered_cb(user, **kwargs):
    """Send e-mail when a user is registered.

    Listens for new user registrations and sends a new user registration
    e-mail to administrators, if this type of notification is enabled (through
    ``mail_send_new_user_mail`` site configuration).
    """
    siteconfig = SiteConfiguration.objects.get_current()

    if siteconfig.get('mail_send_new_user_mail'):
        mail_new_user(user)


def connect_signals():
    """Connect e-mail callbacks to signals."""
    review_request_published.connect(review_request_published_cb,
                                     sender=ReviewRequest)
    review_published.connect(review_published_cb, sender=Review)
    reply_published.connect(reply_published_cb, sender=Review)
    review_request_closed.connect(review_request_closed_cb,
                                  sender=ReviewRequest)
    user_registered.connect(user_registered_cb)


def build_email_address(fullname, email):
    """Build an e-mail address for the name and e-mail address.

    Args:
        fullname (unicode):
            The full name associated with the e-mail address (or ``None``).

        email (unicode):
            The e-mail address.

    Returns:
        unicode: A properly formatted e-mail addresss.
    """
    return formataddr((fullname, email))


def get_email_address_for_user(user):
    """Build an e-mail address for the given user.

    Args:
        user (django.contrib.auth.models.User):
            The user.

    Returns:
        unicode: A properly formatted e-mail address for the user.
    """
    return build_email_address(user.get_full_name(), user.email)


def get_email_addresses_for_group(group):
    """Build a list of e-mail addresses for the group.

    Args:
        group (reviewboard.reviews.models.Group):
            The review group to build the e-mail addresses for.

    Returns:
        list: A list of properly formatted e-mail addresses for all users in
        the review group.
    """
    if group.mailing_list:
        if ',' not in group.mailing_list:
            # The mailing list field has only one e-mail address in it,
            # so we can just use that and the group's display name.
            return [build_email_address(group.display_name,
                                        group.mailing_list)]
        else:
            # The mailing list field has multiple e-mail addresses in it.
            # We don't know which one should have the group's display name
            # attached to it, so just return their custom list as-is.
            return group.mailing_list.split(',')
    else:
        users_q = Q(is_active=True)

        local_site = group.local_site

        if local_site:
            users_q = users_q & (Q(local_site=local_site) |
                                 Q(local_site_admins=local_site))

        users = group.users.filter(users_q)

        return [get_email_address_for_user(u)
                for u in users
                if u.should_send_email()]


class SpiffyEmailMessage(EmailMultiAlternatives):
    """An EmailMessage subclass with improved header and message ID support.

    This also knows about several headers (standard and variations),
    including Sender/X-Sender, In-Reply-To/References, and Reply-To.

    The generated Message-ID header from the e-mail can be accessed
    through the :py:attr:`message_id` attribute after the e-mail is sent.

    This class also supports repeated headers.
    """

    def __init__(self, subject, text_body, html_body, from_email, sender,
                 to, cc=None, in_reply_to=None, headers=None):
        siteconfig = SiteConfiguration.objects.get_current()

        headers = headers or MultiValueDict()

        if (isinstance(headers, dict) and
            not isinstance(headers, MultiValueDict)):
            # Instantiating a MultiValueDict from a dict does not ensure that
            # the values are lists, so we have to do that ourselves.
            headers = MultiValueDict(dict(
                (key, [value])
                for key, value in six.iteritems(headers)
            ))

        if sender:
            headers['Sender'] = sender
            headers['X-Sender'] = sender

        if in_reply_to:
            headers['In-Reply-To'] = in_reply_to
            headers['References'] = in_reply_to

        headers['Reply-To'] = from_email

        # If enabled (through 'mail_enable_autogenerated_header' site
        # configuration), mark the mail as 'auto-generated' (according to
        # RFC 3834) to hopefully avoid auto replies.
        if siteconfig.get("mail_enable_autogenerated_header"):
            headers['Auto-Submitted'] = 'auto-generated'
{'fraction_non_alphanumeric': 0.0795822454308094, 'fraction_numerical': 0.0005221932114882506, 'mean_word_length': 2.79152643040982, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3609997', 'n_tokens_mistral': 5203, 'n_tokens_neox': 5010, 'n_words': 1615}