/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIAddrDatabase.h"
#include "nsStringGlue.h"
#include "nsAbLDIFService.h"
#include "nsIFile.h"
#include "nsILineInputStream.h"
#include "nsIInputStream.h"
#include "nsNetUtil.h"
#include "nsISeekableStream.h"
#include "mdb.h"
#include "plstr.h"
#include "prmem.h"
#include "prprf.h"
#include "nsCRTGlue.h"
#include "nsTArray.h"

#include <ctype.h>

NS_IMPL_ISUPPORTS(nsAbLDIFService, nsIAbLDIFService)

// If we get a line longer than 32K it's just toooooo bad!
#define kTextAddressBufferSz    (64 * 1024)

nsAbLDIFService::nsAbLDIFService()
{
  mStoreLocAsHome = false;
  mLFCount = 0;
  mCRCount = 0;
}

nsAbLDIFService::~nsAbLDIFService()
{
}

#define RIGHT2            0x03
#define RIGHT4            0x0f
#define CONTINUED_LINE_MARKER    '\001'

// XXX TODO fix me
// use the NSPR base64 library.  see plbase64.h
// see bug #145367
static unsigned char b642nib[0x80] = {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
    0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
    0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
    0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
    0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
};

NS_IMETHODIMP nsAbLDIFService::ImportLDIFFile(nsIAddrDatabase *aDb, nsIFile *aSrc, bool aStoreLocAsHome, uint32_t *aProgress)
{
  NS_ENSURE_ARG_POINTER(aSrc);
  NS_ENSURE_ARG_POINTER(aDb);

  mStoreLocAsHome = aStoreLocAsHome;

  char buf[1024];
  char* pBuf = &buf[0];
  int32_t startPos = 0;
  uint32_t len = 0;
  nsTArray<int32_t> listPosArray;   // where each list/group starts in ldif file
  nsTArray<int32_t> listSizeArray;  // size of the list/group info
  int32_t savedStartPos = 0;
  int32_t filePos = 0;
  uint64_t bytesLeft = 0;

  nsCOMPtr<nsIInputStream> inputStream;
  nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
  NS_ENSURE_SUCCESS(rv, rv);

  // Initialize the parser for a run...
  mLdifLine.Truncate();

  while (NS_SUCCEEDED(inputStream->Available(&bytesLeft)) && bytesLeft > 0)
  {
    if (NS_SUCCEEDED(inputStream->Read(pBuf, sizeof(buf), &len)) && len > 0)
    {
      startPos = 0;

      while (NS_SUCCEEDED(GetLdifStringRecord(buf, len, startPos)))
      {
        if (mLdifLine.Find("groupOfNames") == -1)
          AddLdifRowToDatabase(aDb, false);
        else
        {
          //keep file position for mailing list
          listPosArray.AppendElement(savedStartPos);
          listSizeArray.AppendElement(filePos + startPos-savedStartPos);
          ClearLdifRecordBuffer();
        }
        savedStartPos = filePos + startPos;
      }
      filePos += len;
      if (aProgress)
        *aProgress = (uint32_t)filePos;
    }
  }
  //last row
  if (!mLdifLine.IsEmpty() && mLdifLine.Find("groupOfNames") == -1)
    AddLdifRowToDatabase(aDb, false);

  // mail Lists
  int32_t i, pos;
  uint32_t size;
  int32_t listTotal = listPosArray.Length();
  char *listBuf;
  ClearLdifRecordBuffer();  // make sure the buffer is clean

  nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(inputStream, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  for (i = 0; i < listTotal; i++)
  {
    pos  = listPosArray[i];
    size = listSizeArray[i];
    if (NS_SUCCEEDED(seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, pos)))
    {
      // Allocate enough space for the lists/groups as the size varies.
      listBuf = (char *) PR_Malloc(size);
      if (!listBuf)
        continue;
      if (NS_SUCCEEDED(inputStream->Read(listBuf, size, &len)) && len > 0)
      {
        startPos = 0;

        while (NS_SUCCEEDED(GetLdifStringRecord(listBuf, len, startPos)))
        {
          if (mLdifLine.Find("groupOfNames") != -1)
          {
            AddLdifRowToDatabase(aDb, true);
            if (NS_SUCCEEDED(seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0)))
              break;
          }
        }
      }
    PR_FREEIF(listBuf);
    }
  }

  rv = inputStream->Close();
  NS_ENSURE_SUCCESS(rv, rv);

  // Finally commit everything to the database and return.
  return aDb->Commit(nsAddrDBCommitType::kLargeCommit);
}

