//
// This C++ program converts an exported Netscape Messenger address book into
// a Polarbar Mailer HTML address book file.
//
// This program is protected by US law and international treaty.
// Copyright 2000 by David G. Holm, Berrien Springs, Michigan, USA.
// All rights reserved. Permission for royalty-free distribute of the
// source code and any programs derived from the source code is hereby
// granted, provided that the copyright and permission notices remain
// in the source code and are not modified in any manner.
//

#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define VERSION "Beta 3"
#define IN_BUF_MAX 4096
#define PATH_MAX   256
#define POLARBAR_ADDRESSBOOK_EXTENSION "AddressBook.html"
#define POLARBAR_ADDRESSBOOK_PREFIX \
	"<HTML><!-- ADDRESS BOOK -->\n" \
	"<HEAD><TITLE>J Street Address Book</TITLE></HEAD><BODY>\n" \
	"<P><B>NICKNAME:</B> \n" \
	"<P><B>DOMAIN:</B> \n" \
	"<P><B>SORTSTYLE:</B> 0\n" \
	"<P><B>SORTTYPE:</B> 0\n" \
	"<TABLE BORDER=1 CELLPADDING=4>\n" \
	"<TH>Nickname</TH>\n" \
	"<TH>Email Address</TH>\n" \
	"<TH>Full Name</TH>\n" \
	"<TH>Groups</TH>\n" \
	"<TH>Alert</TH>\n" \
	"<TH>Folder</TH>\n" \
	"<TH>Notes</TH>\n" \
	"<TH>Title</TH>\n" \
	"<TH>Organization</TH>\n" \
	"<TH>Postal Address</TH>\n" \
	"<TH>Phone Number</TH>\n" \
	"<TH>Fax Number</TH>\n" \
	"<TH>Short List</TH>\n" \
	"<TH>Persona</TH>\n"
#define POLARBAR_ADDRESSBOOK_END \
	"</TABLE>\n"
#define POLARBAR_ADDRESSBOOK_POSTFIX \
	"</BODY></HTML>\n"
#define POLARBAR_GROUP_PREFIX \
	"<P><B>%s</B> [%s]\n" \
	"<UL>\n"
#define POLARBAR_GROUP_ENTRY \
	"<LI>%s\n"
#define POLARBAR_GROUP_POSTFIX \
	"</UL>\n"
#define PAB_ADDRESS_LINE_SEPARATOR \
        "<BR>"
#define PAB_ADDRESS_PART_SEPARATOR \
        " "
#define PAB_ENTRY_PREFIX \
	"<TR VALIGN=TOP>\n"
#define PAB_ENTRY_POSTFIX \
	"</TR>\n"
#define PAB_FIELD_PREFIX \
	"<TD>"
#define PAB_FIELD_POSTFIX \
	"</TD>\n"
#define HTML_BOLD_PREFIX \
	"<B>"
#define HTML_BOLD_POSTFIX \
	"</B>"
#define NS_BASE64_FIELD_INDICATOR \
	":: "
#define NS_FIELD_INDICATOR \
	": "

static const size_t max_string = 256;
struct entry_struct
{
   char nickname[ max_string ];
   char full_name[ max_string ];
   char email[ max_string ];
   entry_struct * next_ptr;
};

class nick_class
{
private:
   int size;
   struct entry_struct * first_ptr, * current_ptr, * last_ptr;

public:
   nick_class()
   { 
      size = 0;
      first_ptr = NULL;
      last_ptr = NULL;
      current_ptr = NULL;
   };
   ~nick_class()
   { 
      size = 0; 
      current_ptr = first_ptr;
      while( first_ptr )
      {
         first_ptr = current_ptr->next_ptr;
         delete current_ptr;
         current_ptr = first_ptr;
      }
   };

