From afd8717558d81866b2c93ede90a5a560dcca288f Mon Sep 17 00:00:00 2001
From: "dmose%netscape.com" <dmose%netscape.com>
Date: Mon, 4 Jun 2001 03:01:51 +0000
Subject: [PATCH] Fix LDAP autocomplete to show display names (bug 79885)
 r=ducarroz@netscape.com, sr=mscott@netscape.com, a=dbaron@fas.harvard.edu

---
 .../resources/content/MsgComposeCommands.js   |  12 +-
 .../public/nsILDAPAutoCompleteSession.idl     |  13 +-
 .../src/nsLDAPAutoCompleteSession.cpp         | 270 +++++++++++++++---
 .../src/nsLDAPAutoCompleteSession.h           |  14 +-
 4 files changed, 261 insertions(+), 48 deletions(-)

diff --git a/mailnews/compose/resources/content/MsgComposeCommands.js b/mailnews/compose/resources/content/MsgComposeCommands.js
index d2690ccdf21a..832f3d7a4fa1 100644
--- a/mailnews/compose/resources/content/MsgComposeCommands.js
+++ b/mailnews/compose/resources/content/MsgComposeCommands.js
@@ -1879,8 +1879,18 @@ function LoadIdentity(startup)
                 // nsLDAPAutoCompleteSession use its default.
             }
 
+            // override autocomplete entry formatting?
+            //
+            try { 
+                session2.outputFormat = 
+                    prefs.CopyUnicodePref(autocompleteDirectory + 
+                                     ".autoComplete.outputFormat");
+            } catch (ex) {
+                // if this pref isn't there, no big deal.  just let
+                // nsLDAPAutoCompleteSession use its default.
+            }
+
             session2.filterTemplate = "cn=";
-            session2.outputFormat = "cn <mail>";
             session2.sizeLimit = 10;
         }
       }
