########################################################################
# web2ldap
# (c) by Michael Stroeder <michael@stroeder.com>
# Distributed under GNU Public License (GPL)
# web-based LDAP Client, see http://www.web2ldap.de for details
########################################################################
# Functions which writes widely-used GUI-elements
########################################################################

import sys, os, string, types, time, re, \
       cgiforms, utctime, httphelper, ldap, ldapbase, \
       web2ldapcnf, msgzip, w2lcore, w2lcnf

from ldapbase import ParentDN,ParentDNList,is_dn,normalize_dn,dn_pattern

########################################################################
# Initialize some constants used throughout web2ldap
########################################################################

host_pattern = '([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z]+[a-zA-Z0-9-]*(\.[a-zA-Z]+[a-zA-Z0-9-]*)*)?(:[0-9]*|)?'
url_pattern  = r'^(ftp|http|https|news|snews|ldap|ldaps):(|//)[^ ]*'
url_regex  = re.compile(url_pattern)
labeleduri_regex = re.compile(url_pattern+r' .*')
timestamp_pattern = r'^([0-9]){12,14}Z$'
timestamp_regex  = re.compile(timestamp_pattern)
mail_pattern = r'^[\w@.=/_ -]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$'
mail_regex = re.compile(mail_pattern)
ldap_binaryattrkeys = web2ldapcnf.ldap_binaryattr.keys()


# This function searches for variants
def GetVariantFilename(pathname,variantlist):
  for v in variantlist:
    variant_filename = pathname+'.'+v
    if os.path.isfile(variant_filename):
      break
  else:
    variant_filename = pathname
    if not os.path.isfile(variant_filename):
      variant_filename = ''
  return variant_filename


def DisplayDN(dispcharset,dn):
  return '<em>%s</em>' % dn.encode(dispcharset)


# Build the string of a button
def CommandButton(
  form,
  command,
  submitstr,
  who=None,
  cred=None,
  host=None,
  dn=None,
  extrastr='',
  target='_top'
):

  charset = form.accept_charset
  form_str = ["""
<form
  id=CommandForm
  action="%s/%s"
  method="%s"
  target="%s"
  enctype="application/x-www-form-urlencoded"
  accept-charset="%s"
>
"""  % (
    form.script_name,
    command,
    web2ldapcnf.script_method,
    target,
    charset,
  )
]
  for param in ['who','cred','host','dn']:
    if vars()[param]!=None:
      form_str.append('<input type=hidden name="%s" value="%s">' % (
          param,
	  cgiforms.escapeHTML(vars()[param].encode(charset))
	)
      )
  form_str.append("""
<input id=CommandButton type="submit" value="%s">
%s
</form>""" % (submitstr,extrastr)
  )
  return string.join(form_str,'\n')

# Output table containing CommandButton()'s
def CommandTable(
  outf,
  commandlist,
  div_id='CommandDiv',
  table_id='CommandTable',
  table_summary='Command buttons'
):
  outf.write("""
<div id="%s">
<table id="%s" summary="%s" cellpadding="0%%" cellspacing="0%%">\n<tr>
""" % (div_id,table_id,table_summary))
  for s in commandlist:
    outf.write('<td id=MenuItem align=center valign=top>%s</td>\n' % s)
  outf.write('</tr>\n</table>\n</div>\n')


def ContextMenuSingleEntry(outf,form,ls,current_dn=u''):
  """Output the context menu for a single entry"""
  CommandTable(outf,[
    CommandButton(
      form,'vcard','vCard',
      ls.who,ls.cred,ls.host,ls.dn,
      target='_labeledurl'
    ),
    CommandButton(
      form,'search','Other format',
      ls.who,ls.cred,ls.host,ls.dn,
      extrastr="""<br>
      <input type=hidden name="search_scope" value="base">
      <input type=hidden name="search_filterstr" value="(objectclass=*)">
      <select name="search_output">
        <option value="ldif">LDIF (Umich style)</option>
        <option value="ldif1">LDIF (Version 1)</option>
	<option value="dsml">DSML</option>
      </select>
      """,
      target='_altoutput'
    ),
    CommandButton(
      form,'modifyform','Modify',
      ls.who,ls.cred,ls.host,ls.dn,
    ),
    CommandButton(
      form,'modrdn','Modify RDN',
      ls.who,ls.cred,ls.host,ls.dn,
    ),
    CommandButton(
      form,'delete','Delete',
      ls.who,ls.cred,ls.host,ls.dn,
    ),
    CommandButton(
      form,'passwd','Password of entry',
      ls.who,ls.cred,ls.host,ls.dn,
    ),
    CommandButton(
      form,'login','Login as',
      None,None,ls.host,ls.dn,
      extrastr='<input type=hidden name="login_binddn" value="%s">' % (w2lcore.utf2display(form.accept_charset,ls.dn))
    ),
  ],div_id='ContextMenuDiv',table_id='ContextMenuTable')