   int add_entry( const char * nick, const char * name, const char * email )
   {
      int len, count = 0;
      current_ptr = new entry_struct;
      {
         // Insert the new entry into the singly linked list.
         current_ptr->next_ptr = NULL;
         if( last_ptr )
         {
            last_ptr->next_ptr = current_ptr;
            last_ptr = current_ptr;
         }
         else if ( first_ptr )
         {
            first_ptr->next_ptr = current_ptr;
            last_ptr = current_ptr;
         }
         else first_ptr = current_ptr;
         count = ++size;

         // Save the data into the new entry.
         len = max_string - 1;
         if( nick ) strncpy( current_ptr->nickname, nick, max_string );
         else len = 0;
         current_ptr->nickname[ len ] = '\0';
         len = max_string - 1;
         if( name ) strncpy( current_ptr->full_name, name, max_string );
         else len = 0;
         current_ptr->full_name[ len ] = '\0';
         if( email ) strncpy( current_ptr->email, email, max_string );
         else len = 0;
         current_ptr->email[ len ] = '\0';
      }
      return count;
   };

   char * find_entry( const char * name, const char * email )
   {
      current_ptr = first_ptr;
      for( int i = 0; i < size; i++ )
      {
         if( strcmp( current_ptr->full_name, name ) == 0
         && strcmp( current_ptr->email, email ) == 0 )
         {
            return current_ptr->nickname;
         }
         current_ptr = current_ptr->next_ptr;
      }
      return NULL;
   };

   int get_size()
   {
      return size;
   }

   void first( void )
   {
      current_ptr = first_ptr;
   }

   void next( void )
   {
      if( current_ptr ) current_ptr = current_ptr->next_ptr;
   }

   char * get_nickname( void )
   {
      char * nick = NULL;
      if( current_ptr ) nick = current_ptr->nickname;
      return nick;
   }

   char * get_name( void )
   {
      char * nick = NULL;
      if( current_ptr ) nick = current_ptr->full_name;
      return nick;
   }

   char * get_email( void )
   {
      char * nick = NULL;
      if( current_ptr ) nick = current_ptr->email;
      return nick;
   }

} nicknames;

class group_class
{
private:
   int size;
   struct group_struct
   {
      entry_struct * entry;
      nick_class * nick;
      group_struct * next_ptr;
   } * first_ptr, * current_ptr, * last_ptr;

public:
   group_class()
   { 
      size = 0;
      first_ptr = NULL;
      last_ptr = NULL;
      current_ptr = NULL;
   };

   ~group_class()
   { 
      size = 0;
      current_ptr = first_ptr;
      while( first_ptr )
      {
         first_ptr = current_ptr->next_ptr;
         delete current_ptr;
         current_ptr = first_ptr;
      }
   };

   int add_group( const char * name, const char * nick )
   {
      int len, count = 0;
      current_ptr = new group_struct;
      {
         // Insert the new entry into the singly linked list.
         current_ptr->next_ptr = NULL;
         current_ptr->nick = new nick_class;
         current_ptr->entry = new entry_struct;
         if( last_ptr )
         {
            last_ptr->next_ptr = current_ptr;
            last_ptr = current_ptr;
         }
         else if ( first_ptr )
         {
            first_ptr->next_ptr = current_ptr;
            last_ptr = current_ptr;
         }
         else first_ptr = current_ptr;
         count = ++size;

         // Save the data into the new group.
         len = max_string - 1;
         if( name ) strncpy( current_ptr->entry->full_name, name, max_string );
         else len = 0;
         current_ptr->entry->full_name[ len ] = '\0';
         len = max_string - 1;
         if( nick ) strncpy( current_ptr->entry->nickname, nick, max_string );
         else len = 0;
         current_ptr->entry->nickname[ len ] = '\0';
         len = max_string - 1;
      }
      return count;
   };

   int add_nick( const char * nick, const char * name, const char * email )
   {
      int entry = 0;
      if( current_ptr ) entry = current_ptr->nick->add_entry( nick, name, email );
      return entry;
   };

   int get_size()
   {
      return size;
   }

   void first( void )
   {
      current_ptr = first_ptr;
   }

   void next( void )
   {
      if( current_ptr ) current_ptr = current_ptr->next_ptr;
   }

