"""
w2lapp.gui.py: basic functions for GUI elements

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: gui.py,v 1.93 2002/02/26 06:49:18 michael Exp $
"""

import sys,os,types,time,re,urllib,ldif,ldap,UserDict, \
       pyweblib.forms,utctime,pyweblib.httphelper,msbase,ldaputil.base, \
       w2lapp.core,w2lapp.cnf

from ldapurl import LDAPUrl,LDAPUrlExtension,isLDAPUrl
from pyweblib.forms import escapeHTML

from ldaputil.base import ParentDN,ParentDNList,is_dn,normalize_dn,dn_pattern
from w2lapp.viewer import snd_mimetype

from w2lapp.core import utf2display

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

########################################################################
# 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|mailto):(|//)[^ ]*'
url_regex  = re.compile(url_pattern)
labeleduri_regex = re.compile(url_pattern+r' .*')
timestamp_pattern = r'^([0-9]){12,14}(.[0-9]+)*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 = w2lapp.cnf.misc.ldap_binaryattr.keys()

HIDDEN_FIELD = '<input type="hidden" name="%s" value="%s">'

# 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

command_method = {
  'add':'POST',
  'modify':'POST',
  'rename':'POST',
  'login':'POST',
  'passwd':'POST',
  'delete':'POST',
}


def ActionUrl(form,command,sid):
  return '%s/%s%s' % (
    form.script_name,
    command,
    {0:'/%s' % sid,1:''}[sid is None],
  )


def W2L_Form(
  form,command,submitstr,sid,
  form_parameters,
  extrastr='',
  target=None
):
  """
  Build the HTML text of a submit form
  """
  charset = form.accept_charset
  utf2display = w2lapp.core.utf2display
  method = command_method.get(command,'GET')
  target = {0:'target="%s"' % (target),1:''}[target is None]
  form_str = ["""
    <form
      class="CommandForm"
      action="%s"
      method="%s"
      %s
      enctype="application/x-www-form-urlencoded"
      accept-charset="%s"
    >
    """  % (ActionUrl(form,command,sid),method,target,charset)
  ]
  for param_name,param_value in form_parameters:
    form_str.append(HIDDEN_FIELD % (
        param_name,
        utf2display(charset,param_value,escape_entities=1)
      )
    )
  form_str.append("""
    <input type="submit" value="%s">
    %s
    </form>""" % (submitstr,extrastr)
  )
  return '\n'.join(form_str)


def W2L_Anchor(
  form,command,anchor_text,sid,
  form_parameters,
  target=None,
):
  """
  Build the HTML text of a anchor with form parameters
  """
  charset = form.accept_charset
  utf2display = w2lapp.core.utf2display
  method = command_method.get(command,'GET')
  target = {0:'target="%s"' % (target),1:''}[target is None]
  return '<a class="CommandLink" %s href="%s?%s">%s\n</a>\n' % (
    target,
    ActionUrl(form,command,sid),
    '&amp;'.join([
      '%s=%s' % (
        param_name,
        urllib.quote_plus(utf2display(charset,param_value,escape_entities=0))
      )
      for param_name,param_value in form_parameters
    ]),
    anchor_text
  )


def LDAPError2ErrMsg(e):
  """
  Converts a LDAPError exception into HTML error message
  """
  try:
    if type(e.args[0])==type({}):
      ErrMsg = '%s: %s' % (
        e.args[0]['desc'],
        e.args[0].get('info','')
      )
    elif type(e.args[0])==type(1):
      error_desc = repr(e).split(' instance')[0].split('.')[1]
      ErrMsg = '%s (%d): %s' % (
        error_desc,e.args[0],e.args[1]
      )
    else:
      ErrMsg = str(e.args)
  except TypeError:
    ErrMsg = str(e.args)
  except IndexError:
    ErrMsg = str(e.args)
  return ErrMsg


def DisplayDN(sid,form,ls,dn,commandbutton=0,active_ldapurl=0):
  """Display a DN as LDAP URL with or without button"""
  if dn is None:
    return ''
  elif dn:
    dn_str = utf2display(form.accept_charset,dn)
  else:
    dn_str = '- World -'
  if active_ldapurl:
    href_str = LDAPUrl(hostport=ls.host,dn=dn,who=None,cred=None).htmlHREF(
      hrefText=dn_str,hrefTarget='_ldapurl',httpCharset=form.accept_charset
    )
  else:
    href_str = dn_str
  if commandbutton:
    return ' | '.join([
      href_str,
      W2L_Anchor(
	form,'read','Read',sid,[('dn',dn)]
      ),
    ])
  else:
    return href_str