# Output the main menu at top of screen
def MainMenuTable(outf,form,ls,current_dn=''):
  MainMenuList = []
  if current_dn:
    ls.dn = current_dn

  # convert and store to speed things up
  if ls.host and ls.dn!=None:

    MainMenuList.append(
      CommandButton(
        form,'search','Go down',
        ls.who,ls.cred,ls.host,ls.dn,
        extrastr='<input type=hidden name="search_scope" value="one"><input type=hidden name="search_filterstr" value="(objectclass=*)">'
      )
    )

    if ls.dn:
      # Build the select list for choosing parent DNs
      parentdn_list = ldapbase.explode_dn(ls.dn)[1:]
      parentdn_select_list = []
      for i in xrange(len(parentdn_list)):
        parentdn_select_list.append(
          (
            string.join(parentdn_list[i:],u','),
            parentdn_list[i]
          )
        )
      parentdn_select_list.append((u'',u'- World -'))
      parentdn_select_field = cgiforms.formSelectClass(
        'dn',
        u'Parent DN',
        parentdn_select_list,
        default=[parentdn_select_list[0][0]]
      )
      parentdn_select_field.setcharset(form.accept_charset)
      MainMenuList.append(
        CommandButton(
	  form,'search','Go up',
          ls.who,ls.cred,ls.host,None,
	  extrastr='<br>%s<input type=hidden name="search_scope" value="one"><input type=hidden name="search_filterstr" value="(objectclass=*)">' % (parentdn_select_field.inputfield())
        )
      )

    MainMenuList.append(
      CommandButton(form,'searchform','Search',
	ls.who,ls.cred,ls.host,ls.dn,
	extrastr='<br><select name="searchform_mode"><option value="base">Base</option><option value="adv">Advanced</option><option value="exp">Expert</option></select>'
      )
    )
    MainMenuList.extend(
      [
        CommandButton(form,'read','Read',ls.who,ls.cred,ls.host,ls.dn),
	CommandButton(form,'addform','New entry',ls.who,ls.cred,ls.host,ls.dn),
      ]
    )
    if ls.who:
      MainMenuList.append(
        CommandButton(form,'passwd','Password',ls.who,ls.cred,ls.host,ls.who)
      )
    MainMenuList.append(
      CommandButton(form,'secinfo','Security',ls.who,ls.cred,ls.host,ls.dn)
    )
    MainMenuList.append(CommandButton(form,'login','Login',None,None,ls.host,ls.dn))

  MainMenuList.append(CommandButton(form,'','Connect',None,None,ls.host))

  # Status line
  if ls.dn:
    dn_str = DisplayDN(form.accept_charset,ls.dn)
  else:
    dn_str = "- World -"
  if ls.who:
    who_str = ', bound as %s' % DisplayDN(form.accept_charset,ls.who)
  else:
    who_str = ''
  href_str = '<a href="%s" target="_ldapurl">%s</a>' % (
    ldapbase.create_ldap_url(
      ls.host.encode('ascii'),
      ls.dn.encode('utf-8'),
      urlencode=1
    ),
    DisplayDN(form.accept_charset,ls.dn)
  )
  outf.write('<div id=StatusDiv>%s on <strong>%s</strong>: &quot;%s&quot; %s</div>' % (
    href_str,
    ls.host.encode(form.accept_charset),
    unicode(w2lcnf.GetParam(ls,'description',''),'iso-8859-1').encode(form.accept_charset),
    who_str,
  ))
  # Main menu
  CommandTable(outf,MainMenuList,div_id='MainMenuDiv',table_id='MainMenuTable')


# Return a pretty HTML-formatted string for describing the attribute
def AttrNameStr(attr):
  if w2lcore.ldap_knownattr.has_key(attr):
    return '<strong>%s</strong><br><em>(%s)</em>' % (w2lcore.ldap_knownattr[attr],attr)
  else:
    return '<em>%s</em>' % attr


def LDAPURLButton(form,ls,data):
  host,ls.dn,search_attr,search_scope,search_filterstr,ext = ldapbase.parse_ldap_url(data)
  if not host:
    # If the LDAP URL does not contain a host name we stick to current host
    host = ls.host
  if search_scope!=ldap.SCOPE_BASE:
    command_func, command_text, command_extrastr = 'search', 'Search', \
      '<input type=hidden name="search_scope" value="%s"><input type=hidden name="search_filterstr" value="%s">' % (
	ldapbase.ldap_searchscope_str[search_scope],search_filterstr
      ) 
  else:
    command_func, command_text, command_extrastr = 'read','Read',''
  return CommandButton(
    form,command_func,command_text,
    w2lcore.utf2display(form.accept_charset,ls.who),
    w2lcore.utf2display(form.accept_charset,ls.cred),
    host,
    w2lcore.utf2display(form.accept_charset,ls.dn),
    extrastr=command_extrastr
  )