/*
 * str_parse_line - takes a line of the form "type:[:] value" and splits it
 * into components "type" and "value".  if a double colon separates type from
 * value, then value is encoded in base 64, and parse_line un-decodes it
 * (in place) before returning.
 * in LDIF, non-ASCII data is treated as base64 encoded UTF-8 
 */

nsresult nsAbLDIFService::str_parse_line(char *line, char **type, char **value, int *vlen) const
{
  char    *p, *s, *d, *byte, *stop;
  char    nib;
  int    i, b64;

  /* skip any leading space */
  while ( isspace( *line ) ) {
    line++;
  }
  *type = line;

  for ( s = line; *s && *s != ':'; s++ )
    ;    /* NULL */
  if ( *s == '\0' ) {
    return NS_ERROR_FAILURE;
  }

  /* trim any space between type and : */
  for ( p = s - 1; p > line && isspace( *p ); p-- ) {
    *p = '\0';
  }
  *s++ = '\0';

  /* check for double : - indicates base 64 encoded value */
  if ( *s == ':' ) {
    s++;
    b64 = 1;
  /* single : - normally encoded value */
  } else {
    b64 = 0;
  }

  /* skip space between : and value */
  while ( isspace( *s ) ) {
    s++;
  }

  /* if no value is present, error out */
  if ( *s == '\0' ) {
    return NS_ERROR_FAILURE;
  }

  /* check for continued line markers that should be deleted */
  for ( p = s, d = s; *p; p++ ) {
    if ( *p != CONTINUED_LINE_MARKER )
      *d++ = *p;
  }
  *d = '\0';

  *value = s;
  if ( b64 ) {
    stop = PL_strchr( s, '\0' );
    byte = s;
    for ( p = s, *vlen = 0; p < stop; p += 4, *vlen += 3 ) {
      for ( i = 0; i < 3; i++ ) {
        if ( p[i] != '=' && (p[i] & 0x80 ||
             b642nib[ p[i] & 0x7f ] > 0x3f) ) {
          return NS_ERROR_FAILURE;
        }
      }

      /* first digit */
      nib = b642nib[ p[0] & 0x7f ];
      byte[0] = nib << 2;
      /* second digit */
      nib = b642nib[ p[1] & 0x7f ];
      byte[0] |= nib >> 4;
      byte[1] = (nib & RIGHT4) << 4;
      /* third digit */
      if ( p[2] == '=' ) {
        *vlen += 1;
        break;
      }
      nib = b642nib[ p[2] & 0x7f ];
      byte[1] |= nib >> 2;
      byte[2] = (nib & RIGHT2) << 6;
      /* fourth digit */
      if ( p[3] == '=' ) {
        *vlen += 2;
        break;
      }
      nib = b642nib[ p[3] & 0x7f ];
      byte[2] |= nib;

      byte += 3;
    }
    s[ *vlen ] = '\0';
  } else {
    *vlen = (int) (d - s);
  }
  return NS_OK;
}

/*
 * str_getline - return the next "line" (minus newline) of input from a
 * string buffer of lines separated by newlines, terminated by \n\n
 * or \0.  this routine handles continued lines, bundling them into
 * a single big line before returning.  if a line begins with a white
 * space character, it is a continuation of the previous line. the white
 * space character (nb: only one char), and preceeding newline are changed
 * into CONTINUED_LINE_MARKER chars, to be deleted later by the
 * str_parse_line() routine above.
 *
 * it takes a pointer to a pointer to the buffer on the first call,
 * which it updates and must be supplied on subsequent calls.
 */

char* nsAbLDIFService::str_getline(char **next) const
{
  char    *lineStr;
  char    c;

  if ( *next == nullptr || **next == '\n' || **next == '\0' ) {
    return( nullptr);
  }

  lineStr = *next;
  while ( (*next = PL_strchr( *next, '\n' )) != NULL ) {
    c = *(*next + 1);
    if ( isspace( c ) && c != '\n' ) {
      **next = CONTINUED_LINE_MARKER;
      *(*next+1) = CONTINUED_LINE_MARKER;
    } else {
      *(*next)++ = '\0';
      break;
    }
  }

  return( lineStr );
}