# Output table containing W2L_Anchor()'s
def CommandTable(
  outf,
  commandlist,
  div_id='CommandDiv',
):
  if commandlist:
    outf.write('<table class="%s">\n<tr>\n<td>\n' % div_id)
    outf.write('</td>\n<td>|</td>\n<td>\n'.join(commandlist))
    outf.write('</td>\n</tr>\n</table>\n')


def ContextMenuSingleEntry(sid,form,ls,dn):
  """Output the context menu for a single entry"""
  return [
    W2L_Anchor(
      form,'read','Raw',sid,
      [('dn',dn),('read_output','table')],
    ),
    W2L_Anchor(
      form,'login','Bind as',sid,
      [
        ('dn',dn),
        ('login_who',dn),
      ],
    ),
    W2L_Anchor(
      form,'modifyform','Modify',sid,[('dn',dn)]
    ),
    W2L_Anchor(
      form,'rename','Rename',sid,[('dn',dn)]
    ),
    W2L_Anchor(
      form,'delete','Delete',sid,[('dn',dn)]
    ),
    W2L_Anchor(
      form,'passwd','Password',sid,
      [
        ('dn',dn),
        ('passwd_who',dn),
      ],
    ),
    W2L_Anchor(
      form,'groupadm','Groups',sid,[('dn',dn)]
    ),
    W2L_Anchor(
      form,'read','vCard',sid,
      [('dn',dn),('read_output','vcard')],
      target='_altoutput'
    ),
    W2L_Anchor(
      form,'search','LDIF',sid,
      [
        ('dn',dn),
        ('search_output','ldif'),
        ('scope',str(ldap.SCOPE_BASE)),
        ('search_filterstr','(objectClass=*)'),
      ],
      target='_altoutput'
    ),
    W2L_Anchor(
      form,'search','LDIFv1',sid,
      [
        ('dn',dn),
        ('search_output','ldif1'),
        ('scope',str(ldap.SCOPE_BASE)),
        ('search_filterstr','(objectClass=*)'),
      ],
      target='_altoutput'
    ),
    W2L_Anchor(
      form,'search','DSML',sid,
      [
        ('dn',dn),
        ('search_output','dsml'),
        ('scope',str(ldap.SCOPE_BASE)),
        ('search_filterstr','(objectClass=*)'),
      ],
      target='_altoutput'
    ),
  ]


def StatusLine(sid,outf,form,ls,dn):
  """
  Status line displaying host, current DN, LDAP URL and bind D
  """
  if ls!=None and hasattr(ls,'host'):
    # Only output something if valid connection
    template_dict = msbase.CaseinsensitiveStringKeyDict(default='&nbsp;')
    template_dict.update(form.env)
    template_dict = {
      'host':ls.host.encode(form.accept_charset),
      'dn':DisplayDN(sid,form,ls,dn,active_ldapurl=1),
      'description':unicode(w2lapp.cnf.GetParam(ls,'description',''),'iso-8859-1').encode(form.accept_charset),
    }
    if hasattr(ls,'who') and ls.who:
      template_dict['who'] = DisplayDN(sid,form,ls,ls.who)
    else:
      template_dict['who'] = 'anonymous'
    status_line_template = """
    %(dn)s on %(host)s, &quot;%(description)s&quot;<br>
    Bound as: %(who)s
    """
    outf.write('<p class="StatusLine">\n%s\n</p>\n' % (status_line_template % template_dict))


