########################################################################
# 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
########################################################################

import sys,string,time,types,ldap, \
       msbase,cgiforms,httphelper,ldapbase,w2lcore,w2lcnf,web2ldapcnf,w2lgui

from web2ldapcnf import max_searchparams, script_method
from msbase import intersection

##############################################################################
# Search for entries and output results as table, pretty-printable output
# or LDIF formatted
##############################################################################

def w2l_Search(
  outf,
  command,
  form,
  ls,
  search_output = 'table',
  search_scope  = ldap.SCOPE_SUBTREE,
  search_filterstr = None
):

  # Suchparameter sind alle getrennt angegeben
  if 'search_output' in form.inputkeys:
    search_output = form.field['search_output'][0].content
  if 'search_scope' in form.inputkeys:
    search_scope_str = form.field['search_scope'][0].content
    search_scope = ldapbase.ldap_searchscope.get(search_scope_str,ldap.SCOPE_SUBTREE)
  else:
    search_scope_str = ldapbase.ldap_searchscope_str[search_scope]

  # construct LDAP filter string
  if search_filterstr is None:
    if 'search_filterstr' in form.inputkeys:
      search_filterstr = form.field['search_filterstr'][0].content
    else:
      if 'search_mode' in form.inputkeys:
        search_mode = form.field['search_mode'][0].content
      else:
        search_mode = '(&%s)'
      search_filter = []
      if ('search_option' in form.inputkeys) and \
         ('search_attr' in form.inputkeys) and \
         ('search_string' in form.inputkeys) and \
         (
           len(form.field['search_option'][0].content)== \
	   len(form.field['search_attr'][0].content)== \
	   len(form.field['search_string'][0].content) \
         ):
        for i in range(len(form.field['search_option'][0].content)):
	  if form.field['search_string'][0].content[i]:
	    search_filter.append(
	      form.field['search_option'][0].content[i] % (
    		  string.lower(form.field['search_attr'][0].content[i]), \
		  ldapbase.escape_filter_chars(form.field['search_string'][0].content[i])
	      )
	    )
      else:
        raise w2lcore.ErrorExitClass(ls,'Invalid or empty search form data.')
      # Teilsuchen werden alle und-verknuepft
      search_filterstr = search_mode % string.join(search_filter,'')

  if 'search_resminindex' in form.inputkeys:
    search_resminindex = int(form.field['search_resminindex'][0].content)
  else:
    search_resminindex = 0
  if 'search_resnumber' in form.inputkeys:
    search_resnumber = int(form.field['search_resnumber'][0].content)
  else:
    search_resnumber = 10

  if search_output=='print':
    print_template_filenames_dict = w2lcnf.GetParam(ls,'print_template',None)
    print_cols = w2lcnf.GetParam(ls,'print_cols','4')
    
    if print_template_filenames_dict is None:
      raise w2lcore.ErrorExitClass(ls,'No print templates defined.')

    read_attrs = ['objectclass']
    print_template_str_dict = msbase.CaseinsensitiveStringKeyDict()

    for oc in print_template_filenames_dict.keys():
      try:
	print_template_str_dict[oc] = open(print_template_filenames_dict[oc],'r').read()
      except IOError:
	pass
      else:
        read_attrs = msbase.union(
	  read_attrs,
	  msbase.GrabKeys(print_template_str_dict[oc]).keys
	)

  elif search_output=='table':

    search_tdtemplate = msbase.CaseinsensitiveStringKeyDict(
      w2lcnf.GetParam(ls,'search_tdtemplate',{})
    )

    read_attrs = []

    for oc in search_tdtemplate.keys():
      read_attrs = msbase.union(
	read_attrs,
	msbase.GrabKeys(search_tdtemplate[oc]).keys
      )

    search_tablistattrs = w2lcnf.GetParam(ls,'search_tablistattrs',[])
    if search_tablistattrs:
      read_attrs.extend(search_tablistattrs)

    read_attrs.append('objectClass')
    read_attrs.append('displayName')

  elif search_output in ['ldif','ldif1','dsml']:
    # read all attributes
    read_attrs = []

  try:
    # Start asynchronous search call
    ldap_msgid = ls.l.search(
      ls.dn.encode('utf-8'),
      search_scope,
      search_filterstr.encode('utf-8'),
      ldapbase.encode_unicode_list(read_attrs),
      0
    )
  except ldap.NO_SUCH_OBJECT:
    result_dnlist = []

  search_ldap_url = ldapbase.create_ldap_url(
    ls.host,
    ls.dn,
    read_attrs,
    search_scope,
    search_filterstr,
    urlencode=1
  )

  if search_output=='table':

    search_maxhits = search_resminindex+search_resnumber
    result_dnlist = []
    mailtolist = []

    try:
      result_type,result_data = ls.l.result(ldap_msgid,0)
    except ldap.NO_SUCH_OBJECT:
      result_data = []
    else:
      if not result_type in ['RES_SEARCH_ENTRY','RES_SEARCH_RESULT','RES_SEARCH_REFERENCE']:
        ls.l.abandon(ldap_msgid)
        raise w2lcore.ErrorExitClass(ls,'Wrong result type: "%s"' % (result_type))

      resind = 0
      while result_data and \
            (search_maxhits==0 or (resind<search_maxhits)):
        if resind>=search_resminindex:
	  for r in result_data:
            if type(r)==types.TupleType and len(r)==2:
              # Search result
              dn,data=r
	      if data.has_key('mail'):
                mailtolist.extend(data['mail'])
              result_dnlist.append((unicode(dn,'utf-8'),data))
            elif type(r)==types.StringType:
              # Search continuation
              result_dnlist.append(unicode(r,'utf-8'))
            else:
              raise ValueError,"LDAP result of invalid type %s." % (type(r))
        try:
          result_type,result_data = ls.l.result(ldap_msgid,0)
        except ldap.PARTIAL_RESULTS,e:
          matched_dn, ldap_urls = ldapbase.extract_referrals(e)
          if matched_dn!=None:
            result_data = [
              (
                matched_dn,
                {
                  'objectClass':['referral'],
                  'ref':ldap_urls
                }
              )
            ]
          break
        resind = resind+1

      if result_data:
        # Only partial results retrieved, undisplayed data available
        ls.l.abandon(ldap_msgid)

    # HACK! If searching the root level returns empty search results
    # the namingContexts is queried
    if ls.dn=='' and \
       search_scope==ldap.SCOPE_ONELEVEL:
      namingContexts = ldapbase.GetNamingContexts(ls.l,ls.who,ls.cred)
      result_dnlist.extend(map(
        lambda dn: (dn,{}),
        namingContexts
      ))

    result_dnlist.sort()

    # Output search results as table with buttons
    w2lgui.PrintHeader(outf,'Search Results',form.accept_charset)
    w2lgui.MainMenuTable(outf,form,ls)

    if len(result_dnlist):

      ContextMenuList = []

      # There's still data left to be read
      if result_data or search_resminindex:

        prev_resminindex = max(0,search_resminindex-search_resnumber)

        if search_resminindex+search_resnumber<=resind:
          ContextMenuList.append(
	    w2lgui.CommandButton(
	      form,'search','-&gt;&gt;',
	      ls.who,ls.cred,ls.host,ls.dn,
	      extrastr="""
	      <input type=hidden name="search_filterstr" value="%s">
	      <input type=hidden name="search_resminindex" value="%d">
	      <input type=hidden name="search_resnumber" value="%d">
	      <input type=hidden name="search_scope" value="%s">""" % (
	        w2lcore.utf2display(form.accept_charset,search_filterstr),
		search_resminindex+search_resnumber,search_resnumber,
		search_scope_str.encode()
              )
            )
	  )

        if search_resminindex:
          ContextMenuList.append(
	    w2lgui.CommandButton(
	      form,'search','&lt;&lt;-',
	      ls.who,ls.cred,ls.host,ls.dn,
	      extrastr="""
	      <input type=hidden name="search_filterstr" value="%s">
	      <input type=hidden name="search_resminindex" value="%d">
	      <input type=hidden name="search_resnumber" value="%d">
	      <input type=hidden name="search_scope" value="%s">""" % (
	        w2lcore.utf2display(form.accept_charset,search_filterstr),
		prev_resminindex,search_resnumber,
		search_scope_str.encode()
              )
            )
	  )

	result_message = """
	  <p>
	    Results %d - %d of %s search with filter &quot;%s&quot;.
	  </p>
	  """ % (
	    search_resminindex+1,
	    resind,
	    search_scope_str.encode(),
	    w2lcore.utf2display(form.accept_charset,search_filterstr)
        )

      else:
	result_message = '<p>%s search with filter &quot;%s&quot; found %d entries.</p>' % (
	  search_scope_str.encode(),
	  w2lcore.utf2display(form.accept_charset,search_filterstr),
	  len(result_dnlist)
	)

      ContextMenuList.extend([
	w2lgui.CommandButton(
	  form,
	  'searchform',
	  'Refine Filter',
	  ls.who,
	  ls.cred,
	  ls.host,
	  ls.dn,
	  extrastr='<input type=hidden name="searchform_mode" value="exp"><input type=hidden name="search_filterstr" VALUE="%s"><input type=hidden name="search_scope" value="%s">' % (w2lcore.utf2display(form.accept_charset,search_filterstr),search_scope_str.encode())
        ),
	w2lgui.CommandButton(
	  form,
	  'search',
	  'Other format',
	  ls.who,
	  ls.cred,
	  ls.host,
	  ls.dn,
          extrastr="""
	  <input type=hidden name="search_scope" value="%s">
	  <input type=hidden name="search_filterstr" value="%s">
	  <select name="search_output">
	    <option value="print" selected>Printable</option>
	    <option value="ldif">LDIF (Umich style)</option>
	    <option value="ldif1">LDIF (Version 1)</option>
	    <option value="dsml">DSML</option>
	  </select>
	  """ % (search_scope_str.encode(),w2lcore.utf2display(form.accept_charset,search_filterstr)),
	  target='_altoutput'
	)
      ])

      w2lgui.CommandTable(outf,ContextMenuList,div_id='ContextMenuDiv',table_id='ContextMenuTable')

      outf.write('%s<p><A HREF="%s" TARGET="_ldapurl">Search by LDAP URL</A>' % (
          result_message,
          search_ldap_url.encode()
        )
      )
      if mailtolist:
	outf.write(
          '- <A HREF="mailto:%s?cc=%s">Mail to all Cc:-ed</A>- <A HREF="mailto:?bcc=%s">Mail to all Bcc:-ed</A>' % (
	    mailtolist[0],
	    string.join(mailtolist[1:],','),
	    string.join(mailtolist,',')
  	  )
  	)
      outf.write('</p>')

      search_tablistattrs_len = len(search_tablistattrs)

      outf.write('<table id=SearchResultTable summary="Table of search results" WIDTH="100%%" CELLPADDING=0 CELLSPACING=0 BORDER RULES=rows>\n')

      for r in result_dnlist:

        if type(r)==types.UnicodeType:
          # Search continuation
          host,dn,search_attr,search_scope,search_filterstr,ext = ldapbase.parse_ldap_url(r)
          who,cred=None,None
          tableentrystr='<td id=SearchResultItem>Referral =&gt; %s</td>' % (w2lgui.DataStr(form,ls,r,commandbutton=0))

        elif type(r)==types.TupleType and len(r)==2:
          dn,entry=r
          objectclasses = entry.get('objectClass',entry.get('objectclass',[]))
	  tdtemplate_oc = intersection(
	    map(string.lower,objectclasses),
	    map(string.lower,search_tdtemplate.keys())
	  )

          if entry.has_key('displayName') or entry.has_key('displayname'):
            tableentrystr='<td id=SearchResultItem colspan=%d>%s</td>' % (
              search_tablistattrs_len,
              unicode(
                entry.get(
                  'displayName',
                  entry.get(
                    'displayname',
                    [dn.encode('utf-8')]
                  )
                )[0],
                'utf-8'
              ).encode(form.accept_charset)
            )

          elif tdtemplate_oc:

            template_attrs = []
	    for oc in tdtemplate_oc:
	      template_attrs.extend(msbase.GrabKeys(search_tdtemplate[oc]).keys)
	    tableentry_attrs = intersection(
	      template_attrs,
	      entry.keys(),
	      ignorecase=1
	    )
            if tableentry_attrs:
              # Print entry with the help of pre-defined templates
	      tableentry = msbase.CaseinsensitiveStringKeyDict(default='&nbsp;')
	      for attr in tableentry_attrs:
	        tableentry[attr] = []
	        for value in entry[attr]:
  		  tableentry[attr].append(w2lgui.DataStr(form,ls,value,commandbutton=0))
	        tableentry[attr] = string.join(tableentry[attr],', ')
              tdlist = []
	      for oc in tdtemplate_oc:
	        tdlist.append(search_tdtemplate[oc] % tableentry)
	      tableentrystr='<td id=SearchResultItem colspan=%d>%s</td>\n' % (search_tablistattrs_len,string.join(tdlist,'<br>\n'))
	    else:
              # Print DN
	      tableentrystr='<td id=SearchResultItem colspan=%d>%s</td>' % (search_tablistattrs_len,w2lgui.DataStr(form,ls,dn,commandbutton=0))

	  elif search_tablistattrs and entry.has_key(search_tablistattrs[0]):
            # Print values of attributes listed in search_tablistattrs
	    tableentry = msbase.CaseinsensitiveStringKeyDict(default='&nbsp;')
	    tableentry_attrs = intersection(
	      search_tablistattrs,
	      entry.keys(),
	      ignorecase=1
	    )
	    for attr in tableentry_attrs:
	      tableentry[attr] = []
	      for value in entry[attr]:
  	        tableentry[attr].append(w2lgui.DataStr(form,ls,value,commandbutton=0))
	      tableentry[attr] = string.join(tableentry[attr],'<br>')
	    tdlist = []
	    for attr in search_tablistattrs:
	      tdlist.append(tableentry[attr])
            tableentrystr='<td id=SearchResultItem>%s</td>' % string.join(tdlist,'</td><td id=SearchResultItem>')

	  else:
            # Print DN
	    tableentrystr='<td id=SearchResultItem colspan=%d>%s</td>' % (
              search_tablistattrs_len,
              w2lgui.DataStr(form,ls,dn,commandbutton=0)
            )
          host,who,cred=ls.host,ls.who,ls.cred
        else:
          raise ValueError,"LDAP result of invalid type %s." % (type(r))

	outf.write('<TR><TD WIDTH="5%%">%s</td><TD WIDTH="5%%">%s</td>%s</TR>' % (
          w2lgui.CommandButton(
	    form,'search','Go down',
	    who,cred,host,dn,
	    extrastr='<input type=hidden name="search_scope" VALUE="one"><input type=hidden name="search_filterstr" VALUE="(objectclass=*)">'
          ),
          w2lgui.CommandButton(
	    form,'read','Read',
            who,cred,host,dn,
	  ),
	  tableentrystr
	)
      )
      outf.write('</table>')

    else:
      w2lgui.CommandTable(outf,[
	w2lgui.CommandButton(
	  form,'searchform','Refine Filter',
	  ls.who,ls.cred,ls.host,ls.dn,
	  extrastr='<input type=hidden name="searchform_mode" VALUE="exp"><input type=hidden name="search_filterstr" VALUE="%s"><input type=hidden name="search_scope" VALUE="%s">' % (w2lcore.utf2display(form.accept_charset,search_filterstr),search_scope_str.encode())
        )
      ],div_id='ContextMenuDiv',table_id='ContextMenuTable')
      outf.write('<div id=MessageDiv><p>No Entries found with %s search with filter &quot;%s&quot;.</p></div>' % (
          search_scope_str.encode(form.accept_charset),
          w2lcore.utf2display(form.accept_charset,search_filterstr)
        )
      )

    w2lgui.PrintFooter(outf)

  elif search_output=='print':
    result_type,result_dnlist = ls.l.result(ldap_msgid,1)
    if result_type != 'RES_SEARCH_RESULT':
      raise w2lcore.ErrorExitClass(ls,'Wrong result type: "%s"' % (result_type))
    if not result_dnlist:
      raise w2lcore.ErrorExitClass(ls,'No search results.')
    result_dnlist.sort()

    table=[]
    for dn,entry in result_dnlist:
      objectclasses = entry.get('objectclass',entry.get('objectClass',[]))
      template_oc = intersection(
        map(string.lower,objectclasses),
	map(string.lower,print_template_str_dict.keys())
      )
      if template_oc:
	tableentry = msbase.DefaultDict('')
	attr_list=entry.keys()
	for attr in attr_list:
	  tableentry[attr] = string.join(w2lcore.utf2display(form.accept_charset,entry[attr]),', ')
	table.append(print_template_str_dict[template_oc[0]] % (tableentry))

    # Output search results as pretty-printable table without buttons
    w2lgui.PrintHeader(outf,'Search Results',form.accept_charset)
    outf.write("""<h1>%s</h1>
<table
  id=PrintTable
  summary="Table with search results formatted for printing"
  width="100%%" cellpadding="3%%" cellspacing=0 border=1 rules=rows>
    <colgroup span=%d>
    """ % (w2lcore.utf2display(form.accept_charset,ls.dn),print_cols))
    tdwidth=100/print_cols
    for i in range(print_cols):
      outf.write('<col width="%d%%">\n' % (tdwidth))
    outf.write('</colgroup>\n')
    for i in range(len(table)):
      if i%print_cols==0:
        outf.write('<tr>')
      outf.write('<td width="%d%%" align=left valign=top>%s</td>' % (tdwidth,table[i]))
      if i%print_cols==print_cols-1:
        outf.write('</tr>\n')
    if len(table)%print_cols!=print_cols-1:
      outf.write('</tr>\n')

    outf.write('</table>\n')

    w2lgui.PrintFooter(outf)


  elif search_output in ['ldif','ldif1']:

    import ldif

    result_type,result_dnlist = ls.l.result(ldap_msgid,0)
    if result_type !='RES_SEARCH_ENTRY':
      ls.l.abandon(ldap_msgid)
      raise w2lcore.ErrorExitClass(ls,'Wrong result type: "%s"' % (result_type))

    # Output search results as LDIF
    httphelper.SendHeader(outf,'text/plain',charset='US-ASCII')
    if search_output=='ldif1':
      outf.write("""########################################################################
# LDIF export by web2ldap %s, see http://www.web2ldap.de
# Date and time: %s
# Bind-DN: %s
# LDAP-URL of search:
# %s
########################################################################
version: 1

""" % (w2lcore.Version,time.strftime('%A, %Y-%m-%d %H:%M:%S GMT',time.gmtime(time.time()+web2ldapcnf.sec_expire)),ls.who,search_ldap_url))
    while result_dnlist:
      for dn,entry in result_dnlist:
	outf.write('%s\n' % ldif.CreateLDIF(dn,entry,w2lcore.ldap_binaryattrkeys))
      result_type,result_dnlist = ls.l.result(ldap_msgid,0)


  elif search_output=='dsml':

    import dsml

    result_type,result_dnlist = ls.l.result(ldap_msgid,0)
    if result_type !='RES_SEARCH_ENTRY':
      ls.l.abandon(ldap_msgid)
      raise w2lcore.ErrorExitClass(ls,'Wrong result type: "%s"' % (result_type))

    # Output search results as DSML in a rather primitive way.
    # (level 1 producer)
    httphelper.SendHeader(outf,'text/xml',charset='UTF-8')
    outf.write("""<dsml:dsml xmlns:dsml="http://www.dsml.org/DSML">
  <!-- directory entries found by %s -->
  <dsml:directory-entries>
""" % (search_ldap_url))

    while result_dnlist:
      for dn,entry in result_dnlist:
	dsml.CreateDSML(outf,dn,entry,w2lcore.ldap_binaryattrkeys)
      result_type,result_dnlist = ls.l.result(ldap_msgid,0)

    outf.write("""  </dsml:directory-entries>
</dsml:dsml>
""")