nsresult nsAbLDIFService::GetLdifStringRecord(char* buf, int32_t len, int32_t& stopPos)
{
  for (; stopPos < len; stopPos++) 
  {
    char c = buf[stopPos];

    if (c == 0xA)
    {
      mLFCount++;
    }
    else if (c == 0xD)
    {
      mCRCount++;
    }
    else
    {
      if (mLFCount == 0 && mCRCount == 0)
        mLdifLine.Append(c);
      else if (( mLFCount > 1) || ( mCRCount > 2 && mLFCount ) ||
               ( !mLFCount && mCRCount > 1 ))
      {
        return NS_OK;
      }
      else if ((mLFCount == 1 || mCRCount == 1))
      {
        mLdifLine.Append('\n');
        mLdifLine.Append(c);
        mLFCount = 0;
        mCRCount = 0;
      }
    }
  }

  if (((stopPos == len) && (mLFCount > 1)) || (mCRCount > 2 && mLFCount) ||
      (!mLFCount && mCRCount > 1))
    return NS_OK;

  return NS_ERROR_FAILURE;
}

void nsAbLDIFService::AddLdifRowToDatabase(nsIAddrDatabase *aDatabase,
                                           bool bIsList)
{
  // If no data to process then reset CR/LF counters and return.
  if (mLdifLine.IsEmpty())
  {
    mLFCount = 0;
    mCRCount = 0;
    return;
  }

  nsCOMPtr <nsIMdbRow> newRow;
  if (aDatabase)
  {
    if (bIsList)
      aDatabase->GetNewListRow(getter_AddRefs(newRow)); 
    else
      aDatabase->GetNewRow(getter_AddRefs(newRow)); 

    if (!newRow)
      return;
  }
  else
    return;

  char* cursor = ToNewCString(mLdifLine); 
  char* saveCursor = cursor;  /* keep for deleting */ 
  char* line = 0; 
  char* typeSlot = 0; 
  char* valueSlot = 0; 
  int length = 0;  // the length  of an ldif attribute
  while ( (line = str_getline(&cursor)) != nullptr)
  {
    if (NS_SUCCEEDED(str_parse_line(line, &typeSlot, &valueSlot, &length))) {
      AddLdifColToDatabase(aDatabase, newRow, typeSlot, valueSlot, bIsList);
    }
    else
      continue; // parse error: continue with next loop iteration
  }
  free(saveCursor);
  aDatabase->AddCardRowToDB(newRow);    

  if (bIsList)
    aDatabase->AddListDirNode(newRow);
        
  // Clear buffer for next record
  ClearLdifRecordBuffer();
}