def MainMenu(sid,form,ls,dn):
  """
  Returns list of main menu items
  """
  cl = []

  if ls!=None:

    if hasattr(ls,'host') and ls.__dict__.get('who',None)!=None:
      cl.append(
        W2L_Anchor(
          form,'search','Down',sid,
          [
            ('dn',dn),
            ('scope',str(ldap.SCOPE_ONELEVEL)),
          ],
        )
      )
      if dn:
        cl.append(
          W2L_Anchor(
	    form,'search','Up',sid,
            [
              ('dn',ParentDN(dn)),
              ('scope',str(ldap.SCOPE_ONELEVEL)),
            ],
          )
        )
      cl.append(
        W2L_Anchor(form,'searchform','Search',sid,[('dn',dn)]),
      )
      if dn or ls.__dict__.get('hasRootDSE',0):
        cl.append(
          W2L_Anchor(
            form,'read',
            {0:'Read',1:'Root DSE'}[dn==''],
            sid,[('dn',dn)]
          ),
        )
      cl.append(
        W2L_Anchor(form,'addform','New entry',sid,[('dn',dn)]),
      )
      cl.append(W2L_Anchor(form,'conninfo','ConnInfo',sid,[('dn',dn)]))
      cl.append(W2L_Anchor(form,'login','Bind',sid,[('dn',dn)]))

  cl.append(W2L_Anchor(form,'','Disconnect',sid,[]))
  
  return cl


def TopSection(sid,outf,form,ls,dn,title,main_menu_list,context_menu_list=[]):
  w2lapp.gui.PrintHeader(outf,title,form.accept_charset)
  outf.write('<div id="TopSection">\n')
  StatusLine(sid,outf,form,ls,dn)
  CommandTable(outf,main_menu_list,div_id='MainMenu')
  CommandTable(outf,context_menu_list,div_id='ContextMenu')
  outf.write('</div>\n')
  return # TopSection()


def SimpleMessage(
  sid,outf,form,ls,dn,
  title='',message='',
  div_id='MessageDiv',
  main_menu_list=[],context_menu_list=[]
):
  TopSection(sid,outf,form,ls,dn,title,main_menu_list,context_menu_list=context_menu_list)
  outf.write('<div id="%s">\n<p>\n%s</p>\n</div>\n' % (div_id,message))
  w2lapp.gui.PrintFooter(outf,form)
  return # SimpleMessage()


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


def LDAPURLButton(sid,form,ls,data):
  if isinstance(data,LDAPUrl):
    l = data
  else:
    l = LDAPUrl(ldapUrl=data)
  if l.scope!=ldap.SCOPE_BASE:
    command_func, command_text, command_hidden_fields = (
      'search','Search',
      [
        ('scope',str(l.scope)),
        ('search_filterstr',l.filterstr)
      ]
    )
  else:
    command_func,command_text,command_hidden_fields = 'read','Read',[]
  command_hidden_fields.extend([
    ('host',l.hostport),
    ('dn',l.dn),
  ])
  return W2L_Anchor(form,command_func,command_text,sid,command_hidden_fields)


def MailToURL(mailadr):
  return '<a href="mailto:%s">%s</a>' % (mailadr,mailadr)

####################################################################
# Weird pre-initialization of attribute types with known syntax
####################################################################

class AttrValueDisplayer(UserDict.UserDict):

  def register(self,displayer_function,attr_types):
    for a in attr_types:
      self.data[a.lower()]=displayer_function

attr_displayer = AttrValueDisplayer()