   int get_nick_size( void )
   {
      int count = 0;
      if( current_ptr ) count = current_ptr->nick->get_size();
      return count;
   }

   char * get_group_name( void )
   {
      char * name = NULL;
      if( current_ptr ) name = current_ptr->entry->full_name;
      return name;
   }

   char * get_group_nickname( void )
   {
      char * nick = NULL;
      if( current_ptr ) nick = current_ptr->entry->nickname;
      return nick;
   }

   void first_nick( void )
   {
      if( current_ptr ) current_ptr->nick->first();
   }

   void next_nick( void )
   {
      if( current_ptr ) current_ptr->nick->next();
   }

   char * get_nickname( void )
   {
      char * nick = NULL;
      if( current_ptr ) nick = current_ptr->nick->get_nickname();
      return nick;
   }

   char * get_name( void )
   {
      char * name = NULL;
      if( current_ptr ) name = current_ptr->nick->get_name();
      return name;
   }

   char * get_email( void )
   {
      char * name = NULL;
      if( current_ptr ) name = current_ptr->nick->get_email();
      return name;
   }

} group;

static long next_nickname;

static char decodeTable[ 256 ];

void initDecode()
{
   static char map[ 65 ] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
   for (int i = 0; i < 64; i++)
      decodeTable[ map[ i ] ] = i;
}

char * decode(const char * src)
{
   char * decoded = NULL;
   size_t size = strlen( src );
   if ((size % 4) == 0)
   {
      size_t t = 0;
      decoded = new char [ ( size * 3 ) / 4 ];
      for( size_t i = 0; i < size; i += 4 )
      {
      	int i1 = decodeTable[ src[ i ] ];
      	int i2 = decodeTable[ src[ i + 1 ] ];
      	int i3 = decodeTable[ src[ i + 2 ] ] & 0x3f;
      	int i4 = decodeTable[ src[ i + 3 ] ] & 0x3f;
      	decoded[ t++ ] = ( ( i1 << 2 ) + ( i2 >> 4 ) );
      	if( src[ i + 2 ] != '=' )
      	{
      	   decoded[ t++ ] = ( ( ( i2 & 0xf ) << 4 ) + ( i3 >> 2 ) );
      	}
      	if( src[ i + 3 ] != '=' )
      	{
      	   decoded[ t++ ] = ( ( ( i3 & 0x3 ) << 6 ) + i4 );
      	}
      }
      decoded[ t ] = '\0';
   }
   return decoded;
}

char * remove_field_name( char * buffer )
{
   char * ptr = strstr( buffer, NS_BASE64_FIELD_INDICATOR );
   if( ptr )
   {
      ptr += strlen( NS_BASE64_FIELD_INDICATOR );
      ptr = decode( ptr );
   }
   else
   {
      ptr = strstr( buffer, NS_FIELD_INDICATOR );
      if( ptr ) ptr += strlen( NS_FIELD_INDICATOR );
   }
   return ptr;
}

char * write_entry( FILE * out_file, char * buffer, char * default_value = NULL );
char * write_entry( FILE * out_file, char * buffer, char * default_value )
{
   char * ptr = remove_field_name( buffer );
   if( ptr == NULL ) ptr = default_value;
   if( ptr ) fputs( ptr, out_file );
   return ptr;
}

char * write_field( FILE * out_file, char * buffer, char * default_value = NULL );
char * write_field( FILE * out_file, char * buffer, char * default_value )
{
   char * ptr;

   fputs( PAB_FIELD_PREFIX, out_file );
   ptr = write_entry( out_file, buffer, default_value );
   fputs( PAB_FIELD_POSTFIX, out_file );

   return ptr;
}

