Ejemplo n.º 1
0
class BaseHandler(webapp.RequestHandler):
    # Handlers that don't need a repository name can set this to False.
    repo_required = True

    # Handlers that require HTTPS can set this to True.
    https_required = False

    # Set this to True to enable a handler even for deactivated repositories.
    ignore_deactivation = False

    # Handlers that require an admin permission must set this to True.
    admin_required = False

    # List all accepted query parameters here with their associated validators.
    auto_params = {
        'add_note': validate_yes,
        'age': validate_age,
        'alternate_family_names': strip,
        'alternate_given_names': strip,
        'author_email': strip,
        'author_made_contact': validate_yes,
        'author_name': strip,
        'author_phone': strip,
        'believed_dead_permission': validate_checkbox_as_bool,
        'cache_seconds': validate_cache_seconds,
        'clone': validate_yes,
        'confirm': validate_yes,
        'contact_email': strip,
        'contact_name': strip,
        'content_id': strip,
        'context': strip,
        'cursor': strip,
        'date_of_birth': validate_approximate_date,
        'description': strip,
        'domain_write_permission': strip,
        'dupe_notes': validate_yes,
        'email_of_found_person': strip,
        'error': strip,
        'expiry_option': validate_expiry,
        'family_name': strip,
        'full_read_permission': validate_checkbox_as_bool,
        'given_name': strip,
        'home_city': strip,
        'home_country': strip,
        'home_neighborhood': strip,
        'home_postal_code': strip,
        'home_state': strip,
        'home_street': strip,
        'id': strip,
        'id1': strip,
        'id2': strip,
        'id3': strip,
        'is_valid': validate_checkbox_as_bool,
        'key': strip,
        'lang': validate_lang,
        'last_known_location': strip,
        'mark_notes_reviewed': validate_checkbox_as_bool,
        'max_results': validate_int,
        'min_entry_date': validate_datetime,
        'new_repo': validate_repo,
        'note_photo': validate_image,
        'note_photo_url': strip,
        'omit_notes': validate_yes,
        'operation': strip,
        'organization_name': strip,
        'person_record_id': strip,
        'phone_of_found_person': strip,
        'photo': validate_image,
        'photo_url': strip,
        'profile_url1': strip,
        'profile_url2': strip,
        'profile_url3': strip,
        'query': strip,
        'query_type': strip,
        'read_permission': validate_checkbox_as_bool,
        'referrer': strip,
        'resource_bundle': validate_resource_name,
        'resource_bundle_default': validate_resource_name,
        'resource_bundle_original': validate_resource_name,
        'resource_lang': validate_lang,
        'resource_name': validate_resource_name,
        'role': validate_role,
        'search_engine_id': validate_int,
        'search_permission': validate_checkbox_as_bool,
        'sex': validate_sex,
        'signature': strip,
        'skip': validate_int,
        'small': validate_yes,
        'source': strip,
        'source_date': strip,
        'source_name': strip,
        'source_url': strip,
        'stats_permission': validate_checkbox_as_bool,
        'status': validate_status,
        'style': strip,
        'subscribe': validate_checkbox,
        'subscribe_email': strip,
        'subscribe_permission': validate_checkbox_as_bool,
        'suppress_redirect': validate_yes,
        'target': strip,
        'text': strip,
        'ui': strip_and_lower,
        'utcnow': validate_timestamp,
        'version': validate_version,
    }

    def redirect(self, path, repo=None, permanent=False, **params):
        # This will prepend the repo to the path to create a working URL,
        # unless the path has a global prefix or is an absolute URL.
        if re.match('^[a-z]+:', path) or GLOBAL_PATH_RE.match(path):
            if params:
                path += '?' + urlencode(params, self.charset)
        else:
            path = self.get_url(path, repo, **params)
        return webapp.RequestHandler.redirect(self, path, permanent=permanent)

    def render(self,
               name,
               language_override=None,
               cache_seconds=0,
               get_vars=lambda: {},
               **vars):
        """Renders a template to the output stream, passing in the variables
        specified in **vars as well as any additional variables returned by
        get_vars().  Since this is intended for use by a dynamic page handler,
        caching is off by default; if cache_seconds is positive, then
        get_vars() will be called only when cached content is unavailable."""
        self.write(
            self.render_to_string(name, language_override, cache_seconds,
                                  get_vars, **vars))

    def render_to_string(self,
                         name,
                         language_override=None,
                         cache_seconds=0,
                         get_vars=lambda: {},
                         **vars):
        """Renders a template to a string, passing in the variables specified
        in **vars as well as any additional variables returned by get_vars().
        Since this is intended for use by a dynamic page handler, caching is
        off by default; if cache_seconds is positive, then get_vars() will be
        called only when cached content is unavailable."""
        # TODO(kpy): Make the contents of extra_key overridable by callers?
        lang = language_override or self.env.lang
        extra_key = (self.env.repo, self.env.charset,
                     self.request.query_string)

        def get_all_vars():
            vars.update(get_vars())
            for key in ('env', 'config', 'params'):
                if key in vars:
                    raise Exception(
                        'Cannot use "%s" as a key in vars. It is reserved.' %
                        key)
            vars['env'] = self.env  # pass along application-wide context
            vars['config'] = self.config  # pass along the configuration
            vars['params'] = self.params  # pass along the query parameters
            return vars

        return resources.get_rendered(name, lang, extra_key, get_all_vars,
                                      cache_seconds)

    def error(self, code, message='', message_html=''):
        self.info(code, message, message_html, style='error')

    def info(self, code, message='', message_html='', style='info'):
        """Renders a simple page with a message.

        Args:
          code: HTTP status code.
          message: A message in plain text.
          message_html: A message in HTML.
          style: 'info', 'error' or 'plain'. 'info' and 'error' differs in
              appearance. 'plain' just renders the message without extra
              HTML tags. Good for API response.
        """
        is_error = 400 <= code < 600
        if is_error:
            webapp.RequestHandler.error(self, code)
        else:
            self.response.set_status(code)
        if not message and not message_html:
            message = '%d: %s' % (code, httplib.responses.get(code))
        if style == 'plain':
            self.__render_plain_message(message, message_html)
        else:
            try:
                self.render('message.html',
                            cls=style,
                            message=message,
                            message_html=message_html)
            except:
                self.__render_plain_message(message, message_html)
        self.terminate_response()

    def __render_plain_message(self, message, message_html):
        self.response.out.write(
            django.utils.html.escape(message) +
            ('<p>' if message and message_html else '') + message_html)

    def terminate_response(self):
        """Prevents any further output from being written."""
        self.response.out.write = lambda *args: None
        self.get = lambda *args: None
        self.post = lambda *args: None

    def write(self, text):
        """Sends text to the client using the charset from select_charset()."""
        self.response.out.write(text.encode(self.env.charset, 'replace'))

    def get_url(self, action, repo=None, scheme=None, **params):
        """Constructs the absolute URL for a given action and query parameters,
        preserving the current repo and the parameters listed in
        PRESERVED_QUERY_PARAM_NAMES."""
        return get_url(self.request,
                       repo or self.env.repo,
                       action,
                       charset=self.env.charset,
                       scheme=scheme,
                       **params)

    @staticmethod
    def add_task_for_repo(repo, name, action, **kwargs):
        """Queues up a task for an individual repository."""
        task_name = '%s-%s-%s' % (repo, name, int(time.time() * 1000))
        path = '/%s/%s' % (repo, action)
        taskqueue.add(name=task_name, method='GET', url=path, params=kwargs)

    def send_mail(self, to, subject, body):
        """Sends e-mail using a sender address that's allowed for this app."""
        app_id = get_app_name()
        sender = 'Do not reply <do-not-reply@%s.%s>' % (app_id, EMAIL_DOMAIN)
        logging.info('Add mail task: recipient %r, subject %r' % (to, subject))
        taskqueue.add(queue_name='send-mail',
                      url='/global/admin/send_mail',
                      params={
                          'sender': sender,
                          'to': to,
                          'subject': subject,
                          'body': body
                      })

    def get_captcha_html(self, error_code=None, use_ssl=False):
        """Generates the necessary HTML to display a CAPTCHA validation box."""

        # We use the 'custom_translations' parameter for UI messages, whereas
        # the 'lang' parameter controls the language of the challenge itself.
        # reCAPTCHA falls back to 'en' if this parameter isn't recognized.
        lang = self.env.lang.split('-')[0]

        return captcha.get_display_html(
            site_key=config.get('captcha_site_key'),
            use_ssl=use_ssl,
            error=error_code,
            lang=lang)

    def get_captcha_response(self):
        """Returns an object containing the CAPTCHA response information for the
        given request's CAPTCHA field information."""
        captcha_response = self.request.get('g-recaptcha-response')
        return captcha.submit(captcha_response)

    def handle_exception(self, exception, debug_mode):
        logging.error(traceback.format_exc())
        self.error(
            500,
            _('There was an error processing your request.  Sorry for the '
              'inconvenience.  Our administrators will investigate the source '
              'of the problem, but please check that the format of your '
              'request is correct.'))

    def __get_env_language_for_babel(self):
        language_code = self.env.lang
        # A hack to avoid rejecting zh-hk locale.
        # This corresponds to the hack with LANGUAGE_SYNONYMS in const.py.
        # TODO: remove these 2 lines when a original code can be passed to here.
        if language_code == 'zhhk':
            language_code = 'zh-hk'
        try:
            return babel.Locale.parse(language_code, sep='-')
        except babel.UnknownLocaleError as e:
            # fallback language
            return babel.Locale('en')

    def to_local_time(self, date):
        """Converts a datetime object to the local time configured for the
        current repository.  For convenience, returns None if date is None."""
        # TODO(kpy): This only works for repositories that have a single fixed
        # time zone offset and never use Daylight Saving Time.
        if date:
            if self.config.time_zone_offset:
                return date + timedelta(0, 3600 * self.config.time_zone_offset)
            return date

    def format_datetime_localized(self, dt):
        """Formats a datetime object to a localized human-readable string based
        on the current locale."""
        return format_datetime(dt, locale=self.__get_env_language_for_babel())

    def to_formatted_local_time(self, dt):
        """Converts a datetime object to the local time configured for the
        current repository and formats to a localized human-readable string
        based on the current locale."""
        dt = self.to_local_time(dt)
        return self.format_datetime_localized(dt)

    def maybe_redirect_for_repo_alias(self, request):
        """If the specified repository name is an alias, redirects to the URL
        with the canonical repository name and returns True. Otherwise returns
        False.
        """
        # Config repo_alias is a dictionary from a repository name alias to
        # its canonical.
        # e.g., {'yol': '2013-yolanda', 'jam': '2014-jammu-kashmir-floods'}
        #
        # A repository name alias can be used instead of the canonical
        # repository name in URLs. This is especially useful combined with
        # the short URL. e.g., You can access
        # https://www.google.org/personfinder/2014-jammu-kashmir-floods
        # by http://g.co/pf/jam .
        if not self.repo:
            return False
        repo_aliases = config.get('repo_aliases', default={})
        if self.repo in repo_aliases:
            canonical_repo = repo_aliases[self.repo]
            params = {}
            for name in request.arguments():
                params[name] = request.get(name)
            # Redirects to the same URL including the query parameters, except
            # for the repository name.
            self.redirect('/' + self.env.action, repo=canonical_repo, **params)
            self.terminate_response()
            return True
        else:
            return False

    def __init__(self, request, response, env):
        webapp.RequestHandler.__init__(self, request, response)
        self.params = Struct()
        self.env = env
        self.repo = env.repo
        self.config = env.config
        self.charset = env.charset

        # Set default Content-Type header.
        self.response.headers['Content-Type'] = ('text/html; charset=%s' %
                                                 self.charset)

        # Validate query parameters.
        for name, validator in self.auto_params.items():
            try:
                value = self.request.get(name, '')
                setattr(self.params, name, validator(value))
            except Exception, e:
                setattr(self.params, name, validator(None))
                return self.error(400, 'Invalid parameter %s: %s' % (name, e))

        # Ensure referrer is in whitelist, if it exists
        if self.params.referrer and (not self.params.referrer
                                     in self.config.referrer_whitelist):
            setattr(self.params, 'referrer', '')

        # Log the User-Agent header.
        sample_rate = float(self.config and self.config.user_agent_sample_rate
                            or 0)
        if random.random() < sample_rate:
            model.UserAgentLog(
                repo=self.repo,
                sample_rate=sample_rate,
                user_agent=self.request.headers.get('User-Agent'),
                lang=lang,
                accept_charset=self.request.headers.get('Accept-Charset', ''),
                ip_address=self.request.remote_addr).put()

        # Check for SSL (unless running on localhost for development).
        if self.https_required and self.env.domain != 'localhost':
            if self.env.scheme != 'https':
                return self.error(403, 'HTTPS is required.')

        # Handles repository alias.
        if self.maybe_redirect_for_repo_alias(request):
            return

        # Check for an authorization key.
        self.auth = None
        if self.params.key:
            if self.repo:
                # check for domain specific one.
                self.auth = model.Authorization.get(self.repo, self.params.key)
            if not self.auth:
                # perhaps this is a global key ('*' for consistency with config).
                self.auth = model.Authorization.get('*', self.params.key)
        if self.auth and not self.auth.is_valid:
            self.auth = None

        # Shows a custom error page here when the user is not an admin
        # instead of "login: admin" in app.yaml
        # If we use it, user can't sign out
        # because the error page of "login: admin" doesn't have sign-out link.
        if self.admin_required:
            user = users.get_current_user()
            if not user:
                login_url = users.create_login_url(self.request.url)
                webapp.RequestHandler.redirect(self, login_url)
                self.terminate_response()
                return
            if not users.is_current_user_admin():
                logout_url = users.create_logout_url(self.request.url)
                self.render('not_admin_error.html',
                            logout_url=logout_url,
                            user=user)
                self.terminate_response()
                return

        # Handlers that don't need a repository configuration can skip it.
        if not self.repo:
            if self.repo_required:
                return self.error(400, 'No repository specified.')
            return
        # Everything after this requires a repo.

        # Reject requests for repositories that don't exist.
        if not model.Repo.get_by_key_name(self.repo):
            html = 'No such repository. '
            if self.env.repo_options:
                html += 'Select:<p>' + self.render_to_string('repo-menu.html')
            return self.error(404, message_html=html)

        # If this repository has been deactivated, terminate with a message.
        # The ignore_deactivation flag is for admin pages that bypass this.
        if self.config.deactivated and not self.ignore_deactivation:
            self.env.language_menu = []
            self.env.robots_ok = True
            self.render('message.html',
                        cls='deactivation',
                        message_html=self.config.deactivation_message_html)
            self.terminate_response()