def display_directorystring(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  return utf2display(form.accept_charset,value)

attr_displayer.register(
  display_directorystring,
  [
    'cn','givenName','sn','surName','c','countryName','st','o','ou','l','uid','userid',
    'telephoneNumber','homePhoneNumber','facsimiletelephonenumber','fax',
    'street','description','departmentNumber','mobile','homePhone','streetAddress',
    'postalCode','employeeNumber','userClass','roomNumber','displayName',
    'personalTitle','title','buildingName','organizationalStatus',
    'ldapSyntaxes','attributeTypes','objectClasses','matchingRules','aci'
  ]
)

def display_postaladdress(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  return '<br>'.join(utf2display(form.accept_charset,value.split('$')))

attr_displayer.register(
  display_postaladdress,
  [
    'postalAddress','homeAddress','homePostalAddress','registeredAddress'
  ]
)

def display_distinguishedname(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  return DisplayDN(sid,form,ls,value,commandbutton=commandbutton)

attr_displayer.register(
  display_distinguishedname,
  [
    'seeAlso','member','creatorsName','modifiersName','subschemaSubentry',
    'namingContexts','secretary','manager','lastModifiedBy','vlvSearch',
    'associatedName','owner'
  ]
)


def display_rfc822address(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  return MailToURL(value)

attr_displayer.register(
  display_rfc822address,
  [
    'mail','rfc822Mailbox','mailLocalAddress',
    'mailRoutingAddress','mgrpRFC822MailMember','mailForwardingAddress'
  ]
)

def display_image(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  return """
    <img
      src="%s/read/%s?dn=%s&read_attr=%s&amp;read_attrindex=%d"
    >
    <br>%d bytes of image data
    """ % (
    form.script_name,sid,
    urllib.quote_plus(utf2display(form.accept_charset,dn,escape_entities=0)),
    urllib.quote_plus(attrtype),
    valueindex,len(value),
  )

attr_displayer.register(
  display_image,
  ['jpegPhoto','thumbnailPhoto','thumbnailLogo']
)


def display_oid(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  if oid_reg.has_key(value):
    name,description,reference = oid_reg[value]
    return '<strong>%(name)s</strong> (%(value)s):<br>%(description)s (see %(reference)s)' % vars()
  else:
    return value

attr_displayer.register(
  display_oid,
  ['supportedControl','supportedExtension']
)


def display_audio(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  mimetype = snd_mimetype(value)
  return """
    <embed
      type="%s"
      autostart="false"
      src="%s/read/%s?dn=%s&read_attr=%s&read_attrindex=%d"
    >
    %d bytes of audio data (%s)
    """ % (
      mimetype,
      form.script_name,sid,
      urllib.quote_plus(utf2display(form.accept_charset,dn,escape_entities=0)),
      urllib.quote_plus(attrtype),
      valueindex,
      len(value),
      mimetype
    )

attr_displayer.register(display_audio,['audio'])


def display_datetime(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  return utctime.strftimeiso8601(utctime.strptime(value))

attr_displayer.register(
  display_datetime,
  ['createTimestamp','modifyTimestamp']
)


def display_uri(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  try:
    url,label = value.split(' ',1)
  except ValueError:
    return '<a href="%s/urlredirect?%s" target="_labeledurl">%s</a>' % (form.script_name,value,value)
  else:
    return '<a href="%s/urlredirect?%s" target="_labeledurl">%s</a>' % (form.script_name,url,utf2display(form.accept_charset,label))

attr_displayer.register(display_uri,['labeledURI'])


def display_ldapurl(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  if commandbutton:
    commandbuttonstr = LDAPURLButton(sid,form,ls,value)
  else:
    commandbuttonstr = ''
  return '<table><tr><td>%s</td><td><a href="%s" target="_ldapurl">%s</a></td></tr></table>' % (
           commandbuttonstr,
	   value.encode(form.accept_charset),
	   utf2display(form.accept_charset,value)
	 )

attr_displayer.register(display_uri,['labeledURI'])


def display_binary(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  return W2L_Anchor(
    form,'read','View/Load',sid,
    [('dn',dn),('read_attr',attrtype),('read_attrindex',str(valueindex))]
  )

attr_displayer.register(
  display_binary,
  [a for a in w2lapp.core.ldap_binaryattrkeys if not attr_displayer.has_key(a)]
)


# See http://developer.novell.com/ndk/doc/ndslib/nds__enu/data/hniuyp90.html#hniuyp90
novell_network_address_types = (
  ('NT_IPX','Internet Packet Exchange (IPX) network address'),
  ('NT_IP','Internet Protocol (IP) network address'),
  ('NT_SDLC','Synchronous Data Link Control (SDLC) address'),
  ('NT_TOKENRING_ETHERNET','Token ring on Ethernet MAC address'),
  ('NT_OSI','Open Systems Interconnection (OSI) address'),
  ('NT_APPLETALK','AppleTalk network address'),
  ('NT_NETBEUI','NetBIOS Extended User Interface (NetBEUI) address'),
  ('NT_SOCKADDR','Socket address'),
  ('NT_UDP','User Datagram Protocol (UDP) address'),
  ('NT_TCP','Transmission Control Protocol (TCP) address'),
  ('NT_UDP6','User Datagram Protocol (UDP), version 6, address'),
  ('NT_TCP6','Transmission Control Protocol (TCP), version 6, address'),
  ('NT_INTERNAL','Reserved'),
  ('NT_URL','Uniform Resource Locator address (added for NDS 8)'),
  ('NT_COUNT','Returns the maximum number of network address types defined.'),
)

def DataStr(sid,form,ls,dn,attrtype,value,valueindex=0,commandbutton=0,oid_reg={}):
  """
  Return a pretty HTML-formatted string of the attribute value
  """
  if not value:
    return '&nbsp;'
  # Try to avoid excessive regex checking by looking
  # at widely used attribute types
  attrtype_lower = attrtype.lower()
  if attr_displayer.has_key(attrtype_lower):
    return attr_displayer[attrtype_lower](
      sid,form,ls,dn,attrtype,value,valueindex,commandbutton,oid_reg
    )
  elif attrtype_lower=='objectguid':
    objectguid_str = ''.join(['%02X' % ord(c) for c in value])
    return '<a target="ldapurl" href="ldap://%s/&lt;GUID=%s&gt;">%s</a>' % (
      ls.host,
      objectguid_str,
      objectguid_str,
    )
  elif attrtype_lower=='pgpkey':
    return '<pre>%s</pre>' % (value)
  elif ls.__dict__.get('vendorName','').startswith('Novell') and attrtype_lower=='networkaddress':
    address_type,address_data = value.split('#',1)
    address_type_ident,address_type_desc = novell_network_address_types[int(address_type)]
    return '<dl><dt>%s (%s)</dt><dd>%s</dd></dl>' % (
      address_type_ident,
      pyweblib.forms.escapeHTML(address_type_desc),
      pyweblib.forms.escapeHTML(repr(address_data))
    )
  elif isLDAPUrl(value):
    if commandbutton:
      commandbuttonstr = LDAPURLButton(sid,form,ls,value)
    else:
      commandbuttonstr = ''
    return '<table><tr><td>%s</td><td><a href="%s" target="_ldapurl">%s</a></td></tr></table>' % (
             commandbuttonstr,
	     value.encode(form.accept_charset),
	     utf2display(form.accept_charset,value)
	   )
  # Check for RFC822 mail address
  rm = mail_regex.match(value)
  if rm!=None:
    return MailToURL(value)
  # Check for DN
  if is_dn(value):
    return DisplayDN(sid,form,ls,value,commandbutton)
  # Check for URL
  rm = url_regex.search(value)
  if rm!=None:
    rm = labeleduri_regex.match(value)
    if rm!=None:
      url,label = value.split(' ',1)
      return '<a href="%s/urlredirect?%s" target="_labeledurl">%s</a>' % (form.script_name,url,utf2display(form.accept_charset,label))
    else:
      return '<a href="%s/urlredirect?%s" target="_labeledurl">%s</a>' % (form.script_name,value,value)
  # Check for timestamp
  rm = timestamp_regex.match(value)
  if rm!=None:
    return utctime.strftimeiso8601(utctime.strptime(value))
  # No special format detected
  return utf2display(form.accept_charset,value)


# Ausdrucken eines HTML-Kopfes mit Titelzeile
def PrintHeader(outf,TitleMsg,HTTP_charset):
  
  pyweblib.httphelper.SendHeader(
    outf,
    'text/html',
    HTTP_charset,
    expires_offset=w2lapp.cnf.misc.sec_expire,
    additional_header={'Pragma':'no-cache'}
  )

  outf.write("""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>web2ldap - %s</title>
    <meta name="generator" content="web2ldap %s, see www.web2ldap.de">
    <meta http-equiv="Content-Type" content="text/html;charset=%s">
    <meta http-equiv="Pragma" content="no-cache">
    <meta name="robots" content="noindex,nofollow">
    %s
  </head>
%s
""" % (
    TitleMsg,
    w2lapp.__version__,
    HTTP_charset,
    w2lapp.cnf.misc.html_head,
    w2lapp.cnf.misc.html_bodybegin
  ))
  return


# Ausdrucken eines HTML-Endes
def PrintFooter(f,form):
  footer_template_dict = msbase.CaseinsensitiveStringKeyDict(default='&nbsp;')
  footer_template_dict.update(form.env)
  footer_template_dict.update({
    'web2ldap_version':escapeHTML(w2lapp.__version__),
  })
  footer_template = r"""
  Powered by
  <a href="%(SCRIPT_NAME)s/urlredirect?http://www.web2ldap.de/" target="_web2ldap">
    web2ldap %(web2ldap_version)s
  </a>
"""
  f.write('<div id="AboutDiv">\n%s\n</div>\n</body>\n</html>\n' % (
      footer_template
    ) % (footer_template_dict)
  )


BindInput = """
  <fieldset title="User account info">
    <p>
      to host:port %s (%s)
      with identification search below %s.
    </p>
    <table summary="User account info">
      <tr>
        <td>Bind as</td>
        <td>
          <input name="who" maxlength="1024" size="40" value="%s">
        </td>
      </tr>
      <tr>
        <td>with password</td>
        <td>
          <input type="password" name="cred" maxlength="200" size="25" value="">
        </td>
      </tr>
    </table>
    <p>
      <input type="submit" value="%s">
    </p>
  </fieldset>
"""

def SearchRootField(form,ls,dn,name='dn',text=u'Search Root'):
  """Prepare input field for search root"""
  if dn:
    dn_select_list = [dn]+ldaputil.base.ParentDNList(dn,ls.searchRoot)
  else:
    dn_select_list = []
  dn_select_list = msbase.union(
    filter(None,ls.namingContexts), # filter out zero-length strings
    dn_select_list,
    ignorecase=1
  )
  dn_select_list.append((u'',u'- World -'))
  srf = pyweblib.forms.Select(name,text,1,options=dn_select_list,ignoreCase=1)
  srf.setDefault(ls.__dict__.get('searchRoot',''))
  srf.setCharset(form.accept_charset)
  return srf


def ExceptionMsg(sid,outf,form,ls,dn,Heading,Msg):
  TopSection(sid,outf,form,ls,dn,'Error',MainMenu(sid,form,ls,dn),context_menu_list=[])
  outf.write("""
<div id="ErrorMessageDiv">
  <h1>%s</h1>
  <p>
    %s
  </p>
  <p>
    For questions or error reports contact:
    <a href="mailto:%s">%s</a>
  </p>
</div>
""" % (Heading,Msg,form.env.get('SERVER_ADMIN',''),form.env.get('SERVER_ADMIN','')))
  PrintFooter(outf,form)


class ObjectClassSelect(pyweblib.forms.Select):
  """Select field class for choosing the object class(es)"""
  def __init__(
    self,
    name='ldap_oc',
    text='Object classes',
    default=[],
    required=0,
    accesskey='',
    size=12,            # Size of displayed select field
  ):
    pyweblib.forms.Select.__init__(
      self,
      name,
      text,
      len(w2lapp.core.ldap_known_objectclasses),
      required,
      options=w2lapp.core.ldap_known_objectclasses,
      default=default,
      accessKey=accesskey,
      size=size,
      ignoreCase=1,
      multiSelect=1
    )
    self.setRegex('[\w]+')
  

class DistinguishedNameInput(pyweblib.forms.Input):
  """Input field class for LDAP DNs."""

  def __init__(self,name='dn',text='DN',maxValues=1,required=0):
    pyweblib.forms.Input.__init__(
      self,name,text,1024,maxValues,'',
      size=40,required=required
    )

  def _validateFormat(self,value):
    if value and not ldaputil.base.is_dn(value):
      raise pyweblib.forms.InvalidValueFormat(
        self.name,
        self.text.encode(self.charset),
        value.encode(self.charset)
      )


class LDIFTextArea(pyweblib.forms.Textarea):
  """A single multi-line input field for LDIF data"""

  def __init__(
    self,name='in_ldif',text='LDIF data',required=0,max_entries=1
  ):
    pyweblib.forms.Textarea.__init__(
      self,
      'in_ldif','LDIF data',
      w2lapp.cnf.misc.ldif_maxbytes,1,ldif.ldif_pattern
    )
    self._max_entries = max_entries
    self.allRecords = []

  def getLDIFRecords(self):
    if self.value:
      f = StringIO('\n'.join(self.value).encode(self.charset))
      ldif_parser = ldif.LDIFRecordList(
        f,
        ignored_attr_types=[],
        max_entries=self._max_entries,
        process_url_schemes=w2lapp.cnf.misc.ldif_url_schemes
      )
      # It's not clear why ldif_parser stays persistent.
      # But let's reset class attribute now.
      # FIX ME!!! Likely not thread-safe!
      ldif_parser.all_records = []
      ldif_parser.parse()
      return ldif_parser.all_records[:]
    else:
      return []

