"""
w2lapp.viewer.py: handler classes for displaying binary attributes

web2ldap - a web-based LDAP Client,
see http://www.web2ldap.de for details

(c) by Michael Stroeder <michael@stroeder.com>

This module is distributed under the terms of the
GPL (GNU GENERAL PUBLIC LICENSE) Version 2
(see http://www.gnu.org/copyleft/gpl.html)

$Id: viewer.py,v 1.15 2002/02/25 14:03:00 michael Exp $
"""

import sys,os,binascii,sndhdr,imghdr, \
       pyweblib.forms,utctime,pyweblib.httphelper,msbase,pyweblib.helper, \
       w2lapp.gui,w2lapp.core,w2lapp.cnf

try:
  from cStringIO import StringIO
except ImportError:
  from StringIO import StringIO

def what_sndhdr(h):
  """Recognize sound headers in string h"""
  f = StringIO(h)
  for tf in sndhdr.tests:
    res = tf(h, f)
    if res:
      return res
  return None

def snd_mimetype(h,default='audio/basic'):
  """Determine MIME-type for sound in h"""
  sndhdr_data = what_sndhdr(h)
  if sndhdr_data:
    return w2lapp.core.sndhdr_mimetype.get(sndhdr_data[0],default)
  else:
    return default

def DisplayBinaryAttribute(
  sid,outf,command,form,
  attrtype,
  entry,
  index=None,
  mimetype=None
):
  """Display a binary attribute."""
  browsertype,browserversion = pyweblib.helper.BrowserType(
    form.env.get('HTTP_USER_AGENT','')
  )
  if entry[attrtype][index].startswith('{ASN}'):
    value = binascii.unhexlify(entry[attrtype][index][5:])
  else:
    value = entry[attrtype][index]
  if mimetype is None:
    imgtype = imghdr.what(None,value)
    if (not imgtype is None) and \
       w2lapp.core.imghdr_mimetype.has_key(imgtype):
      # Get MIME-type from determined image format type
      mimetype = w2lapp.core.imghdr_mimetype[imgtype]
    else:
      sndhdr_data = what_sndhdr(value)
      if sndhdr_data and \
         w2lapp.core.sndhdr_mimetype.has_key(sndhdr_data[0]):
        # Get MIME-type from determined image format type
        mimetype = w2lapp.core.sndhdr_mimetype[sndhdr_data[0]]
      else:
        # Get MIME-type from configuration module w2lapp.cnf.misc
        ldap_browsermimetypes = msbase.CaseinsensitiveStringKeyDict(
          w2lapp.cnf.misc.ldap_browsermimetypes.get(
            (browsertype,browserversion),
            w2lapp.cnf.misc.ldap_browsermimetypes.get(
              browsertype,
              {}
            ),
          )
        )
        mimetype = ldap_browsermimetypes.get(
          attrtype,
          w2lapp.core.ldap_binaryattr.get(
            attrtype,
            ('','application/octet-stream')
          )[1]
        )
  # Send HTTP header with appropriate MIME type
  pyweblib.httphelper.SendHeader(
    outf=outf,
    contenttype=mimetype,
    charset=form.accept_charset,
    contentlength=len(value),
    expires_offset=w2lapp.cnf.misc.sec_expire,
  )
  # send attribute value
  outf.write(value)


viewer_func = msbase.CaseinsensitiveStringKeyDict(default=DisplayBinaryAttribute)

try:
  # Modules from standard lib
  import base64,md5,sha
  # ASN.1 parser from Pisces
  from pisces import asn1
  # my own mspki modules
  from mspki import x509v3,asn1helper,asn1types,util

except ImportError:
  pass