char * write_nickname( FILE * out_file, char * buffer, char * default_1, char * default_2 )
{
   char * nickname_used, number[ 16 ];

   fputs( PAB_FIELD_PREFIX, out_file );
   fputs( HTML_BOLD_PREFIX, out_file );
   sprintf( number, "%li", next_nickname++ );
   if( *buffer ) nickname_used = write_entry( out_file, buffer );
   else if( *default_1 ) nickname_used = write_entry( out_file, default_1 );
   else nickname_used = write_entry( out_file, default_2, number );
   fputs( HTML_BOLD_POSTFIX, out_file );
   fputs( PAB_FIELD_POSTFIX, out_file );

   return nickname_used;
}

void write_postal_address( FILE * out_file, char * street_address, char * locality, char * state, char * postal_code, char * country_name )
{
   fputs( PAB_FIELD_PREFIX, out_file );
   write_entry( out_file, street_address );
   fputs( PAB_ADDRESS_LINE_SEPARATOR, out_file );
   write_entry( out_file, locality );
   fputs( PAB_ADDRESS_PART_SEPARATOR, out_file );
   write_entry( out_file, state );
   fputs( PAB_ADDRESS_PART_SEPARATOR, out_file );
   write_entry( out_file, postal_code );
   fputs( PAB_ADDRESS_LINE_SEPARATOR, out_file );
   write_entry( out_file, country_name );
   fputs( PAB_FIELD_POSTFIX, out_file );
}

