import cherrypy, splunk, datetime, platform, logging, os, time, cgi, util
import xml.sax.saxutils as su

logger = logging.getLogger('splunk.appserver.mrsparkle.lib.error')

# oldest crash log to present to the user, in minutes
CRASH_WINDOW = 10

HTML_TEMPLATE = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- 
    This is a static HTML string template to render errors.  To edit this
    template, see appserver/mrsparkle/lib/error.py. 
-->
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:splunk="http://www.splunk.com/xhtml-extensions/1.0" xml:lang="en">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta name="referrer" content="never" />
    <meta name="referrer" content="no-referrer" />
    <link rel="shortcut icon" href="%(favicon)s" />
    <title>%(message)s - Splunk</title>
    <style>
        *       { margin: 0; padding: 0; }
        body    { font-family: helvetica, arial, sans-serif; color: #333; padding: 20px; }
        p,pre   { margin-bottom: 1em; font-size: .8em; }
        .status { font-size: .7em; color: #999; margin-bottom: 1em; }
        .msg    { margin-bottom: 1em; font-size: 1.4em;}
        pre     { font-family: Monaco,Courier Bold,Courier New,monospace; font-size: .7em;background-color: #eee;  padding: 5px;}
        #toggle { font-size: .8em; margin-bottom: 1em; }
        .byline { color: #555; }
        .byline span { font-weight: bold; line-height: 1.4em; }
        hr      { height: 1px; background-color: #ccc; border: 0; margin: 20px 0 10px; }
        h2      { font-size: 1em; margin-bottom: 1em; }
        table   { border-collapse: collapse; }
        td      { padding: 2px; }
        td.k    { font-family: helvetica, arial, sans-serif; font-weight: bold; }
        #debug  { display: %(visibility)s; }
        #crashes { margin: 20px 0; padding: 10px; border: 1px solid #800; }
        #crashes dt { font-size: 12px; margin-bottom: 5px; }
        #crashes dd { white-space: pre; background: #f2f2f2; padding: 10px; margin-left: 20px; display: none; font: 10px Monaco,Courier Bold,Courier New,monospace; }
    </style>
    <script>
        function toggle(what) {
            what = document.getElementById(what);
            if (what.style.display == 'block') {
                what.style.display = 'none';
            } else {
                what.style.display = 'block';
            }
        }
    </script>
</head>
<body>
    <p class="status">%(status)s</p>
    <p class="homelink"><a href="%(homeurl)s">Return to Splunk home page</a></p>
    <h1 class="msg">%(message)s</h1>
    %(more_results_link)s
    <br />
    <br />
    %(crashes)s
    %(referrer)s
    <br />
    <br />
    %(debug)s
    <hr />
    <p class="byline">%(byline)s.</p>
</body>
</html>
'''

SIMPLE_HTML_TEMPLATE = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:splunk="http://www.splunk.com/xhtml-extensions/1.0" xml:lang="en">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <link rel="shortcut icon" href="%(favicon)s" />
    <title>%(message)s - Splunk</title>
    <style>.status {color: #999;}</style>
</head>
<body>
    <p class="status">%(status)s</p>
</body>
</html>
'''

DEBUG_TEMPLATE = '''
<p id="toggle"><a href="javascript:void(0)" onclick="toggle('debug')">Debugging information</a></p>
<div id="debug">
    <h2>Python Traceback</h2>
    <pre class="traceback">%(traceback)s</pre>
    <h2>Appserver configuration</h2>
    <pre class="config">%(splunkConfig)s</pre>
    <h2>Client request headers</h2>
    <pre class="headers">%(requestHeaders)s</pre>
    <h2>WSGI environment</h2>
    <pre class="headers">%(wsgi)s</pre>
    <h2>Platform</h2>
    <pre class="traceback">%(uname)s</pre>
</div>
'''


def handleError(**kwargs):
    '''This should be safe.
    Note that the message parameter of kwargs seems to be magically
    HTML escaped by Cherrypy so it is left untreated, for now.
    '''

    # augment the kwargs with mrsparkle data
    kwargs['splunkConfig'] = renderTable(cherrypy.config)
    kwargs['requestHeaders'] = renderTable(cherrypy.request.headers)
    kwargs['byline'] = generateByline()
    kwargs['homeurl'] = cherrypy.config.get('root_endpoint', '/')
    kwargs['uname'] = su.escape('\n'.join(platform.uname()))
    kwargs['wsgi'] = renderTable(cherrypy.request.wsgi_environ)
    kwargs['referrer'] = generateReferrer()
    kwargs['more_results_link'] = generateMoreResultsLink()

    try:
        kwargs['favicon'] = util.make_url(['static','img',util.getFaviconFileName()])
    except Exception:
        kwargs['favicon'] = ""
    
    # only show recent crash logs when we can't reach splunkd
    kwargs['crashes'] = ''
    if kwargs.get('status', '')[:3] ==  '503':
        try:
            kwargs['crashes'] = generateCrashLogs()
        except Exception, e:
            logger.exception(e)

    # SPL-93824: 404 pages should NOT display path information
    if kwargs.get('status', '')[:3] ==  '404':
        errMsg = kwargs.get('message', '')
        logger.info("Masking the original 404 message: '%s' with 'Page not found!' for security reasons" % errMsg)
        kwargs['message'] = 'Page not found!'

    # only show debug information while logging is set to debug 
    # (or if --debug was used)
    kwargs['debug'] = ''
    kwargs['visibility'] = 'none'
    if logging.getLogger('splunk').getEffectiveLevel() == logging.DEBUG:
        kwargs['debug'] = DEBUG_TEMPLATE % kwargs
    
    # extract additional info if we're in 500 territory
    if kwargs.get('status', '')[:3] ==  '500' and not kwargs.get('noexname', None):
        kwargs['message'] = kwargs['traceback'].strip('\n').split('\n')[-1]

    if cherrypy.config.get('simple_error_page'):
        return SIMPLE_HTML_TEMPLATE % kwargs
    else:
        return HTML_TEMPLATE % kwargs

def renderTable(D, sort=True):
    '''This should be safe'''
    output = []
    if len(D) == 0:
        output.append('No data')

    keys = D.keys()
    if sort: keys.sort()

    output.append('<table>')
    for key in keys:
        output.append('<tr>')
        output.append('<td class="k">%s:</td>' % su.escape(key))
        output.append('<td class="v">%s</td>' % su.escape(str(D.get(key))))
        output.append('</tr>')
    output.append('</table>')
    return ''.join(output)
    
    
def generateCrashLogs():
    '''This should be safe'''
    entries = []
    loc = util.make_splunkhome_path(['var', 'log', 'splunk'])
    for name in sorted(os.listdir(loc), reverse=True):
        if name.startswith('crash') and name.endswith('log'):
            info = os.stat(os.path.join(loc,name))
            if info.st_mtime > (time.time() - CRASH_WINDOW*60):
                h = open(os.path.join(loc,name), 'r')
                contents = '[UNREADABLE]'
                try:
                    contents = ''.join(h.readlines())
                finally:
                    h.close()
                tmpItem = {
                    'filename': name,
                    'delta': time.time() - info.st_mtime,
                    'contents': contents
                }
                entries.append(tmpItem)
        
    if entries:
        output = ['<div id="crashes">']
        output.append('<h2>Splunkd has recently crashed:</h2>')
        output.append('<dl>')
        for i, item in enumerate(entries):
            output.append('<dt><a href="javascript:void(0)" onclick="toggle(\'%s\')">%s</a> (%d minutes(s) ago)</dt>' % ('cl%s' % i, item['filename'], item['delta']/60))
            output.append('<dd id="%s">%s</dd>' % ('cl%s' % i, su.escape(item['contents'])))
        output.append('</dl>')
        output.append('</div>')
        return ''.join(output)
        
    else:
        return ''
    
        
            
def generateByline():
    '''This should be safe'''
    appHost = cherrypy.request.headers.get('host', 'UNKONWN_HOST')
    splunkdHost = splunk.mergeHostPath()
    buildNumber = cherrypy.config.get('build_number', 'UNKNOWN_BUILD')
    currentTime = str(datetime.datetime.now().ctime())

    try:
        userName = cherrypy.session['user']['name']
        return 'You are logged into <span>%s</span> as <span>%s</span>, which is connected to splunkd <span>@%s</span> at <span>%s</span> on <span>%s</span>' % (su.escape(appHost), userName, buildNumber, splunkdHost, currentTime)
    except:
        return 'You are using <span>%s</span>, which is connected to splunkd <span>@%s</span> at <span>%s</span> on <span>%s</span>' % (su.escape(appHost), buildNumber, splunkdHost, currentTime)

def generateReferrer():
    '''This should be safe'''
    r = cherrypy.request.wsgi_environ.get('HTTP_REFERER')
    if r and r.startswith("http"):
        return '<p>This page was linked to from <a href="%s">%s</a>.</p>' % (cgi.escape(r, True), su.escape(r))
    else:
        return ''

def generateMoreResultsLink():
    try:
        host = platform.uname()[1]
    except Exception:
        host = '*'

    request_id = util.get_request_id()
    search_string = 'index=_internal host="%s" source=*web_service.log log_level=ERROR requestid=%s' % (host, request_id)
    qs = { 'q': search_string }
    # Try to generate a link to more results in the current locale.  This might throw an exception (e.g. the error we are
    # displaying is that the locale is invalid, see SPL-81750), in which case fall back to the default locale.
    try:
        link_url = util.make_url('/app/search/search', _qs=qs)
    except:
        link_url = util.make_url('/app/search/search', _qs=qs, translate=False)

    return '<a href="%s" target="_blank">View more information about your request (request ID = %s) in Search</a>' % (link_url, request_id)