void nsAbLDIFService::AddLdifColToDatabase(nsIAddrDatabase *aDatabase,
                                           nsIMdbRow* newRow, char* typeSlot,
                                           char* valueSlot, bool bIsList)
{
  nsAutoCString colType(typeSlot);
  nsAutoCString column(valueSlot);

  // 4.x exports attributes like "givenname", 
  // mozilla does "givenName" to be compliant with RFC 2798
  ToLowerCase(colType);

  mdb_u1 firstByte = (mdb_u1)(colType.get())[0];
  switch ( firstByte )
  {
  case 'b':
    if (colType.EqualsLiteral("birthyear"))
      aDatabase->AddBirthYear(newRow, column.get());
    else if (colType.EqualsLiteral("birthmonth"))
      aDatabase->AddBirthMonth(newRow, column.get());
    else if (colType.EqualsLiteral("birthday"))
      aDatabase->AddBirthDay(newRow, column.get());
    break; // 'b'

  case 'c':
    if (colType.EqualsLiteral("cn") || colType.EqualsLiteral("commonname"))
    {
      if (bIsList)
        aDatabase->AddListName(newRow, column.get());
      else
        aDatabase->AddDisplayName(newRow, column.get());
    }
    else if (colType.EqualsLiteral("c") || colType.EqualsLiteral("countryname"))
    {
      if (mStoreLocAsHome )
        aDatabase->AddHomeCountry(newRow, column.get());
      else
        aDatabase->AddWorkCountry(newRow, column.get());
    }

    else if (colType.EqualsLiteral("cellphone") )
      aDatabase->AddCellularNumber(newRow, column.get());

    else if (colType.EqualsLiteral("carphone"))
      aDatabase->AddCellularNumber(newRow, column.get());
        
    else if (colType.EqualsLiteral("custom1"))
      aDatabase->AddCustom1(newRow, column.get());
        
    else if (colType.EqualsLiteral("custom2"))
      aDatabase->AddCustom2(newRow, column.get());
        
    else if (colType.EqualsLiteral("custom3"))
      aDatabase->AddCustom3(newRow, column.get());
        
    else if (colType.EqualsLiteral("custom4"))
      aDatabase->AddCustom4(newRow, column.get());
        
    else if (colType.EqualsLiteral("company"))
      aDatabase->AddCompany(newRow, column.get());
    break; // 'c'

  case 'd':
    if (colType.EqualsLiteral("description"))
    {
      if (bIsList)
        aDatabase->AddListDescription(newRow, column.get());
      else
        aDatabase->AddNotes(newRow, column.get());
    }

    else if (colType.EqualsLiteral("department"))
      aDatabase->AddDepartment(newRow, column.get());

    else if (colType.EqualsLiteral("displayname"))
    {
      if (bIsList)
        aDatabase->AddListName(newRow, column.get());
      else
        aDatabase->AddDisplayName(newRow, column.get());
    }
    break; // 'd'

  case 'f':

    if (colType.EqualsLiteral("fax") ||
        colType.EqualsLiteral("facsimiletelephonenumber"))
      aDatabase->AddFaxNumber(newRow, column.get());
    break; // 'f'

  case 'g':
    if (colType.EqualsLiteral("givenname"))
      aDatabase->AddFirstName(newRow, column.get());

    break; // 'g'

  case 'h':
    if (colType.EqualsLiteral("homephone"))
      aDatabase->AddHomePhone(newRow, column.get());

    else if (colType.EqualsLiteral("homestreet"))
      aDatabase->AddHomeAddress(newRow, column.get());

    else if (colType.EqualsLiteral("homeurl"))
      aDatabase->AddWebPage2(newRow, column.get());
    break; // 'h'

  case 'l':
    if (colType.EqualsLiteral("l") || colType.EqualsLiteral("locality"))
    {
      if (mStoreLocAsHome)
        aDatabase->AddHomeCity(newRow, column.get());
      else
        aDatabase->AddWorkCity(newRow, column.get());
    }
    // labeledURI contains a URI and, optionally, a label
    // This will remove the label and place the URI as the work URL
    else if (colType.EqualsLiteral("labeleduri"))
    {
      int32_t index = column.FindChar(' ');
      if (index != -1)
        column.SetLength(index);

      aDatabase->AddWebPage1(newRow, column.get());
    }

    break; // 'l'

  case 'm':
    if (colType.EqualsLiteral("mail"))
      aDatabase->AddPrimaryEmail(newRow, column.get());

    else if (colType.EqualsLiteral("member") && bIsList)
      aDatabase->AddLdifListMember(newRow, column.get());

    else if (colType.EqualsLiteral("mobile"))
      aDatabase->AddCellularNumber(newRow, column.get());

    else if (colType.EqualsLiteral("mozilla_aimscreenname"))
      aDatabase->AddAimScreenName(newRow, column.get());

    else if (colType.EqualsLiteral("mozillacustom1"))
      aDatabase->AddCustom1(newRow, column.get());
        
    else if (colType.EqualsLiteral("mozillacustom2"))
      aDatabase->AddCustom2(newRow, column.get());
        
    else if (colType.EqualsLiteral("mozillacustom3"))
      aDatabase->AddCustom3(newRow, column.get());
        
    else if (colType.EqualsLiteral("mozillacustom4"))
      aDatabase->AddCustom4(newRow, column.get());

    else if (colType.EqualsLiteral("mozillahomecountryname"))
      aDatabase->AddHomeCountry(newRow, column.get());

    else if (colType.EqualsLiteral("mozillahomelocalityname"))
      aDatabase->AddHomeCity(newRow, column.get());

    else if (colType.EqualsLiteral("mozillahomestate"))
      aDatabase->AddHomeState(newRow, column.get());

    else if (colType.EqualsLiteral("mozillahomestreet"))
      aDatabase->AddHomeAddress(newRow, column.get());

    else if (colType.EqualsLiteral("mozillahomestreet2"))
      aDatabase->AddHomeAddress2(newRow, column.get());

    else if (colType.EqualsLiteral("mozillahomepostalcode"))
      aDatabase->AddHomeZipCode(newRow, column.get());

    else if (colType.EqualsLiteral("mozillahomeurl"))
      aDatabase->AddWebPage2(newRow, column.get());

    else if (colType.EqualsLiteral("mozillanickname"))
    {
      if (bIsList)
        aDatabase->AddListNickName(newRow, column.get());
      else
        aDatabase->AddNickName(newRow, column.get());
    }

    else if (colType.EqualsLiteral("mozillasecondemail"))
      aDatabase->Add2ndEmail(newRow, column.get());

    else if (colType.EqualsLiteral("mozillausehtmlmail"))
    {
      ToLowerCase(column);
      if (-1 != column.Find("true"))
        aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::html);
      else if (-1 != column.Find("false"))
        aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::plaintext);
      else
        aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::unknown);
    }

    else if (colType.EqualsLiteral("mozillaworkstreet2"))
      aDatabase->AddWorkAddress2(newRow, column.get());

    else if (colType.EqualsLiteral("mozillaworkurl"))
      aDatabase->AddWebPage1(newRow, column.get());

    break; // 'm'

  case 'n':
    if (colType.EqualsLiteral("notes"))
      aDatabase->AddNotes(newRow, column.get());

    else if (colType.EqualsLiteral("nscpaimscreenname") || 
             colType.EqualsLiteral("nsaimid"))
      aDatabase->AddAimScreenName(newRow, column.get());

    break; // 'n'

  case 'o':
    if (colType.EqualsLiteral("objectclass"))
      break;

    else if (colType.EqualsLiteral("ou") || colType.EqualsLiteral("orgunit"))
      aDatabase->AddDepartment(newRow, column.get());

    else if (colType.EqualsLiteral("o")) // organization
      aDatabase->AddCompany(newRow, column.get());

    break; // 'o'

  case 'p':
    if (colType.EqualsLiteral("postalcode"))
    {
      if (mStoreLocAsHome)
        aDatabase->AddHomeZipCode(newRow, column.get());
      else
        aDatabase->AddWorkZipCode(newRow, column.get());
    }

    else if (colType.EqualsLiteral("postofficebox"))
    {
      nsAutoCString workAddr1, workAddr2;
      SplitCRLFAddressField(column, workAddr1, workAddr2);
      aDatabase->AddWorkAddress(newRow, workAddr1.get());
      aDatabase->AddWorkAddress2(newRow, workAddr2.get());
    }
    else if (colType.EqualsLiteral("pager") || colType.EqualsLiteral("pagerphone"))
      aDatabase->AddPagerNumber(newRow, column.get());

    break; // 'p'

  case 'r':
    if (colType.EqualsLiteral("region"))
    {
      aDatabase->AddWorkState(newRow, column.get());
    }

    break; // 'r'

  case 's':
    if (colType.EqualsLiteral("sn") || colType.EqualsLiteral("surname"))
      aDatabase->AddLastName(newRow, column.get());

    else if (colType.EqualsLiteral("street"))
      aDatabase->AddWorkAddress(newRow, column.get());

    else if (colType.EqualsLiteral("streetaddress"))
    {
      nsAutoCString addr1, addr2;
      SplitCRLFAddressField(column, addr1, addr2);
      if (mStoreLocAsHome)
      {
        aDatabase->AddHomeAddress(newRow, addr1.get());
        aDatabase->AddHomeAddress2(newRow, addr2.get());
      }
      else
      {
        aDatabase->AddWorkAddress(newRow, addr1.get());
        aDatabase->AddWorkAddress2(newRow, addr2.get());
      }
    }
    else if (colType.EqualsLiteral("st"))
    {
    if (mStoreLocAsHome)
      aDatabase->AddHomeState(newRow, column.get());
    else
      aDatabase->AddWorkState(newRow, column.get());
    }
        
    break; // 's'

  case 't':
    if (colType.EqualsLiteral("title"))
      aDatabase->AddJobTitle(newRow, column.get());

    else if (colType.EqualsLiteral("telephonenumber") )
    {
      aDatabase->AddWorkPhone(newRow, column.get());
    }

    break; // 't'

  case 'u':

    if (colType.EqualsLiteral("uniquemember") && bIsList)
      aDatabase->AddLdifListMember(newRow, column.get());

    break; // 'u'

  case 'w':
    if (colType.EqualsLiteral("workurl"))
      aDatabase->AddWebPage1(newRow, column.get());

    break; // 'w'

  case 'x':
    if (colType.EqualsLiteral("xmozillanickname"))
    {
      if (bIsList)
        aDatabase->AddListNickName(newRow, column.get());
      else
        aDatabase->AddNickName(newRow, column.get());
    }

    else if (colType.EqualsLiteral("xmozillausehtmlmail"))
    {
      ToLowerCase(column);
      if (-1 != column.Find("true"))
        aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::html);
      else if (-1 != column.Find("false"))
        aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::plaintext);
      else
        aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::unknown);
    }

    break; // 'x'

  case 'z':
    if (colType.EqualsLiteral("zip")) // alias for postalcode
    {
      if (mStoreLocAsHome)
        aDatabase->AddHomeZipCode(newRow, column.get());
      else
        aDatabase->AddWorkZipCode(newRow, column.get());
    }

    break; // 'z'

  default:
    break; // default
  }
}