else:

  # Get OID dictionary
  try:
    oids = asn1helper.ParseCfg(w2lapp.cnf.misc.dumpasn1cfg)
  except IOError:
    oids = None

  class CRLDisplayer(x509v3.CRL):

    def htmlShortView(self,sid,outf,form,dn,ldap_attrtype,ldap_attrindex):
      """Display a CRL in HTML only with subject/issuer info"""

    def htmlDetailView(self,sid,outf,form,dn,ldap_attrtype,ldap_attrindex):
      """Display a CRL in HTML with all details"""
      asn1types.url_prefix = '%s/urlredirect?' % (form.script_name)
      asn1types.url_target = '_certurl'
      w2lapp.gui.CommandTable(
        outf,
        [
          w2lapp.gui.W2L_Anchor(
	    form,'read','Install',sid,
            [
              ('dn',dn),
              ('read_attr',ldap_attrtype),
              ('read_attrmode','load'),
              ('read_attrindex',str(ldap_attrindex)),
            ],
          ),
          w2lapp.gui.W2L_Anchor(
	    form,'read','Save to disk',sid,
            [
              ('dn',dn),
              ('read_attr',ldap_attrtype),
              ('read_attrmode','load'),
              ('read_attrmimetype','application/octet-stream'),
              ('read_attrindex',str(ldap_attrindex)),
            ],
          ),
        ]
      )

      revokedCertificates = self.revokedCertificates()
      if revokedCertificates:
        revokedCertificates_str = """<table>
        <tr><th>Serial Number</th><th>Revocation date</th>
        %s
        </table>
        """ % '\n'.join(
          [
            '<tr><td>%d</td><td>%s</td></tr>\n' % (
              i[0],i[1]
            )
            for i in revokedCertificates
          ]
        )
      else:
        revokedCertificates_str = '<p>No revoked certificates.</p>'

      # Get the extensions as string-keyed dict but with
      # numeric string representation of OIDs
      extensions = self.crlExtensions()
      if extensions:
        extensions_html_list = []
        for e in extensions:
          try:
            class_name = e.extnValue.__class__.__name__
          except AttributeError:
            class_name = repr(type(e))
          extensions_html_list.append(
            '<dt>%s (%s)</dt><dd>%s</dd>' % (
                pyweblib.forms.escapeHTML(class_name),
                str(e.extnId),
                x509v3.htmlize(e.extnValue)
            )
          )
      else:
        extensions_html_list = ['No extensions.']
      outf.write("""
    <table>
    <tr>
      <td>
	<dl>
          <dt><strong>This CRL was issued by:</strong></dt>
          <dd>%s</dd>
	</dl>
      </td>
    </tr>
    <tr>
      <td colspan="2">
	<dl>
          <dt><strong>CRL Version:</strong><dt>
          <dd>%d</dd>
          <dt><strong>This CRL is valid from %s until %s.</strong></dt>
          <dt><strong>Signature Algorithm:</strong></dt>
          <dd>%s</dd>
  	  <dt><strong>Revoked certificates:</strong><dt>
          <dd>%s</dd>
	</dl>
	<strong>CRL extensions:</strong><br>
	<dl>
          %s
	</dl>
      </td>
    </tr>
  </table>
  """ % (
	self.issuer().__html__(oids,form.accept_charset),
	self.version(),
	self.thisUpdate(),
	self.nextUpdate(),
	asn1helper.GetOIDDescription(self.signatureAlgorithm(),oids),
        revokedCertificates_str,
	'\n'.join(extensions_html_list),
      ))


  class X509CertificateDisplayer(x509v3.Certificate):

    def htmlShortView(self,sid,outf,form,dn,ldap_attrtype,ldap_attrindex):
      """Display a X.509 certificate in HTML only with subject/issuer info"""

    def htmlDetailView(self,sid,outf,form,dn,ldap_attrtype,ldap_attrindex):
      """Display a X.509 certificate in HTML with all details"""
      asn1types.url_prefix = '%s/urlredirect?' % (form.script_name)
      asn1types.url_target = '_certurl'
      w2lapp.gui.CommandTable(
        outf,
        [
          w2lapp.gui.W2L_Anchor(
	    form,'read','Install',sid,
            [
              ('dn',dn),
              ('read_attr',ldap_attrtype),
              ('read_attrmode','load'),
              ('read_attrindex',str(ldap_attrindex)),
            ],
          ),
          w2lapp.gui.W2L_Anchor(
	    form,'read','Save to disk',sid,
            [
              ('dn',dn),
              ('read_attr',ldap_attrtype),
              ('read_attrmode','load'),
              ('read_attrmimetype','application/octet-stream'),
              ('read_attrindex',str(ldap_attrindex)),
            ],
          ),
        ]
      )

      # strings containing UTCTime of begin and end of validity period
      notBefore,notAfter=self.validity()

      # Get the extensions as string-keyed dict but with
      # numeric string representation of OIDs
      extensions = self.extensions()
      nsBaseUrl=''
      if extensions:
        extensions_html_list = []
        for e in extensions:
          if e.extnValue.__class__.__name__ == 'nsBaseUrl':
            nsBaseUrl = str(e.extnValue)
          if e.extnValue.__class__.__name__ in [
            'nsCaRevocationUrl','nsRevocationUrl',
            'nsRenewalUrl','nsCaPolicyUrl'
          ]:
            extensions_html_list.append(
              '<dt>%s (%s)</dt><dd>%s</dd>' % (
                  e.extnValue.__class__.__name__,
                  str(e.extnId),
                  e.extnValue.__html__(nsBaseUrl,hex(self.serialNumber())[2:-1])
              )
            )
          else:
            extensions_html_list.append(
              '<dt>%s (%s)</dt><dd>%s</dd>' % (
                  e.extnValue.__class__.__name__,
                  str(e.extnId),
                  x509v3.htmlize(e.extnValue)
              )
            )
      else:
        extensions_html_list = ['No extensions.']

      outf.write("""
    <table>
    <tr>
      <td width="50%%">
	<dl>
          <dt><strong>This certificate belongs to:</strong></dt>
          <dd>%s</dd>
	</dl>
      </td>
      <td width="50%%">
	<dl>
          <dt><strong>This certificate was issued by:</strong></dt>
          <dd>%s</dd>
	</dl>
      </td>
    </tr>
    <tr>
      <td colspan="2">
	<dl>
          <dt><strong>Certificate Version:</strong></dt>
          <dd>%d</dd>
          <dt><strong>Serial Number:</strong></dt>
          <dd>%s</dd>
          <dt><strong>Validity Period:</strong></dt>
          <dd>
            <dl>
              <dt>not before</dt><dd>%s</dd>
              <dt>not after</dt><dd>%s</dd>
            </dl>
          </dd>
          <dt><strong>Fingerprint:</strong></dt>
          <dd>
            <dl>
              <dt>MD5</dt><dd>%s</dd>
              <dt>SHA-1</dt><dd>%s</dd>
            </dl>
          </dd>
          <dt><strong>Signature Algorithm:</strong></dt>
          <dd>%s</dd>
	</dl>
	<strong>X.509v3 certificate extensions:</strong>
	<dl>
          %s
	</dl>
      </td>
    </tr>
  </table>
  """ % (
	self.subject().__html__(oids,form.accept_charset),
	self.issuer().__html__(oids,form.accept_charset),
	self.version(),
	self.serialNumber(),
	notBefore,
	notAfter,
	self.MD5Fingerprint(),
	self.SHA1Fingerprint(),
	asn1helper.GetOIDDescription(self.signatureAlgorithm(),oids),
	'\n'.join(extensions_html_list),
      ))



  def DisplayX509Certificate_der(sid,outf,command,form,dn,attr,entry,index=None):
    """Display a DER-encoded X.509 certificate attribute"""
    outf.write('<h1>%s</h1>' % (w2lapp.gui.AttrNameStr(attr)))
    for index in range(len(entry[attr])):
      X509CertificateDisplayer(entry[attr][index],'der').htmlDetailView(
        sid,outf,form,dn,attr,index,
      )
    return None
  

  def DisplayX509Certificate_base64(sid,outf,command,form,dn,attr,entry,index=None):
    """Display a base64-encoded X.509 certificate attribute"""
    outf.write('<h1>%s</h1>' % (w2lapp.gui.AttrNameStr(attr)))
    for index in range(len(entry[attr])):
      if util.is_base64(entry[attr][index]):
	inform='base64'
        value = entry[attr][index]
      elif entry[attr][index].startswith('{ASN}'):
	inform='der'
        value = binascii.unhexlify(entry[attr][index][5:])
      else:
	inform='der'
        value = entry[attr][index]
      X509CertificateDisplayer(value,inform).htmlDetailView(
        sid,outf,form,dn,attr,index,
      )
    return None


  def DisplayCRL_der(sid,outf,command,form,dn,attr,entry,index=None):
    """Display a DER-encoded CRL attribute"""
    outf.write('<h1>%s</h1>' % (w2lapp.gui.AttrNameStr(attr)))
    for index in range(len(entry[attr])):
      CRLDisplayer(entry[attr][index],'der').htmlDetailView(
        sid,outf,form,dn,attr,index,
      )
    return None
  

  def DisplayCRL_base64(sid,outf,command,form,dn,attr,entry,index=None):
    """Display a base64-encoded CRL attribute"""
    outf.write('<h1>%s</h1>' % (w2lapp.gui.AttrNameStr(attr)))
    for index in range(len(entry[attr])):
      if util.is_base64(entry[attr][index]):
	inform='base64'
        value = entry[attr][index]
      elif entry[attr][index].startswith('{ASN}'):
	inform='der'
        value = binascii.unhexlify(entry[attr][index][5:])
      else:
	inform='der'
        value = entry[attr][index]
      CRLDisplayer(value,inform).htmlDetailView(
        sid,outf,form,dn,attr,index,
      )
    return None

  # register viewer functions

  viewer_func['userCertificate;binary'] = \
  viewer_func['cACertificate;binary'] = DisplayX509Certificate_der
  viewer_func['entrustPolicyCertificate'] = DisplayX509Certificate_der
  viewer_func['attributeCertificate'] = DisplayX509Certificate_der

  viewer_func['cACertificate'] = \
  viewer_func['userCertificate'] = \
  viewer_func['pem_x509'] = DisplayX509Certificate_base64
#  viewer_func['fCert'] = DisplayX509Certificate_der
#  viewer_func['rCert'] = DisplayX509Certificate_der

  viewer_func['certificateRevocationList;binary'] = DisplayCRL_der
  viewer_func['certificateRevocationList'] = DisplayCRL_base64
  viewer_func['authorityRevocationList;binary'] = DisplayCRL_der
  viewer_func['authorityRevocationList'] = DisplayCRL_base64

  # NDS certificate attributes
  viewer_func['nDSPKIPublicKeyCertificate'] = DisplayX509Certificate_der
  viewer_func['nDSPKICertificateChain'] = DisplayX509Certificate_der