int main( int argc, char * argv[] )
{
   int rc = 0;
   char path[ PATH_MAX ];
   FILE * in_file;
   FILE * out_file = NULL;

   fputs( "\nCopyright 2000 by David G. Holm, Berrien Springs, Michigan, USA.\n", stderr );

   if( argc != 3 ) rc = 1;
   else
   {
      // Convert the starting nickname value.
      next_nickname = strtol( argv[ 2 ], NULL, 10 );
      if( next_nickname < 1L ) rc = 6;
   }
   if( rc == 0 )
   {
      static char in_buf[ IN_BUF_MAX + 1 ];
      static char nickname[ IN_BUF_MAX + 1 ];
      static char email_address[ IN_BUF_MAX + 1 ];
      static char full_name[ IN_BUF_MAX + 1 ];
      static char notes[ ( IN_BUF_MAX * 4 ) + 1 ];
      static char title[ IN_BUF_MAX + 1 ];
      static char organization[ IN_BUF_MAX + 1 ];
      static char street_address[ IN_BUF_MAX + 1 ];
      static char locality[ IN_BUF_MAX + 1 ];
      static char state[ IN_BUF_MAX + 1 ];
      static char postal_code[ IN_BUF_MAX + 1 ];
      static char country_name[ IN_BUF_MAX + 1 ];
      static char phone_number[ IN_BUF_MAX + 1 ];
      static char fax_number[ IN_BUF_MAX + 1 ];
      char * last_entry = NULL;
      char * out_buf;
      size_t in_size;
      int group_member_count = 0;

      initDecode();

      // Open the Netscape Messenger exported address book file.
      in_file = fopen( argv[ 1 ], "r" );
      if( in_file == NULL ) rc = 2;
      // Check if the output file name buffer might overflow.
      else if( strlen( argv[ 1 ] ) > PATH_MAX - strlen( POLARBAR_ADDRESSBOOK_EXTENSION ) - 1 ) rc = 3;
      else
      {
         // Create the output file name from the input file name.
         strcpy( path, argv[ 1 ] );
         for( int i = strlen( path ); i && path[ i - 1 ] != '.'; i-- )
         {
            // Remove the current file name extension.
            path[ i - 1 ] = '\0';
         }
         // Append the Polarbar Mailer address book file name extension.
         strcat( path, POLARBAR_ADDRESSBOOK_EXTENSION );
         // If the file doesn't already exist, create it.
         if( access( path, 00 ) == 0 ) rc = 5;
         else
         {
            out_file = fopen( path, "w" );
            if( !out_file ) rc = 4;
            else fputs( POLARBAR_ADDRESSBOOK_PREFIX, out_file );
         }
      }
      while( !rc && !feof( in_file ) )
      {
         // Read a line from the Netscape Messenger file
         // (they all end with a single LF character).
         if( fgets( in_buf, IN_BUF_MAX, in_file ) )
            in_size = strlen( in_buf );
         else
            in_size = 0;
         if( in_size )
         {
            // Strip trailing end-of-line character, if any.
            if( in_buf[ in_size - 1 ] == '\n' )
            {
               in_size--;
               in_buf[ in_size ] = '\0';
            }
            // Accumulate select address book records for each entry.
            if( *in_buf == ' ' )
            {
               // This line actually goes with the previous entry.
               // Only save it if the previous entry was saved.
               if( last_entry ) strcat( last_entry, & in_buf[ 1 ] );
            }
            else if( strncmp( in_buf, "xmozillanickname:", 17 ) == 0 )
               last_entry = strcpy( nickname, in_buf );
            else if( strncmp( in_buf, "mail:", 5 ) == 0 )
               last_entry = strcpy( email_address, in_buf );
            else if( strncmp( in_buf, "cn:", 3 ) == 0 )
               last_entry = strcpy( full_name, in_buf );
            else if( strncmp( in_buf, "description:", 12 ) == 0 )
               last_entry = strcpy( notes, in_buf );
            else if( strncmp( in_buf, "title:", 6 ) == 0 )
               last_entry = strcpy( title, in_buf );
            else if( strncmp( in_buf, "o:", 2 ) == 0 )
               last_entry = strcpy( organization, in_buf );
            else if( strncmp( in_buf, "streetaddress:", 14 ) == 0 )
               last_entry = strcpy( street_address, in_buf );
            else if( strncmp( in_buf, "locality:", 9 ) == 0 )
               last_entry = strcpy( locality, in_buf );
            else if( strncmp( in_buf, "st:", 3 ) == 0 )
               last_entry = strcpy( state, in_buf );
            else if( strncmp( in_buf, "postalcode:", 11 ) == 0 )
               last_entry = strcpy( postal_code, in_buf );
            else if( strncmp( in_buf, "countryname:", 12 ) == 0 )
               last_entry = strcpy( country_name, in_buf );
            else if( strncmp( in_buf, "telephonenumber:", 16 ) == 0 )
               last_entry = strcpy( phone_number, in_buf );
            else if( strncmp( in_buf, "facsimiletelephonenumber:", 24 ) == 0 )
               last_entry = strcpy( fax_number, in_buf );
            else if( strncmp( in_buf, "member:", 7 ) == 0 )
            {
               if( group_member_count == 0 )
               {
                  group.add_group( remove_field_name( full_name ), remove_field_name( nickname ) );
               }
               char * cn = strstr( in_buf, "cn=" );
               if( cn ) cn += 3; // Set up the name string.
               char * mail = strstr( in_buf, ",mail=" );
               if( mail )
               {
                  // Null terminate the name string and setup the email string.
                  * mail = '\0';
                  mail += 6;
               }
               group.add_nick( NULL, cn, mail );
               group_member_count++;
               last_entry = NULL;
            }
            else last_entry = NULL;

            // Check for the end of an actual address book entry.
            if( strlen( in_buf ) == 0 )
            {
               // Is this an address book entry that is to be saved?
               if( ( * email_address || * full_name ) && group_member_count == 0 )
               {
                  // Write the address book entry.
                  fputs( PAB_ENTRY_PREFIX, out_file );
                  char * nick = write_nickname( out_file, nickname, full_name, organization );
                  char * email = write_field( out_file, email_address, "@" );
                  char * name = write_field( out_file, full_name );
                  write_field( out_file, "" );
                  write_field( out_file, "" );
                  write_field( out_file, "" );
                  write_field( out_file, notes );
                  write_field( out_file, title );
                  write_field( out_file, organization );
                  write_postal_address( out_file, street_address, locality, state, postal_code, country_name );
                  write_field( out_file, phone_number );
                  write_field( out_file, fax_number );
                  write_field( out_file, "false" );
                  write_field( out_file, "" );
                  fputs( PAB_ENTRY_POSTFIX, out_file );

                  // Save the current nickname, full name, and email values
                  // for later use when creating groups from lists.
                  nicknames.add_entry( nick, name, email );
               }

               // Reset the address book entry accumulators.
               group_member_count = 0;
               *nickname = '\0';
               *email_address = '\0';
               *full_name = '\0';
               *notes = '\0';
               *title = '\0';
               *organization = '\0';
               *street_address = '\0';
               *locality = '\0';
               *state = '\0';
               *postal_code = '\0';
               *country_name = '\0';
               *phone_number = '\0';
               *fax_number = '\0';
            }
         }
      }
      if( out_file )
      {
         // Indicate the end of the address part of the address book.
         fputs( POLARBAR_ADDRESSBOOK_END, out_file );
      }
      if( out_file && group.get_size() > 0 )
      {
         // Convert any Netscape lists to Polarbar groups.
         char * nickname, * name, * email, number[ 16 ];
         group.first();
         for( int i = 0; i < group.get_size(); i++ )
         {
            // Write the group prefix, name and nickname.
            name = group.get_group_name();
            nickname = group.get_group_nickname();
            sprintf( number, "%li", next_nickname++ );
            if( !nickname || !*nickname ) nickname = name;
            else nickname = number;
            fprintf( out_file, POLARBAR_GROUP_PREFIX, name, nickname );

            // Write all of the group member nicknames, starting with the first.
            group.first_nick();
            for( int j = 0; j < group.get_nick_size(); j++ )
            {
               // Get data values for current nickname entry for group.
               name = group.get_name();
               email = group.get_email();
               nickname = nicknames.find_entry( name, email );

               // Write a group entry, if the nickname was located.
               if( nickname ) fprintf( out_file, POLARBAR_GROUP_ENTRY, nickname );

               // Set up for next nickname entry for group.
               group.next_nick();
            }
            // Write the group postfix and setup for the next group.
            fputs( POLARBAR_GROUP_POSTFIX, out_file );
            group.next();
         }
      }
      if( out_file )
      {
         // Indicate the end of the address book file.
         fputs( POLARBAR_ADDRESSBOOK_POSTFIX, out_file );
         fclose( out_file );
         fprintf( stderr, "\nSuccess: The Netscape address book export file has been converted:\n    %s\n => %s\n", argv[ 1 ], path );
      }
      fclose( in_file );
   }
   if( rc == 6 )
   {
      fprintf( stderr, "\nError: Bad starting nickname value:\n    %s\n", argv[ 2 ] );
   }
   if( rc == 5 )
   {
      fprintf( stderr, "\nError: The destination file already exists:\n    %s\n", path );
   }
   if( rc == 4 )
   {
      fprintf( stderr, "\nError: The destination file could not be created:\n    %s\n", path );
   }
   if( rc == 3 )
   {
      fprintf( stderr, "\nError: The source path is too long to create the destination file\n    %s\n", argv[ 1 ] );
   }
   if( rc == 2 )
   {
      fprintf( stderr, "\nError: The source file could not be opened:\n    %s\n", argv[ 1 ] );
   }
   if( rc == 1 )
   {
      // Find the start of the actual EXE file name.
      size_t count, last = 0;

      for( count = 0; count < strlen( argv[ 0 ] ); count++ )
      {
         switch( argv[ 0 ][ count ] )
         {
            case ':':
            case '\\':
            case '/':
               last = count + 1;
         }
      }
      // Display the correct command line syntax.
      fprintf( stderr, "\nSyntax: %s exported_Netscape_address_book first_nickname\n\n"
                       "Version: " VERSION "\n\n"
                       "This C++ program converts an exported Netscape Messenger\n"
                       "address book LDIF file into a Polarbar Mailer HTML address\n"
                       "book file the first nickname must be a numeric value. It is\n"
                       "incremented for each Polarbar Mailer address book entry, but\n"
                       "is only used for those entries that do not have a nickname,\n"
                       "full name, or organization name in the Netscape address book.\n"
      , &argv[ 0 ][ last ] );
   }

   return rc;
}