void nsAbLDIFService::ClearLdifRecordBuffer()
{
  if (!mLdifLine.IsEmpty())
  {
    mLdifLine.Truncate();
    mLFCount = 0;
    mCRCount = 0;
  }
}

// Some common ldif fields, it an ldif file has NONE of these entries
// then it is most likely NOT an ldif file!
static const char *const sLDIFFields[] = {
    "objectclass",
    "sn",
    "dn",
    "cn",
    "givenName",
    "mail",
    nullptr
};
#define kMaxLDIFLen        14

// Count total number of legal ldif fields and records in the first 100 lines of the 
// file and if the average legal ldif field is 3 or higher than it's a valid ldif file.
NS_IMETHODIMP nsAbLDIFService::IsLDIFFile(nsIFile *pSrc, bool *_retval)
{
  NS_ENSURE_ARG_POINTER(pSrc);
  NS_ENSURE_ARG_POINTER(_retval);

  *_retval = false;

  nsresult rv = NS_OK;

  nsCOMPtr<nsIInputStream> fileStream;
  rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), pSrc);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t lineLen = 0;
  int32_t lineCount = 0;
  int32_t ldifFields = 0;  // total number of legal ldif fields.
  char field[kMaxLDIFLen];
  int32_t fLen = 0;
  const char *pChar;
  int32_t recCount = 0;  // total number of records.
  int32_t i;
  bool gotLDIF = false;
  bool more = true;
  nsCString line;

  while (more && NS_SUCCEEDED(rv) && (lineCount < 100))
  {
    rv = lineInputStream->ReadLine(line, &more);

    if (NS_SUCCEEDED(rv) && more)
    {
      pChar = line.get();
      lineLen = line.Length();
      if (!lineLen && gotLDIF)
      {
        recCount++;
        gotLDIF = false;
      }
                   
      if (lineLen && (*pChar != ' ') && (*pChar != '\t'))
      {
        fLen = 0;

        while (lineLen && (fLen < (kMaxLDIFLen - 1)) && (*pChar != ':'))
        {
          field[fLen] = *pChar;
          pChar++;
          fLen++;
          lineLen--;
        }
                
        field[fLen] = 0;

        if (lineLen && (*pChar == ':') && (fLen < (kMaxLDIFLen - 1)))
        {
          // see if this is an ldif field (case insensitive)?
          i = 0;
          while (sLDIFFields[i])
          {
            if (!PL_strcasecmp( sLDIFFields[i], field))
            {
              ldifFields++;
              gotLDIF = true;
              break;
            }
            i++;
          }
        }
      }
    }
    lineCount++;
  }

  // If we just saw ldif address, increment recCount.
  if (gotLDIF)
    recCount++;

  rv = fileStream->Close();

  if (recCount > 1)
    ldifFields /= recCount;

  // If the average field number >= 3 then it's a good ldif file.
  if (ldifFields >= 3)
  {
    *_retval = true;
  }

  return rv;
}

void nsAbLDIFService::SplitCRLFAddressField(nsCString &inputAddress, nsCString &outputLine1, nsCString &outputLine2) const
{
  int32_t crlfPos = inputAddress.Find("\r\n");
  if (crlfPos != -1)
  {
    outputLine1 = Substring(inputAddress, 0, crlfPos);
    outputLine2 = Substring(inputAddress, crlfPos + 2);
  }
  else
    outputLine1.Assign(inputAddress);
}