# -*- test-case-name: txweb2.test.test_log -*-
##
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
# Copyright (c) 2010-2017 Apple Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
##

"""
Default error output filter for txweb2.
"""

from txweb2 import stream, http_headers
from txweb2.responsecode import (
    MOVED_PERMANENTLY, FOUND, SEE_OTHER, USE_PROXY, TEMPORARY_REDIRECT,
    BAD_REQUEST, UNAUTHORIZED, PAYMENT_REQUIRED, FORBIDDEN, NOT_FOUND,
    NOT_ALLOWED, NOT_ACCEPTABLE, PROXY_AUTH_REQUIRED, REQUEST_TIMEOUT, CONFLICT,
    GONE, LENGTH_REQUIRED, PRECONDITION_FAILED, REQUEST_ENTITY_TOO_LARGE,
    REQUEST_URI_TOO_LONG, UNSUPPORTED_MEDIA_TYPE,
    REQUESTED_RANGE_NOT_SATISFIABLE, EXPECTATION_FAILED, INTERNAL_SERVER_ERROR,
    NOT_IMPLEMENTED, BAD_GATEWAY, SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT,
    HTTP_VERSION_NOT_SUPPORTED, INSUFFICIENT_STORAGE_SPACE, NOT_EXTENDED,
    RESPONSES,
)

from twisted.web.template import Element, flattenString, XMLString, renderer

# 300 - Should include entity with choices
# 301 -
# 304 - Must include Date, ETag, Content-Location, Expires, Cache-Control, Vary.

# 401 - Must include WWW-Authenticate.
# 405 - Must include Allow.
# 406 - Should include entity describing allowable characteristics
# 407 - Must include Proxy-Authenticate
# 413 - May  include Retry-After
# 416 - Should include Content-Range
# 503 - Should include Retry-After

ERROR_MESSAGES = {
    # 300
    # no MULTIPLE_CHOICES
    MOVED_PERMANENTLY:
    'The document has permanently moved <a>here<t:attr name="href">'
    '<t:slot name="location" /></t:attr></a>.',
    FOUND:
    'The document has temporarily moved <a>here<t:attr name="href">'
    '<t:slot name="location" /></t:attr></a>.',
    SEE_OTHER:
    'The results are available <a>here<t:attr name="href">'
    '<t:slot name="location" /></t:attr></a>.',
    # no NOT_MODIFIED
    USE_PROXY:
    'Access to this resource must be through the proxy '
    '<t:slot name="location" />.',
    # 306 unused
    TEMPORARY_REDIRECT:
    'The document has temporarily moved <a><t:attr name="href">'
    '<t:slot name="location" /></t:attr>here</a>.',

    # 400
    BAD_REQUEST:
    'Your browser sent an invalid request.',
    UNAUTHORIZED:
    'You are not authorized to view the resource at <t:slot name="uri" />. '
    "Perhaps you entered a wrong password, or perhaps your browser doesn't "
    'support authentication.',
    PAYMENT_REQUIRED:
    'Payment Required (useful result code, this...).',
    FORBIDDEN:
    'You don\'t have permission to access <t:slot name="uri" />.',
    NOT_FOUND:
    'The resource <t:slot name="uri" /> cannot be found.',
    NOT_ALLOWED:
    'The requested method <t:slot name="method" /> is not supported by '
    '<t:slot name="uri" />.',
    NOT_ACCEPTABLE:
    'No representation of <t:slot name="uri" /> that is acceptable to your '
    'client could be found.',
    PROXY_AUTH_REQUIRED:
    'You are not authorized to view the resource at <t:slot name="uri" />. '
    'Perhaps you entered a wrong password, or perhaps your browser doesn\'t '
    'support authentication.',
    REQUEST_TIMEOUT:
    'Server timed out waiting for your client to finish sending the request.',
    CONFLICT:
    'Conflict (?)',
    GONE:
    'The resource <t:slot name="uri" /> has been permanently removed.',
    LENGTH_REQUIRED:
    'The resource <t:slot name="uri" /> requires a Content-Length header.',
    PRECONDITION_FAILED:
    'A precondition evaluated to false.',
    REQUEST_ENTITY_TOO_LARGE:
    'The provided request entity data is too longer than the maximum for '
    'the method <t:slot name="method" /> at <t:slot name="uri" />.',
    REQUEST_URI_TOO_LONG:
    'The request URL is longer than the maximum on this server.',
    UNSUPPORTED_MEDIA_TYPE:
    'The provided request data has a format not understood by the resource '
    'at <t:slot name="uri" />.',
    REQUESTED_RANGE_NOT_SATISFIABLE:
    'None of the ranges given in the Range request header are satisfiable by '
    'the resource <t:slot name="uri" />.',
    EXPECTATION_FAILED:
    'The server does support one of the expectations given in the Expect '
    'header.',

    # 500
    INTERNAL_SERVER_ERROR:
    'An internal error occurred trying to process your request. Sorry.',
    NOT_IMPLEMENTED:
    'Some functionality requested is not implemented on this server.',
    BAD_GATEWAY:
    'An upstream server returned an invalid response.',
    SERVICE_UNAVAILABLE:
    'This server cannot service your request becaues it is overloaded.',
    GATEWAY_TIMEOUT:
    'An upstream server is not responding.',
    HTTP_VERSION_NOT_SUPPORTED:
    'HTTP Version not supported.',
    INSUFFICIENT_STORAGE_SPACE:
    'There is insufficient storage space available to perform that request.',
    NOT_EXTENDED:
    'This server does not support the a mandatory extension requested.'
}