diff --git a/xpfe/components/autocomplete/public/nsILDAPAutoCompleteSession.idl b/xpfe/components/autocomplete/public/nsILDAPAutoCompleteSession.idl
index 8e32fb81c9a5..0d9896147d65 100644
--- a/xpfe/components/autocomplete/public/nsILDAPAutoCompleteSession.idl
+++ b/xpfe/components/autocomplete/public/nsILDAPAutoCompleteSession.idl
@@ -44,9 +44,16 @@ interface nsILDAPAutoCompleteSession : nsIAutoCompleteSession {
     attribute wstring filterTemplate;
 
     /**
-     * A template for formatting the autocompletion results.  
-     * 
-     * XXX syntax to-be-determined
+     * A template string for formatting the autocompletion results.   
+     *
+     * Required attributes are delimited by curly braces, and optional 
+     * attributes are determined by brackets.  Backslash escapes any 
+     * character, including itself.  The default template is
+     * "[cn] <{mail}>", without the quotes.  This will generate autocomplete
+     * items of the form "John Doe <john.doe@foo.com>", or, if the cn is not
+     * found, " <john.doe@foo.com>".  This latter form is suboptimal, in that
+     * the space and angle brackets are a bit ugly, but should work.  See
+     * bug 81961.
      *
      * @exception NS_ERROR_NULL_POINTER     NULL pointer passed to getter
      * @exception NS_ERROR_OUT_OF_MEMORY    Getter couldn't allocate string
diff --git a/xpfe/components/autocomplete/src/nsLDAPAutoCompleteSession.cpp b/xpfe/components/autocomplete/src/nsLDAPAutoCompleteSession.cpp
index b886300f0509..8d7ccff3bd10 100644
--- a/xpfe/components/autocomplete/src/nsLDAPAutoCompleteSession.cpp
+++ b/xpfe/components/autocomplete/src/nsLDAPAutoCompleteSession.cpp
@@ -21,7 +21,6 @@
  *
  */
 
-
 // Work around lack of conditional build logic in codewarrior's
 // build system.  The MOZ_LDAP_XPCOM preprocessor symbol is only 
 // defined on Mac because noone else needs this weirdness; thus 
@@ -33,7 +32,6 @@
 
 #include "nsLDAPAutoCompleteSession.h"
 #include "nsIComponentManager.h"
-#include "nsIConsoleService.h"
 #include "nsIServiceManager.h"
 #include "nsIProxyObjectManager.h"
 #include "nsString.h"
@@ -442,12 +440,6 @@ nsLDAPAutoCompleteSession::OnLDAPBind(nsILDAPMessage *aMessage)
 nsresult
 nsLDAPAutoCompleteSession::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
 {
-    // ldap attribute names used to fill in fields in nsIAutoCompleteItem
-    //
-    // XXXdmose need to get this from outputFormat
-    //
-    const char *valueField="mail";
-
     nsresult rv;        // temporary for return vals
 
     PR_LOG(sLDAPAutoCompleteLogModule, PR_LOG_DEBUG, 
@@ -460,6 +452,18 @@ nsLDAPAutoCompleteSession::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
                  "nsLDAPAutoCompleteSession::OnLDAPSearchEntry(): "
                  "mResultsArrayItems is uninitialized");
 
+    nsAutoString value;
+    rv = GenerateAutoCompleteValue(aMessage, value);
+    if (NS_FAILED(rv)) {
+        // Something went wrong lower down the stack; a message should have 
+        // already been logged there.  Return an error (which ultimately gets
+        // ignored, since this is being called through an async proxy).  But
+        // the important thing is that we're bailing out here rather than 
+        // trying to generate a bogus nsIAutoCompleteItem
+        //
+        return NS_ERROR_FAILURE;
+    }
+
     // create an nsIAutoCompleteItem to hold the returned value
     //
     nsCOMPtr<nsIAutoCompleteItem> item = do_CreateInstance(
@@ -476,42 +480,11 @@ nsLDAPAutoCompleteSession::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
         //
         return NS_ERROR_FAILURE;
     }
-        
-    // get the attribute values for the field which will be used to fill in
-    // nsIAutoCompleteItem::value
-    //
-    PRUint32 numVals;
-    PRUnichar **values;
-
-    rv = aMessage->GetValues(valueField, &numVals, &values);
-    if (NS_FAILED(rv)) {
-
-        switch (rv) {
-        case NS_ERROR_LDAP_DECODING_ERROR:
-            // XXXdmose log error for non-debug builds?  could mean that this 
-            // entry didn't have an attr named "email".
-            //
-            NS_WARNING("nsLDAPAutoCompleteSession::OnLDAPSearchEntry(): "
-                       "error decoding email attribute");
-            return NS_ERROR_LDAP_DECODING_ERROR;
-
-        case NS_ERROR_OUT_OF_MEMORY:
-        case NS_ERROR_UNEXPECTED:
-            return rv;
-
-        default:
-            NS_ERROR("nsLDAPAutoCompleteSession::OnLDAPSearchEntry(): "
-                     "unexpected return code from aMessage->getValues()");
-            return NS_ERROR_UNEXPECTED;
-        }
-    }
 
     // just use the first value for the email attribute; subsequent values
-    // are ignored
+    // are ignored.  XXXdmose should do better than this; bug 76595.
     //
-    rv = item->SetValue(values[0]);
-    NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(numVals, values);
-
+    rv = item->SetValue(value.get());
     if (NS_FAILED(rv)) {
         NS_ERROR("nsLDAPAutoCompleteSession::OnLDAPSearchEntry(): "
                  "item->SetValue failed");
@@ -519,8 +492,6 @@ nsLDAPAutoCompleteSession::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
         return NS_ERROR_FAILURE;
     }
 
-    // XXXdmose process outputFormat here
-
     rv = mResultsArray->AppendElement(item);
     if (NS_FAILED(rv)) {
         NS_ERROR("nsLDAPAutoCompleteSession::OnLDAPSearchEntry(): "
@@ -535,6 +506,219 @@ nsLDAPAutoCompleteSession::OnLDAPSearchEntry(nsILDAPMessage *aMessage)
     return NS_OK;
 }
 
+// use outputFormat to generate an autocomplete value from attributes.  
+// any errors (including failure to find a required attribute) return 
+// an NS_ERROR_* up the stack so that the caller doesn't try and generate
+// an nsIAutoCompleteItem from this.
+//
+nsresult
+nsLDAPAutoCompleteSession::GenerateAutoCompleteValue(nsILDAPMessage *aMessage,
+                                                     nsAWritableString &aValue)
+{
+    nsresult rv;    // temp for return values
+
+    // if our outputFormat attribute hasn't been set, use the default
+    //
+    if (mOutputFormat.IsEmpty()) {
+        mOutputFormat = NS_LITERAL_STRING("[cn] <{mail}>");
+    }
+
+    // get some iterators to parse mOutputFormat
+    //
+    nsReadingIterator<PRUnichar> iter, iterEnd;
+    mOutputFormat.BeginReading(iter);
+    mOutputFormat.EndReading(iterEnd);
+
+    // get the console service for error logging
+    //
+    nsCOMPtr<nsIConsoleService> consoleSvc = 
+        do_GetService("@mozilla.org/consoleservice;1", &rv);
+    if (NS_FAILED(rv)) {
+        NS_WARNING("nsLDAPAutoCompleteSession::GenerateAutoCompleteValue(): "
+                   "couldn't get console service");
+    }
+
+    PRBool attrRequired = PR_FALSE;     // is this attr required or optional?
+
+    // parse until we hit the end of the string
+    //
+    while (iter != iterEnd) {
+
+        switch (*iter) {            // process the next char
+
+        case PRUnichar('{'):
+
+            attrRequired = PR_TRUE;  // this attribute is required
+
+            /*FALLTHROUGH*/
+
+        case PRUnichar('['):
+
+            rv = ProcessAttribute(iter, iterEnd, aMessage, attrRequired,
+                                  consoleSvc, aValue);
+            if ( NS_FAILED(rv) ) {
+
+                // something unrecoverable happened; stop parsing and 
+                // propagate the error up the stack
+                //
+                return NS_ERROR_FAILURE;
+            }
+
+            attrRequired = PR_FALSE; // reset to the default for the next pass
+
+            break;
+
+        case PRUnichar('\\'):
+
+            // advance the iterator and be sure we haven't run off the end
+            //
+            iter++;
+            if (iter == iterEnd) {
+
+                // abort; missing escaped char
+                //
+                if (consoleSvc) {
+                    consoleSvc->LogStringMessage(
+                        NS_LITERAL_STRING(
+                            "LDAP autocomplete session: error parsing outputFormat: premature end of string after \\ escape").get());
+
+                    NS_ERROR("LDAP autocomplete session: error parsing "
+                             "outputFormat: premature end of string after \\ "
+                             "escape");
+                }
+
+                return NS_ERROR_FAILURE;
+            }
+
+            /*FALLTHROUGH*/
+
+        default:
+
+            // this character gets treated as a literal
+            //
+            aValue.Append(*iter);
+
+        }
+
+        iter++; // advance the iterator
+    }
+
+    return NS_OK;
+}
+
+nsresult 
+nsLDAPAutoCompleteSession::ProcessAttribute(
+    nsReadingIterator<PRUnichar> &aIter,        // iterators for mOutputString
+    nsReadingIterator<PRUnichar> &aIterEnd, 
+    nsILDAPMessage *aMessage,                   // message being processed
+    PRBool aAttrRequired,                       // required?  or just optional?
+    nsCOMPtr<nsIConsoleService> &aConsoleSvc,   // no need to reacquire this
+    nsAWritableString &aValue)                  // value being built
+{
+    nsCAutoString attrName;
+
+    // reset attrname, and move past the opening brace
+    //
+    aIter++;
+
+    // get the rest of the attribute name
+    //
+    do {
+
+        // be sure we haven't run off the end
+        //
+        if (aIter == aIterEnd) {
+
+            // abort; missing closing delimiter
+            //
+            if (aConsoleSvc) {
+                aConsoleSvc->LogStringMessage(
+                    NS_LITERAL_STRING(
+                        "LDAP autocomplete session: error parsing outputFormat: missing } or ]").get());
+
+                NS_ERROR("LDAP autocomplete session: error parsing "
+                         "outputFormat: missing } or ]");
+            }
+
+            return NS_ERROR_FAILURE;
+
+        } else if ( (aAttrRequired && *aIter == PRUnichar('}')) || 
+                    *aIter == PRUnichar(']') ) {
+
+            // done with this attribute
+            //
+            break;
+
+        } else {
+
+            // this must be part of the attribute name
+            //
+            attrName.Append(char(*aIter));
+        }
+
+        aIter++;
+
+    } while (1);
+                      
+    // get the attribute values for the field which will be used 
+    // to fill in nsIAutoCompleteItem::value
+    //
+    PRUint32 numVals;
+    PRUnichar **values;
+
+    nsresult rv;
+    rv = aMessage->GetValues(attrName, &numVals, &values);
+    if (NS_FAILED(rv)) {
+
+        switch (rv) {
+        case NS_ERROR_LDAP_DECODING_ERROR:
+            // this may not be an error, per se; it could just be that the 
+            // requested attribute does not exist in this particular message,
+            // either because we didn't request it with the search operation,
+            // or because it doesn't exist on the server.
+            //
+            PR_LOG(sLDAPAutoCompleteLogModule, PR_LOG_WARNING,
+                   ("nsLDAPAutoCompleteSession::OnLDAPSearchEntry(): "
+                       "couldn't decode '%s' attribute", attrName.get()));
+            break;
+
+        case NS_ERROR_OUT_OF_MEMORY:
+        case NS_ERROR_UNEXPECTED:
+            break;
+
+        default:
+            NS_ERROR("nsLDAPAutoCompleteSession::OnLDAPSearchEntry(): "
+                     "unexpected return code from aMessage->getValues()");
+            rv = NS_ERROR_UNEXPECTED;
+            break;
+        }
+
+        // if this was a required attribute, don't append anything to aValue
+        // and return the error code
+        //
+        if (aAttrRequired) {
+            return rv;
+        } else {
+            // otherwise forget about this attribute, but return NS_OK, which
+            // will cause our caller to continue processing outputFormat in 
+            // order to generate an nsIAutoCompleteItem.
+            //
+            return NS_OK;
+        }
+    }
+
+    // append the value to our string; then free the array of results
+    //
+    aValue.Append(values[0]);
+    NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(numVals, values);
+
+    // if this attribute wasn't required, we fall through to here, and return 
+    // ok
+    //
+    return NS_OK;
+
+}
+
 nsresult
 nsLDAPAutoCompleteSession::OnLDAPSearchResult(nsILDAPMessage *aMessage)
 {
diff --git a/xpfe/components/autocomplete/src/nsLDAPAutoCompleteSession.h b/xpfe/components/autocomplete/src/nsLDAPAutoCompleteSession.h
index d3685f7696e8..80379e5d425a 100644
--- a/xpfe/components/autocomplete/src/nsLDAPAutoCompleteSession.h
+++ b/xpfe/components/autocomplete/src/nsLDAPAutoCompleteSession.h
@@ -29,6 +29,7 @@
 #include "nsILDAPURL.h"
 #include "nsString.h"
 #include "nsISupportsArray.h"
+#include "nsIConsoleService.h"
 
 // 964665d0-1dd1-11b2-aeae-897834fb00b9
 //
@@ -62,7 +63,7 @@ class nsLDAPAutoCompleteSession : public nsILDAPMessageListener,
     nsCOMPtr<nsILDAPURL> mServerURL;        // URL for the directory to search
     PRInt32 mSizeLimit;                     // return at most this many entries
     PRUint32 mMinStringLength;              // strings < this size are ignored
-
+    
     // stopgap until nsLDAPService works
     nsresult InitConnection();             
 
@@ -72,6 +73,17 @@ class nsLDAPAutoCompleteSession : public nsILDAPMessageListener,
     // add to the results set
     nsresult OnLDAPSearchEntry(nsILDAPMessage *aMessage); 
 
+    // use outputFormat to generate an autocomplete value from attributes
+    nsresult GenerateAutoCompleteValue(nsILDAPMessage *aMessage, 
+                                       nsAWritableString &aValue);
+
+    // process a single attribute while parsing outputFormat
+    nsresult ProcessAttribute(nsReadingIterator<PRUnichar> & aIter,  
+                              nsReadingIterator<PRUnichar> & aIterEnd, 
+                              nsILDAPMessage *aMessage, PRBool aAttrRequired,
+                              nsCOMPtr<nsIConsoleService> & aConsoleSvc,
+                              nsAWritableString & aValue);
+
     // all done; call OnAutoComplete
     nsresult OnLDAPSearchResult(nsILDAPMessage *aMessage);