Ejemplo n.º 2
0
class BaseHandler(webapp.RequestHandler):
    # Handlers that don't need a repository name can set this to False.
    repo_required = True

    # Handlers that require HTTPS can set this to True.
    https_required = False

    # Set this to True to enable a handler even for deactivated repositories.
    ignore_deactivation = False

    # List all accepted query parameters here with their associated validators.
    auto_params = {
        'add_note': validate_yes,
        'age': validate_age,
        'alternate_family_names': strip,
        'alternate_given_names': strip,
        'author_email': strip,
        'author_made_contact': validate_yes,
        'author_name': strip,
        'author_phone': strip,
        'believed_dead_permission': validate_checkbox_as_bool,
        'cache_seconds': validate_cache_seconds,
        'clone': validate_yes,
        'confirm': validate_yes,
        'contact_email': strip,
        'contact_name': strip,
        'content_id': strip,
        'cursor': strip,
        'date_of_birth': validate_approximate_date,
        'description': strip,
        'domain_write_permission': strip,
        'dupe_notes': validate_yes,
        'email_of_found_person': strip,
        'error': strip,
        'expiry_option': validate_expiry,
        'family_name': strip,
        'full_read_permission': validate_checkbox_as_bool,
        'given_name': strip,
        'home_city': strip,
        'home_country': strip,
        'home_neighborhood': strip,
        'home_postal_code': strip,
        'home_state': strip,
        'home_street': strip,
        'id': strip,
        'id1': strip,
        'id2': strip,
        'id3': strip,
        'is_valid': validate_checkbox_as_bool,
        'key': strip,
        'lang': validate_lang,
        'last_known_location': strip,
        'mark_notes_reviewed': validate_checkbox_as_bool,
        'max_results': validate_int,
        'min_entry_date': validate_datetime,
        'new_repo': validate_repo,
        'note_photo': validate_image,
        'note_photo_url': strip,
        'omit_notes': validate_yes,
        'operation': strip,
        'organization_name': strip,
        'person_record_id': strip,
        'phone_of_found_person': strip,
        'photo': validate_image,
        'photo_url': strip,
        'profile_url1': strip,
        'profile_url2': strip,
        'profile_url3': strip,
        'query': strip,
        'query_type': strip,
        'read_permission': validate_checkbox_as_bool,
        'referrer': strip,
        'resource_bundle': validate_resource_name,
        'resource_bundle_default': validate_resource_name,
        'resource_bundle_original': validate_resource_name,
        'resource_lang': validate_lang,
        'resource_name': validate_resource_name,
        'role': validate_role,
        'search_engine_id': validate_int,
        'search_permission': validate_checkbox_as_bool,
        'sex': validate_sex,
        'signature': strip,
        'skip': validate_int,
        'small': validate_yes,
        'source': strip,
        'source_date': strip,
        'source_name': strip,
        'source_url': strip,
        'stats_permission': validate_checkbox_as_bool,
        'status': validate_status,
        'style': strip,
        'subscribe': validate_checkbox,
        'subscribe_email': strip,
        'subscribe_permission': validate_checkbox_as_bool,
        'suppress_redirect': validate_yes,
        'target': strip,
        'text': strip,
        'ui': strip_and_lower,
        'utcnow': validate_timestamp,
        'version': validate_version,
    }

    def redirect(self, path, repo=None, permanent=False, **params):
        # This will prepend the repo to the path to create a working URL,
        # unless the path has a global prefix or is an absolute URL.
        if re.match('^[a-z]+:', path) or GLOBAL_PATH_RE.match(path):
            if params:
                path += '?' + urlencode(params, self.charset)
        else:
            path = self.get_url(path, repo, **params)
        return webapp.RequestHandler.redirect(self, path, permanent=permanent)

    def render(self,
               name,
               language_override=None,
               cache_seconds=0,
               get_vars=lambda: {},
               **vars):
        """Renders a template to the output stream, passing in the variables
        specified in **vars as well as any additional variables returned by
        get_vars().  Since this is intended for use by a dynamic page handler,
        caching is off by default; if cache_seconds is positive, then
        get_vars() will be called only when cached content is unavailable."""
        self.write(
            self.render_to_string(name, language_override, cache_seconds,
                                  get_vars, **vars))

    def render_to_string(self,
                         name,
                         language_override=None,
                         cache_seconds=0,
                         get_vars=lambda: {},
                         **vars):
        """Renders a template to a string, passing in the variables specified
        in **vars as well as any additional variables returned by get_vars().
        Since this is intended for use by a dynamic page handler, caching is
        off by default; if cache_seconds is positive, then get_vars() will be
        called only when cached content is unavailable."""
        # TODO(kpy): Make the contents of extra_key overridable by callers?
        lang = language_override or self.env.lang
        extra_key = (self.env.repo, self.env.charset,
                     self.request.query_string)

        def get_all_vars():
            vars['env'] = self.env  # pass along application-wide context
            vars['config'] = self.config  # pass along the configuration
            vars['params'] = self.params  # pass along the query parameters
            vars.update(get_vars())
            return vars

        return resources.get_rendered(name, lang, extra_key, get_all_vars,
                                      cache_seconds)

    def error(self, code, message='', message_html=''):
        self.info(code, message, message_html, style='error')

    def info(self, code, message='', message_html='', style='info'):
        """Renders a simple page with a message.

        Args:
          code: HTTP status code.
          message: A message in plain text.
          message_html: A message in HTML.
          style: 'info', 'error' or 'plain'. 'info' and 'error' differs in
              appearance. 'plain' just renders the message without extra
              HTML tags. Good for API response.
        """
        is_error = 400 <= code < 600
        if is_error:
            webapp.RequestHandler.error(self, code)
        else:
            self.response.set_status(code)
        if not message and not message_html:
            message = '%d: %s' % (code, httplib.responses.get(code))
        if style == 'plain':
            self.__render_plain_message(message, message_html)
        else:
            try:
                self.render('message.html',
                            cls=style,
                            message=message,
                            message_html=message_html)
            except:
                self.__render_plain_message(message, message_html)
        self.terminate_response()

    def __render_plain_message(self, message, message_html):
        self.response.out.write(
            django.utils.html.escape(message) +
            ('<p>' if message and message_html else '') + message_html)

    def terminate_response(self):
        """Prevents any further output from being written."""
        self.response.out.write = lambda *args: None
        self.get = lambda *args: None
        self.post = lambda *args: None

    def write(self, text):
        """Sends text to the client using the charset from select_charset()."""
        self.response.out.write(text.encode(self.env.charset, 'replace'))

    def get_url(self, action, repo=None, scheme=None, **params):
        """Constructs the absolute URL for a given action and query parameters,
        preserving the current repo and the parameters listed in
        PRESERVED_QUERY_PARAM_NAMES."""
        return get_url(self.request,
                       repo or self.env.repo,
                       action,
                       charset=self.env.charset,
                       scheme=scheme,
                       **params)

    @staticmethod
    def add_task_for_repo(repo, name, action, **kwargs):
        """Queues up a task for an individual repository."""
        task_name = '%s-%s-%s' % (repo, name, int(time.time() * 1000))
        path = '/%s/%s' % (repo, action)
        taskqueue.add(name=task_name, method='GET', url=path, params=kwargs)

    def send_mail(self, to, subject, body):
        """Sends e-mail using a sender address that's allowed for this app."""
        app_id = get_app_name()
        sender = 'Do not reply <do-not-reply@%s.%s>' % (app_id, EMAIL_DOMAIN)
        logging.info('Add mail task: recipient %r, subject %r' % (to, subject))
        taskqueue.add(queue_name='send-mail',
                      url='/global/admin/send_mail',
                      params={
                          'sender': sender,
                          'to': to,
                          'subject': subject,
                          'body': body
                      })

    def get_captcha_html(self, error_code=None, use_ssl=False):
        """Generates the necessary HTML to display a CAPTCHA validation box."""

        # We use the 'custom_translations' parameter for UI messages, whereas
        # the 'lang' parameter controls the language of the challenge itself.
        # reCAPTCHA falls back to 'en' if this parameter isn't recognized.
        lang = self.env.lang.split('-')[0]

        return captcha.get_display_html(
            public_key=config.get('captcha_public_key'),
            use_ssl=use_ssl,
            error=error_code,
            lang=lang,
            custom_translations={
                # reCAPTCHA doesn't support all languages, so we treat its
                # messages as part of this app's usual translation workflow
                'instructions_visual': _('Type the two words:'),
                'instructions_audio': _('Type what you hear:'),
                'play_again': _('Play the sound again'),
                'cant_hear_this': _('Download the sound as MP3'),
                'visual_challenge': _('Get a visual challenge'),
                'audio_challenge': _('Get an audio challenge'),
                'refresh_btn': _('Get a new challenge'),
                'help_btn': _('Help'),
                'incorrect_try_again': _('Incorrect.  Try again.')
            })

    def get_captcha_response(self):
        """Returns an object containing the CAPTCHA response information for the
        given request's CAPTCHA field information."""
        challenge = self.request.get('recaptcha_challenge_field')
        response = self.request.get('recaptcha_response_field')
        remote_ip = os.environ['REMOTE_ADDR']
        return captcha.submit(challenge, response,
                              config.get('captcha_private_key'), remote_ip)

    def handle_exception(self, exception, debug_mode):
        logging.error(traceback.format_exc())
        self.error(
            500,
            _('There was an error processing your request.  Sorry for the '
              'inconvenience.  Our administrators will investigate the source '
              'of the problem, but please check that the format of your '
              'request is correct.'))

    def to_local_time(self, date):
        """Converts a datetime object to the local time configured for the
        current repository.  For convenience, returns None if date is None."""
        # TODO(kpy): This only works for repositories that have a single fixed
        # time zone offset and never use Daylight Saving Time.
        if date:
            if self.config.time_zone_offset:
                return date + timedelta(0, 3600 * self.config.time_zone_offset)
            return date

    def __init__(self, request, response, env):
        webapp.RequestHandler.__init__(self, request, response)
        self.params = Struct()
        self.env = env
        self.repo = env.repo
        self.config = env.config
        self.charset = env.charset

        # Set default Content-Type header.
        self.response.headers['Content-Type'] = ('text/html; charset=%s' %
                                                 self.charset)

        # Validate query parameters.
        for name, validator in self.auto_params.items():
            try:
                value = self.request.get(name, '')
                setattr(self.params, name, validator(value))
            except Exception, e:
                setattr(self.params, name, validator(None))
                return self.error(400, 'Invalid parameter %s: %s' % (name, e))

        # Ensure referrer is in whitelist, if it exists
        if self.params.referrer and (not self.params.referrer
                                     in self.config.referrer_whitelist):
            setattr(self.params, 'referrer', '')

        # Log the User-Agent header.
        sample_rate = float(self.config and self.config.user_agent_sample_rate
                            or 0)
        if random.random() < sample_rate:
            model.UserAgentLog(
                repo=self.repo,
                sample_rate=sample_rate,
                user_agent=self.request.headers.get('User-Agent'),
                lang=lang,
                accept_charset=self.request.headers.get('Accept-Charset', ''),
                ip_address=self.request.remote_addr).put()

        # Check for SSL (unless running on localhost for development).
        if self.https_required and self.env.domain != 'localhost':
            if self.env.scheme != 'https':
                return self.error(403, 'HTTPS is required.')

        # Check for an authorization key.
        self.auth = None
        if self.params.key:
            if self.repo:
                # check for domain specific one.
                self.auth = model.Authorization.get(self.repo, self.params.key)
            if not self.auth:
                # perhaps this is a global key ('*' for consistency with config).
                self.auth = model.Authorization.get('*', self.params.key)
        if self.auth and not self.auth.is_valid:
            self.auth = None

        # Handlers that don't need a repository configuration can skip it.
        if not self.repo:
            if self.repo_required:
                return self.error(400, 'No repository specified.')
            return
        # Everything after this requires a repo.

        # Reject requests for repositories that don't exist.
        if not model.Repo.get_by_key_name(self.repo):
            if legacy_redirect.do_redirect(self):
                return legacy_redirect.redirect(self)
            html = 'No such repository. '
            if self.env.repo_options:
                html += 'Select:<p>' + self.render_to_string('repo-menu.html')
            return self.error(404, message_html=html)

        # If this repository has been deactivated, terminate with a message.
        # The ignore_deactivation flag is for admin pages that bypass this.
        if self.config.deactivated and not self.ignore_deactivation:
            self.env.language_menu = []
            self.env.robots_ok = True
            self.render('message.html',
                        cls='deactivation',
                        message_html=self.config.deactivation_message_html)
            self.terminate_response()