class DefaultErrorElement(Element):
    """
    An L{ErrorElement} is an L{Element} that renders some HTML for the default
    rendering of an error page.
    """

    loader = XMLString("""
    <html xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
          t:render="error">
          <head>
              <title><t:slot name="code"/> <t:slot name="title"/></title>
          </head>
        <body>
            <h1><t:slot name="title" /></h1>
            <t:slot name="message" />
        </body>
    </html>
    """)

    def __init__(self, request, response):
        super(DefaultErrorElement, self).__init__()
        self.request = request
        self.response = response

    @renderer
    def error(self, request, tag):
        """
        Top-level renderer for page.
        """
        return tag.fillSlots(
            code=str(self.response.code),
            title=RESPONSES.get(self.response.code),
            message=self.loadMessage(self.response.code).fillSlots(
                uri=self.request.uri,
                location=self.response.headers.getHeader('location'),
                method=self.request.method,
            )
        )

    def loadMessage(self, code):
        tag = XMLString(
            ('<t:transparent xmlns:t="http://twistedmatrix.com/'
             'ns/twisted.web.template/0.1">') +
            ERROR_MESSAGES.get(code, "") +
            '</t:transparent>').load()[0]
        return tag


def defaultErrorHandler(request, response):
    """
    Handle errors which do not have any stream (i.e. output) associated with
    them, so that users will see a nice message in their browser.

    This is used as a response filter in L{txweb2.server.Request}.
    """
    if response.stream is not None:
        # Already got an error message
        return response

    if response.code < 300:
        # We only do error messages
        return response

    message = ERROR_MESSAGES.get(response.code, None)
    if message is None:
        # No message specified for that code
        return response

    message = message % {
        'uri': request.uri,
        'location': response.headers.getHeader('location'),
        'method': request.method,
    }

    data = []
    error = []

    (flattenString(request, DefaultErrorElement(request, response))
     .addCallbacks(data.append, error.append))

    # No deferreds from our renderers above, so this has always already fired.
    if data:
        subtype = 'html'
        body = data[0]
    else:
        subtype = 'error'
        body = 'Error in default error handler:\n' + error[0].getTraceback()

    ctype = http_headers.MimeType('text', subtype, {'charset': 'utf-8'})
    response.headers.setHeader("content-type", ctype)
    response.stream = stream.MemoryStream(body)
    return response

defaultErrorHandler.handleErrors = True


__all__ = ['defaultErrorHandler', ]