def DataStr(form,ls,data,attr='',commandbutton=0):
  """
  Return a pretty HTML-formatted string of the attribute data
  """

  if data is None:
    return ''

  if ldapbase.is_ldap_url(data):
    if commandbutton:
      commandbuttonstr = LDAPURLButton(form,ls,data)
    else:
      commandbuttonstr = ''
    return '<table><tr><td>%s</td><td><a href="%s" target="_ldapurl">%s</a></td></tr></table>' % (
             commandbuttonstr,
	     data.encode(form.accept_charset),
	     w2lcore.utf2display(form.accept_charset,data)
	   )

  rm = mail_regex.match(data)
  if rm!=None:
    return '<a href="mailto:%s">%s</a>' % (data,data)

  if is_dn(data):
    href_str = '<a href="%s" target="_ldapurl">%s</a>' % (
      ldapbase.create_ldap_url(
	ls.host.encode('ascii'),
	data.encode('utf-8'),
	urlencode=1
      ),
      DisplayDN(form.accept_charset,data)
    )
    if commandbutton:
      return '<table><tr><td>%s</td><td>%s</td></tr></table>' % (
	       CommandButton(
	         form,
		 'read',
		 'Read',
		 ls.who,
		 ls.cred,
		 ls.host,
		 data
               ),
	       href_str
	     )
    else:
      return href_str

  rm = url_regex.search(data)
  if rm!=None:
    rm = labeleduri_regex.match(data)
    if rm!=None:
      url,label = string.split(data,' ',1)
      return '<a href="%s" target="_labeledurl">%s</a>' % (url,w2lcore.utf2display(form.accept_charset,label))
    else:
      return '<a href="%s" target="_labeledurl">%s</a>' % (data,data)

  rm = timestamp_regex.match(data)
  if rm!=None:
    return utctime.strftimeiso8601(utctime.strptime(data))

  return string.join(string.split(w2lcore.utf2display(form.accept_charset,data),'$'),'<br>')


# Ausdrucken eines HTML-Kopfes mit Titelzeile
def PrintHeader(outf,TitleMsg,HTTP_charset='ISO-8859-1'):
  
  httphelper.SendHeader(
    outf,
    'text/html',
    HTTP_charset,
    expires_offset=web2ldapcnf.sec_expire,
  )

  outf.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
  <meta name="generator" content="web2ldap %s, see www.web2ldap.de" />
  <title>web2ldap %s:</title>
  <meta http-equiv="Content-Type" content="text/html;charset=%s" />
  %s
</head>
%s""" % (
    w2lcore.Version,
    TitleMsg,
    HTTP_charset,
    web2ldapcnf.html_head,
    web2ldapcnf.html_bodybegin
  ))
  return


# Ausdrucken eines HTML-Endes
def PrintFooter(f):
  f.write("""<div id=AboutDiv>
  <p align=center>
    <font size=-2>
      Powered by
      <a href="http://www.web2ldap.de/" target="_web2ldap">web2ldap %s</a>
    </font>
  </p>
</div>
</body>
</html>""" % (w2lcore.Version))
  return

BindInput = """
  <fieldset title="User account info">
    <table summary="User account info">
      <tr>
        <td>Bind as</td><td><input name="who" maxlength=200 size=40 value="%s"></td>
      </tr>
      <tr>
        <td>with password</td><td><input type="password" name="cred" maxlength=100 size=15 value=""></td>
      </tr>
    </table>
  </fieldset>
"""

def SearchRootField(form,ls,name='dn',text=u'Search Root'):
  """Prepare input field for search root"""
#  ls.l.options = 0
  search_root_list = ldapbase.GetNamingContexts(ls.l)
#  ls.l.options = ls.options
  if not ls.dn:
    dn_select_list = []
  else:
    dn_select_list = [ls.dn]
  for dn in search_root_list:
    if dn:
      dn_select_list.append((dn,dn))
  dn_select_list.append((u'',u'- World -'))
  srf = cgiforms.formSelectClass(name,text,dn_select_list)
  if ls.dn:
    matched_dn=ldapbase.match_dn(ls.dn,search_root_list)
    if matched_dn!=None:
      srf.setdefault(matched_dn)
  else:
    srf.setdefault(u'')
  srf.setcharset(form.accept_charset)
  return srf
