#!/usr/bin/perl -w

########################################################################################################################
#
# GetLive - perl script to get mail from hotmail (live) mailboxes.
#
# $Id: GetLive.pl,v 2.16 2010/11/09 19:21:33 jdla Exp $
# $Name: Release_2_3 $
#
# Copyright (C) 2007-2010 Jos De Laender <jos@de-laender.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
########################################################################################################################

use strict;
use File::Spec;
use URI::Escape;

########################################################################################################################
#
# XXX
# XXX This is inserted to cope with French characters in the folder names.
# XXX Not too sure about. It works on my LANG=nl_BE.UTF-8 box, but I'm afraid it may screw up other boxes ...
# XXX In my case also use encoding("UTF-8") worked (it's my locale after all). 
# XXX
#
########################################################################################################################

eval "local $^W = 0; use encoding(\":locale\");";

########################################################################################################################
#
# Global constants and variables.
#
########################################################################################################################

my $ProgramName = "GetLive";
my $Revision    = '$Revision: 2.16 $';                # Meant for RCS.

# Constants of configuration.
my $Proxy                   = "";
my $ProxyAuth               = "";
my $CurlCommand             = 'curl -k';
my $Verbosity               = 1;                    # 0:Silent; 1:Normal; 2:Verbose; 10:debug; 100:heavy debug 
my $RetryLimit              = 2;
my $ConfigFile              = "";
my $ServerMode              = 0;
my $TrashFolderId           = "00000000-0000-0000-0000-000000000002"; 
# This is not used in server mode. Only in classic mode :
my %FoldersToProcess = ();  # The folders to process (empty will be considered as all). Otherwise FolderName=>1 assoc.


########################################################################################################################
#
# GetLive package
# This is a kind of 'objectization' of the original GetLive.
# It should be useable in a 'classic' GetLive scenario as well as in a POP3 server scenario.
#
########################################################################################################################

package GetLive;
 
use strict;
use File::Spec;
use URI::Escape;

########################################################################################################################
#
# GetLive object creation.
#
########################################################################################################################

sub new {
  my $Class = shift;

  my $Self = {};

  $Self->{'Login'}             = "";
  $Self->{'Domain'}            = "hotmail.com";
  $Self->{'Password'}          = "";
  $Self->{'MailProcessor'}     = '/usr/local/bin/procmail'; # Any program taking mbox formatted at stdin will do.
  $Self->{'DownloadedIdsFile'} = "";                  # Local file with Ids of already downloaded messages.
  $Self->{'MarkRead'}          = "No";                # No,Yes : But never when downloaded before !
  $Self->{'Delete'}            = "No";                # No,Yes : But never when downloaded before !
  $Self->{'SkipTrash'}         = "No";                # No,Yes : Do not handle the Trash folder
  $Self->{'Strategy'}          = "All";               # "Unread" or "All" or "NotDownloaded"
                                                      # Determines which messages will be handled.
  $Self->{'MoveToFolder'}      = "";                  # The name of the folder to move to after the download. "" is not.
                                                      # If it begins with @ it is reference to a filename that
                                                      # contains the folder to move to. This is a hook for 
                                                      # autoclassifying the mail on the server,including spam filtering.
  $Self->{'BreakOnAlreadyDownloaded'} = 0;            # When a sequence found of this number already downloaded, stop

  # Files in a temporary directory. Must be per object.
  my $TmpDir = File::Spec->tmpdir() . "/$ProgramName.$$.$^T";
  # Don't allow others to read our temp files
  umask(077);
  # The temporary directory creation.
  mkdir($TmpDir) || die "Could not create $TmpDir : $!.";

  $Self->{'TmpDir'}             = $TmpDir;
  $Self->{'TmpCurlHeadersFile'} = "$TmpDir/Headers";
  $Self->{'TmpCookiesFile'}     = "$TmpDir/Cookies";
  $Self->{'TmpFormDataFile'}    = "$TmpDir/Form";
  $Self->{'TmpCurlStderrFile'}  = "$TmpDir/CurlStderr";
  $Self->{'TmpCurlStdoutFile'}  = "$TmpDir/CurlStdout";
  $Self->{'TmpCurlTraceFile'}   = "$TmpDir/CurlTrace";
  $Self->{'TmpLogFile'}         = "$TmpDir/Log";

  # Messages retrieved from a folder.
  $Self->{'NrMessages'}         = 0;

  # Various variables.
  $Self->{'BaseUrl'}    = "";        # The one in the logged in screen used for fetching folders.
  $Self->{'NParameter'} = "";

  $Self->{'FolderHrefs'}      = ();  # The Hrefs found for the different folders.
  $Self->{'FolderIds'}        = ();  # The Ids found for the different folders.
  $Self->{'FolderNames'}      = ();  # The names found for the different folders.
  $Self->{'FolderNrMessages'} = ();  # The number of messages found for the different folders.
  $Self->{'NrFolders'}        = 0;   # The number of folders found.

  $Self->{'CurlRun'}    = 0;         # Increased with each Curl run. Basically for debug reasons.

  $Self->{'DieOnError'}  = 1;        # Might be put on 0 for server application.
  $Self->{'LogToStdout'} = 1;        # Might be put on 0 for server application.

  my $LogFileHandle;
  my $LogFileName = $Self->{'TmpLogFile'};
  if ($Verbosity) {
    open ($LogFileHandle,">$LogFileName") || die "Could not open $LogFileName : $!";
    $Self->{'LogFileHandle'} = $LogFileHandle;
  }

  bless $Self,$Class;
  return $Self;
}

########################################################################################################################
# 
# Class method
# Parse the Configuration File
#
########################################################################################################################

sub ParseConfig {
  my $Self = shift;

  open (CONFIG,$ConfigFile) || die "Configuration file '$ConfigFile' could not be opened : $!.";

  # Parse the file
  while (<CONFIG>) {
    my $Line = $_;
    next if ($Line =~ /^#/); # Comment.
    next if ($Line =~ /^\s*$/); # Empty line.
    if (not $Line =~ m/^([a-zA-Z0-9-_]+)/) {
      $Self->Log("Wrong configuration line : '$_'.\n",stderr=>1);
    }
    my $Option      = $1;
    my $OptionValue = "";
    $Line           = $'; # The remaining of the line.
    if (not $Line =~ m/\s*=\s*\S+/) {
      $Self->Log("Wrong configuration line : '$_' (no value).\n",stderr => 1);
    }
    # Remove equals sign and leading, trailing whitespace.
    $Line =~ s/=//;
    $Line =~ s/^\s+|\s+$//g;
    $OptionValue = $Line;

    if ($Option =~ m/^UserName$/i) {
      $Self->{'Login'} = $OptionValue;
    } elsif ($Option =~ m/^Password$/i) {
      $Self->{'Password'} = $OptionValue;
    } elsif ($Option =~ m/^Mode$/i) {
      warn "Option Mode is not available anymore";
    } elsif ($Option =~ m/^Domain$/i) {
      $Self->{'Domain'} = $OptionValue;
    } elsif ($Option =~ m/^Proxy$/i) {
      $Proxy = $OptionValue;
    } elsif ($Option =~ m/^ProxyAuth$/i) {
      $ProxyAuth = $OptionValue;
    } elsif ($Option =~ m/^Downloaded$/i) {
      $Self->{'DownloadedIdsFile'} = $OptionValue;
      if ($OptionValue) {
        $Self->{'Strategy'} = "NotDownloaded";
      }
    } elsif ($Option =~ m/^RetryLimit$/i) {
      $RetryLimit = $OptionValue;
    } elsif ($Option =~ m/^Processor$/i) {
      $Self->{'MailProcessor'} = $OptionValue;
    } elsif ($Option =~ m/^CurlBin$/i) {
      $CurlCommand = $OptionValue;
    } elsif ($Option =~ m/^Folder$/i) {
      $FoldersToProcess{lc $OptionValue} = 1;
    } elsif ($Option =~ m/^FetchOnlyUnread$/i) {
      my $FetchOnlyUnread = $OptionValue;
      if ($FetchOnlyUnread =~ m/Yes/i) {
        $Self->{'Strategy'} = "Unread";
      }
    } elsif ($Option =~ m/^MarkRead$/i) {
      $Self->{'MarkRead'} = $OptionValue;
    } elsif ($Option =~ m/^Delete$/i) {
      $Self->{'Delete'} = $OptionValue;
    } elsif ($Option =~ m/^SkipTrash$/i) {
      $Self->{'SkipTrash'} = $OptionValue;
    } elsif ($Option =~ m/^MoveToFolder$/i) {
      $Self->{'MoveToFolder'} = $OptionValue;
    } elsif ($Option =~ m/^BreakOnAlreadyDownloaded$/i) {
      warn "Option BreakOnAlreadyDownloaded is not available anymore";
    } else {
      $Self->Log("Wrong configuration line : '$_' (unknown option).\n",stderr=>1);
    }
  }
  close(CONFIG);
}

########################################################################################################################
# 
# Class method : Destructor.
# Needed for cleanup of tmp files.
#
########################################################################################################################

sub DESTROY {
  my $Self = shift;
  close $Self->{'LogFileHandle'} if $Verbosity;
  return if ($Verbosity >9);    # Considered debug mode and thus keep the files !
  $Self->CleanTempFiles();
}


########################################################################################################################
# 
# Class method.
# Clean up any temporary files which are collected in a temporary directory.
#
########################################################################################################################

sub CleanTempFiles {
  my $Self = shift;

  my $TmpDir = $Self->{'TmpDir'};
  return if (! -e $TmpDir);     # We're even not at the point that the tmpdir exists ...
  # We are very forgiving on errors in removal. It's not the end of the world in the first place.
  # Besides our logging would be maybe in this same dir as well ...
  opendir (TMPDIR,$TmpDir) || return;
  while (my $FileName = readdir(TMPDIR)) {
    next if $FileName =~ m/^\.$/;    # Not the .
    next if $FileName =~ m/^\.\.$/;  # Nor .. directory
    $FileName =~ m/(.*)/;
    $FileName = $1;
    unlink("$TmpDir/$FileName");
  }
  closedir (TMPDIR);
  # Finally get rid of the temporary directory itself.
  rmdir($TmpDir);
}
 
########################################################################################################################
# 
# Class method.
# Log some text.
# First parameter : text to be displayed.
# Then a number of named parameters that are optional. 
# See %args.
#
########################################################################################################################

sub Log {
  my $Self = shift;
  my $Text = shift;
  my %Args = (MinVerbosity => 0,
              stderr       => 0,
              @_);

  my $LogFileHandle = $Self->{'LogFileHandle'};
  my $DieOnError    = $Self->{'DieOnError'};
  my $LogToStdout   = $Self->{'LogToStdout'};

  # stderr messages are under no circumstances suppressed.
  if ($Args{'stderr'}) {
    if ($Verbosity) {
      print $LogFileHandle $Text;
    }
    print $Text if $LogToStdout;
    die $Text if $DieOnError;
    return;
  }

  # Filter out the ones for which the verbosity is too high.
  return if ($Args{'MinVerbosity'} > $Verbosity);

  # And finally print ;-)
  # Stdout is flushed immediate , not to miss error messages.
  if ($Verbosity) {
    my $WasSelected = select($LogFileHandle);
    $|=1;
    select($WasSelected);
    print $LogFileHandle $Text;
  }
  print $Text if $LogToStdout;

  return;
}

########################################################################################################################
# 
# Unescape html characters, widechars become blank along the conversion.
#
# Based on a function with copyright: Bryant H. McGill - 11c Lower Dorset Street, Dublin 1, Ireland
# Use Terms: Free for non-commercial use, commercial use with notification.
#
########################################################################################################################

sub HtmlUnescape {
  my $String = shift;

  $String =~ s[&(.*?);]{
    local $_ = $1;
    /^amp$/i ? "&" :
    /^quot$/i ? '"' :
    /^gt$/i ? ">" :
    /^lt$/i ? "<" :
    /^nbsp$/i ? " " :
    /^#(\d+)$/ ? ($1>255 ? "":chr($1)) :
    /^#x([0-9a-f]+)$/i ? (hex($1)>255 ? "": chr(hex($1))) :
    $_
    }gex;
  return $String;
}

########################################################################################################################
# 
# Class method.
# Get a html page, basically via curl. 
# Returns the page as one big string.
# Returns a second string with the latest url.
# The parameters should be reasonably clear. FollowForward will follow a redirection.
#
########################################################################################################################

sub GetPage {
  my $Self = shift;
  my %Args = (Url           => "",
              CurlDataArg   => "",
              FollowForward => 0,
              @_);

  my $Url           = $Args{'Url'};
  my $CurlDataArg   = $Args{'CurlDataArg'};
  my $FollowForward = $Args{'FollowForward'};

  my $TmpCurlHeadersFile = $Self->{'TmpCurlHeadersFile'};
  my $TmpCookiesFile     = $Self->{'TmpCookiesFile'};
  my $TmpFormDataFile    = $Self->{'TmpFormDataFile'};
  my $TmpCurlStderrFile  = $Self->{'TmpCurlStderrFile'};
  my $TmpCurlStdoutFile  = $Self->{'TmpCurlStdoutFile'};
  my $TmpCurlTraceFile   = $Self->{'TmpCurlTraceFile'};
  my $CurlRun            = $Self->{'CurlRun'};

  $CurlRun++;
  $Self->{'CurlRun'} = $CurlRun;

  my $OptionsToCurl = "";

  if ($Proxy) {
    $OptionsToCurl .= "--proxy $Proxy "; 
  }
  if ($ProxyAuth) { 
    $OptionsToCurl .= "--proxy-user $ProxyAuth "; 
  }

  # The files with the Cookies.
  $OptionsToCurl .= "-b $TmpCookiesFile -c $TmpCookiesFile ";

  if ($CurlDataArg ne "") { 
    $OptionsToCurl .= "--data \"$CurlDataArg\" ";
  }

  # Curl is put silent (but with error output) 
  # when not interactive or low verbosity.
  if ( (not -t STDOUT) || ($Verbosity <= 1) ) { 
    $OptionsToCurl .= "-s -S " 
  }

  if ($Verbosity > 9) { 
    $OptionsToCurl .= "-v --trace $TmpCurlTraceFile.$CurlRun" 
  }

  # JDLA curl outputs info via stderr. Catched in file and appended
  # to stdout output in debug mode.
  my $CommandLine = 
    "$CurlCommand --stderr $TmpCurlStderrFile.$CurlRun \"$Url\" " .
    "$OptionsToCurl -i -m 600 -D $TmpCurlHeadersFile.$CurlRun " .
    "-A \"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.5) Gecko/20061201 Firefox/2.0.0.5 (Ubuntu-feisty)\"";
  $Self->Log("Curl run $CurlRun.\nCommandLine : '$CommandLine'.\n", MinVerbosity => 10);
  my $NrTries = 0;
  my @CurlOutput = ();
  while (!@CurlOutput && $NrTries++ < $RetryLimit) {
    $Self->Log("Trying [$NrTries/$RetryLimit].\n",MinVerbosity => 2);
    $CommandLine =~ m/(.*)/;
    $CommandLine = $1;
    @CurlOutput = `$CommandLine`;
    # Copy output. Only in very high debug levels.  # We have it in file anyway.
    if ($Verbosity > 99) { # The if around makes it a bit more efficient over the loop.
      foreach my $Line (@CurlOutput) { 
        $Self->Log($Line,MinVerbosity => 100); 
      }
    }
    my $Success = open (CURL_STDERR,"$TmpCurlStderrFile.$CurlRun");
    if (!$Success) {
      $Self->Log(__LINE__ . ": Could not open $TmpCurlStderrFile.$CurlRun : $!.\n",stderr => 1);
      return("","");
    }
    # Copy curl stderr.
    $Self->Log("\nstderr of curl :\n",MinVerbosity => 10);
    while(<CURL_STDERR>) {
      my $Line = $_;
      my $PasswordToBlank = uri_escape($Self->{'Password'},"^A-Za-z");
      $Line =~ s/$PasswordToBlank/YouThinkThisIsThePassword/g;
      $Self->Log("$Line",MinVerbosity => 10);
    }
    close(CURL_STDERR);
    $Self->Log("\nEnd of stderr of curl.\n",MinVerbosity => 10);

    # Some checking on the HTTP response to see if there's no 5** Server errror or 4** Client error.
    # In general : 2** is Success, 3** is Redirection, 4** is Client Error and 5** is Server Error.
    
    if ($CurlOutput[0] !~ m/HTTP[^ ]+ (\d{3})/) {
      $Self->Log(__LINE__ . ": Irregular HTTP header '$CurlOutput[0]' received.\n",stderr => 1);
      return ("","");
    }
    my $HttpCode = $1;
    if ($HttpCode =~ m/(1|2|3)\d{2}/) {
      $Self->Log("Http Status OK : $HttpCode.\n",MinVerbosity=>2);
    } elsif ($HttpCode =~m/4\d{2}/) {
      $Self->Log("Http Client Error : $HttpCode.\n",MinVerbosity=>2);
      @CurlOutput = (); # Force retry.
    } elsif ($HttpCode =~m/5\d{2}/) {
      $Self->Log("Http Server Error : $HttpCode.\n",MinVerbosity=>2);
      @CurlOutput = (); # Force retry.
    } else {
      $Self->Log(__LINE__ . ": Unexpected HTTP status : '$HttpCode'.\n",stderr => 1);
      return ("","");
    }
  }

  # In debug mode (Verbosity>9) we copy the output to a file.
  if ($Verbosity > 9) {
    my $Success = open (CURL_STDOUT,">$TmpCurlStdoutFile.$CurlRun");
    if (!$Success) {
      $Self->Log(__LINE__ . ": Could not open $TmpCurlStdoutFile.$CurlRun : $!.\n",stderr => 1);
      return ("","");
    }
    print CURL_STDOUT @CurlOutput;
    close(CURL_STDOUT);
  }

  if (!@CurlOutput && $NrTries > $RetryLimit) {
    $Self->Log(__LINE__ . ": Curl run $CurlRun.\nCommandLine : '$CommandLine'.\n",stderr => 1);
    $Self->Log(__LINE__ . ": An error was encountered getting the page.\n",stderr => 1);
    return ("","");
  }

  # Redirect search in headers.
  my $Redirection = "";
  my $Success = open (CURL_HEADERS,"$TmpCurlHeadersFile.$CurlRun");
  if (!$Success) {
    $Self->Log(__LINE__ . ": Could not open $TmpCurlHeadersFile.$CurlRun : $!.\n",stderr => 1);
    return("","");
  }
  while (<CURL_HEADERS>) {
    if (m/^Location: (\S+)\s/) {
      $Redirection = $1;
      if ($Redirection =~ m/BrowserSupport/i){
          $Redirection = "";
      }
      $Self->Log("Following  $Redirection.\n",MinVerbosity => 2);
      last;
    }
  }
  close(CURL_HEADERS);

  # If we have been asked to follow Location: headers
  if ($FollowForward) {
    if ($Redirection ne "") {
      if ($Redirection !~ m/^http.*/i){
          if ($Url =~ m/(http?:\/\/[^\/]+)\//i) {
            $Redirection = $1 . $Redirection;
          }
        }
      $Self->Log("Following redirect to $Redirection.\n",MinVerbosity => 2);
      return $Self->GetPage(Url => $Redirection,FollowForward => $FollowForward);
    }
  }

  return (join("",@CurlOutput),$Url);
}

########################################################################################################################
# 
# Class method.
# Do the HotMail login process - log in until we have the URL of the inbox.
# Return 1 on success, 0 on failure.
#
########################################################################################################################

sub Login {
  my $Self = shift;

  my $BaseUrl = $Self->{'BaseUrl'};

  $Self->Log("Getting hotmail index loginpage.\n", MinVerbosity =>2);

  my ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => "http://mail.live.com/",FollowForward => 1);

  # We expect here a number of functions now (aug 2007) to be hidden in a javascript
  # that is loaded separately. Let's load and append.
  # XXX JDLA It can turnout that after all we don't use anything of it, but reconstruct.
  # Then one can speed up by leaving this JSPageAsString out.
  
  my $BaseHref = "";
  if ($LoginPageAsString =~ m/<base\s+href=\"([^\"]+)\"/) {
    $BaseHref = $1;
    $Self->Log("Found base href to be '$BaseHref'.\n",MinVerbosity => 10);
  }

  my @JavaScriptHrefs;
  my $JavaScriptHref = "";
  while ($LoginPageAsString =~ m/<script\s+type=\"text\/javascript\"\s+src=\"([^\"]+)\"/g ) {
    $JavaScriptHref = $1;
    push @JavaScriptHrefs,$JavaScriptHref;
    $Self->Log("Found javascript href to be '$JavaScriptHref'.\n",MinVerbosity => 10);
  }
  
  if (!$JavaScriptHref) {
    $Self->Log(__LINE__ . ": Expected javascript href at this stage.\n",stderr => 1);
    return 0;
  }
 
  foreach my $Ref (@JavaScriptHrefs) {
    $Self->Log("Fetching the JS href.\n",MinVerbosity => 10);
    if ($Ref !~ m/^http[s]?:\/\//i) {
      $Ref = $BaseHref . $Ref;
    }
    my ($JSPageAsString,$JSGetPageUrl) = $Self->GetPage(Url => "$Ref",FollowForward => 1);

    # Append the JS stuff into our page.
    $LoginPageAsString .= $JSPageAsString;
  }

  # We would look to :
  #
  # function FormStart(){var s="
  # <form name=\"f1\" method=\"POST\" target=\"_top\" action=\""+g_urlPost+"\" 
  # onsubmit=\"return WLSubmit(this)\">";
  # s+=WL_HiddenField("idsbho","IDSBHO","1");
  # s+=WL_HiddenField("PwdPad","i0340",null);
  # s+=WL_HiddenField("LoginOptions","LoginOptions","3");
  # s+=WL_HiddenField("CS","CS",null);
  # s+=WL_HiddenField("FedState","FedState",null);
  # s+=WL_HiddenField("PPSX","i0326",g_sRBlob);
  # s+=WL_HiddenField("type","type",null);return s;}
  # 
  # The WL_HiddenField = 'name','identifier','value'. Identifier unimportant.
  # But assume g_urlPost is always there. So action is g_urlPost --> srf_uPost
  # Assume also g_sRBlob -->srf_sRBlob


  my $FormStart;
  if ($LoginPageAsString !~ m/function\s+_d5\(a\)\s*\{([^\}]+)\}/i) {
    $Self->Log(__LINE__ . ": Page doesn't contain _d5 as expected.\n",stderr => 1);
    return 0;
  }
  $FormStart = $1;
  $Self->Log("FormStart detected as '$FormStart'.\n", MinVerbosity => 10 );

  # Hidden fields as described above.
  my %Fields = ();
  while ($FormStart =~ m/a\.setPostParam\(\"([^\"]+)\",([^\)]+)\)/gc) {
    my $Key = $1;
    my $Val = $2;
    if ($Val =~ m/\"([^\"]+)\"/) {
      $Val = $1;
    }
    if ($Val eq "null") {
      $Val = "";
    }
    $Fields{$Key} = $Val;
    $Self->Log("Detected setPostParam : $Key->$Val\n", MinVerbosity => 10);
  }

  # Assumed g_urlPost
  if ($LoginPageAsString !~ m/var srf_uPost='(\S+)'/i) {
    $Self->Log(__LINE__ . ": Page doesn't contain var srf_uPost in the expected place.\n",stderr => 1);
    return 0;
  }
  my $LoginUrl = $1;
  $Self->Log("LoginUrl detected as '$LoginUrl'.\n", MinVerbosity => 10 );

  # Transformation of LoginUrl (mimick part of the g_DO in javascript ..).
  if ($LoginUrl !~ m/(http[s]?):\/\/([^\/]+)\/(.*)/ ) {
    $Self->Log(__LINE__ . ": Malformed LoginUrl : '$LoginUrl'.\n",stderr => 1);
    return 0;
  }
  my $ProtocolLoginUrl   = $1;
  my $FirstPartLoginUrl  = $2;
  my $SecondPartLoginUrl = $3;
  my $Domain = $Self->{'Domain'};
  if ($Domain eq "msn.com") {
    $FirstPartLoginUrl = "msnia.login.live.com";
  }
  $LoginUrl = "$ProtocolLoginUrl://$FirstPartLoginUrl/$SecondPartLoginUrl";
  $Self->Log("LoginUrl transformed as '$LoginUrl'.\n", MinVerbosity => 10 );
  
  # Assumed g_sRBlob (and hence PPSX)
  if ($LoginPageAsString !~ m/var srf_sRBlob='(\S+)'/i) {
    $Self->Log(__LINE__ . ": Page doesn't contain var srf_sRBlob in the expected place.\n",stderr => 1);
    return 0;
  }
  $Self->Log("PPSX detected as '$1'.\n", MinVerbosity => 10 );
  $Fields{"PPSX"} = $1;
  
  # PPFT is a normal (ie non JS) hidden input type.
  if( $LoginPageAsString !~ m/<\s*input\s+.*name=\"PPFT\"(\s+id="\S+")?\s+value=\"(\S*)\"/ ) {
    Self->Log(__LINE__ . "Page doesn't contain input field PPFT as expected.\n",stderr => 1);
    return 0;
  }
  $Self->Log("PPFT detected : '$2'.\n",MinVerbosity => 10 );
  $Fields{"PPFT"} = $2;

  # A number of other assumption that are peeled deep out of JS.
  # I'm afraid that the need for an embedded JS interpreter is coming closer ...
  $Fields{"type"} = "11";
  $Fields{"NewUser"} = "1";
  $Fields{"i1"} = "0";
  $Fields{"i2"} = "0";
  
  # Omar Ramadan Bug-fix for passwords containing '='
  my $Password = $Self->{'Password'};
  my @PassExplode = split("=", $Password);
  $Password = $PassExplode[0];
  # Hope the password padding still works ...
  my $Padding = "BovenGentRijstEenzaamEnGrijsHetOudBelfort";
  my $PwdPad = substr( $Padding, 0, length($Padding)-length($Password) ); 
  $Self->Log("PwdPad constructed : '$PwdPad'.\n",MinVerbosity => 10 );
  $Fields{"PwdPad"} = $PwdPad;

  #login and password.
  my $Login = $Self->{'Login'};
  $Fields{"login"} =  uri_escape($Login . '@' . $Domain, "^A-Za-z");
  $Fields{"passwd"} = uri_escape($Password, "^A-Za-z");

  # Construct the form with above in a temporary file.
  my $TmpFormDataFile    = $Self->{'TmpFormDataFile'};
  my $Success = open (FORMFILE,">$TmpFormDataFile");
  if (!$Success) {
    $Self->Log(__LINE__ . ": Could not open $TmpFormDataFile : $!.\n",stderr => 1);
    return 0;
  }
  my $HaveAlreadyArgument = 0;
  foreach my $Key (keys %Fields) {
    if ($HaveAlreadyArgument) { print FORMFILE "\&"; }
    print FORMFILE "$Key=$Fields{$Key}";
    $HaveAlreadyArgument = 1;
  }
  close FORMFILE;
  
  # Second step of login. The form is provided as a curl --data argumetn.
  $Self->Log("Logging in.\n",MinVerbosity => 1);

  ($LoginPageAsString,$GetPageUrl) = 
    $Self->GetPage(Url => $LoginUrl,CurlDataArg => "\@$TmpFormDataFile",FollowForward => 1);

  if ($LoginPageAsString !~ m/window\.location\.replace\(\"(.*)\"\);/i && 
      $LoginPageAsString !~ m/<meta http-equiv=\"REFRESH\" content=\"0;\sURL=([^\"]*)\"/i) { 
    $Self->Log(__LINE__ . ": Hotmail's login structure has changed! (redirloc).\n",stderr => 1);
    return 0;
  }
  $LoginUrl = $1; 

  $Self->Log("LoginUrl 2 : '$LoginUrl'.\n",MinVerbosity => 10);
  # Following the redirect : Third step of login.
  $Self->Log("Following redirect.\n",MinVerbosity => 2);

  if( $LoginUrl =~ m/gfx2.hotmail.com\/mail\/uxp\/w4\/m3\/pr16\/h\/s4.png/ ) {
    $LoginUrl=$BaseUrl."?rru=inbox" ;
  }
  ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $LoginUrl,FollowForward => 1);
  $LoginUrl = $GetPageUrl;

  if ($LoginPageAsString =~ m/<form\s*name=\"MessageAtLoginForm\"\s*method="post"\s*action=\"([^\"]*)\"/i) { 
    $Self->Log(__LINE __ . ": I suspect there to be a NAG screen. Please switch it off.\n",stderr => 1);
    return 0;
  };

  if ($LoginUrl !~ m/(http[s]?:\/\/([^\/]+\/)+)/) {
    $Self->Log(__LINE__ . ": Could not detect BaseUrl.\n",stderr => 1);
    return 0;
  } 

  $BaseUrl = $1;
  $Self->{'BaseUrl'} = $BaseUrl;
  my $NParameter = "";
  if ($LoginUrl =~ m/(n=\d+)/) {
    $NParameter = $1;
    $Self->{'NParameter'} = $NParameter;
  }

  $LoginUrl = $BaseUrl."InboxLight.aspx?".$NParameter;
  ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $LoginUrl,FollowForward => 1);

  $Self->Log("LoginUrl    : $LoginUrl.\n",MinVerbosity => 10);
  $Self->Log("BaseUrl     : $BaseUrl.\n",MinVerbosity => 10);
  $Self->Log("NParameter  : $NParameter.\n",MinVerbosity => 10);

  # At this moment we assume we are logged in, but there should be some 'markers' to  
  # check this reasonably.

  my $LoggedIn = 0;
  if ($LoginPageAsString =~ m/href=\"ManageFoldersLight.aspx/) {
    $LoggedIn = 1;
  } elsif ($LoginPageAsString =~ m/MSNPlatform\/browsercompat.js/) {
    $LoggedIn = 1;
  }

  if (!$LoggedIn) {
    $Self->Log(__LINE__ . ": Could not log in. Maybe structure has changes or was not foreseen.\n",stderr => 1);
    return 0;
  }

  $BaseUrl .= "mail/" if ($BaseUrl !~ m/mail\/$/);
  $Self->{'BaseUrl'} = $BaseUrl;
  if ($LoginPageAsString =~ m/ManageFoldersLight\.aspx\?(n=\d+)/) {
    $NParameter = $1;
    $Self->{'NParameter'} = $NParameter;
  }
  if (!$NParameter) {
    $Self->Log(__LINE__ . ": Could not retrieve 'NParameter'.\n",stderr => 1);
    return 0;
  }

  $Self->Log("Got MainPage.\n",MinVerbosity => 1);

  return 1;
}

########################################################################################################################
# 
# Class method.
# Get a list of the folders we have to deal with and parse them one by one.
# Return 1 on success, 0 on failure.
# 
########################################################################################################################

sub GetFolders {
  my $Self = shift;

  # We go via the mobile part of the site for simplicity.
  my $MobBaseUrl = $Self->{'BaseUrl'};
  if ($MobBaseUrl !~ s/\/mail\//\/md\//) {
    $Self->Log(__LINE__ . ": Unexpected format in $MobBaseUrl.\n",stderr => 1);
    return 0;
  }
  my ($FolderPageAsString,$GetPageUrl) = $Self->GetPage(Url => "${MobBaseUrl}/folders.aspx",
                                                        FollowForward => 1);
  # Scan the line for all folders, their href and title.
  # NrFolders on the fly;
  my @FolderHrefs = ();
  my @FolderNames = ();
  my @FolderIds   = ();
  my $NrFolders   = 0;

  while ($FolderPageAsString =~ 
         m/<a\s*id="FolderList_ItemLink"\s*href=\"([^\"]*)\"[^>]*>(.*?)<\/a>/gc) { 
    $FolderHrefs[$NrFolders]      = $1;
    my $Name = HtmlUnescape($2);
    my $Nr   = 0;
    if ($Name =~ m/([^\(]+)\(\s*(\d+)\s*\)/) {
      $Name = $1;
      $Nr   = $2;
    }
    $Name =~ s/\s+$//;
    $Name =~ s/^\s+//;
    $FolderNames[$NrFolders]      = $Name;
    if ( $FolderHrefs[$NrFolders] !~ m/fid=([^&]*)/ ) {
       die "Could not detect FolderId.";
    }

    $FolderIds[$NrFolders] = $1;

    $Self->Log(
     "Folder $NrFolders - $FolderIds[$NrFolders] - $FolderNames[$NrFolders].\n", 
      MinVerbosity => 10);
    $NrFolders++;
  }
  if (!$NrFolders) {
    $Self->Log(__LINE__ . ": No folders detected. Likely the page structure has changed.\n",stderr => 1);
    return 0;
  }

  $Self->{'FolderHrefs'} = \@FolderHrefs;
  $Self->{'FolderNames'} = \@FolderNames;
  $Self->{'FolderIds'}   = \@FolderIds;
  $Self->{'NrFolders'}   = $NrFolders;

  return 1;
}

########################################################################################################################
# 
# Class method.
# Get the messages from the folder with Idx as argument.
# 
########################################################################################################################

sub GetMessagesFromFolder {
  my $Self            = shift;
  my $FolderIdx       = shift;

  my $FolderNames     = $Self->{'FolderNames'};
  my $FolderIds       = $Self->{'FolderIds'};
  my $FolderHrefs     = $Self->{'FolderHrefs'};

  my $FolderName      = $FolderNames->[$FolderIdx];
  my $FolderId        = $FolderIds->[$FolderIdx];

  $Self->Log("Loading folder '$FolderName'.\n",MinVerbosity => 1);
 
  # We go via the mobile part of the site for simplicity.
  my $MobBaseUrl = $Self->{'BaseUrl'};
  if ($MobBaseUrl !~ s/\/mail\//\/md\//) {
    $Self->Log("Unexpected format in $MobBaseUrl.\n",stderr => 1);
    return
  }

  my $Page          = 0;
  my $StillPageToGo = 1;

  my $PageAsString;
  my $GetPageUrl;
  
  my $PageUrl = $MobBaseUrl.$FolderHrefs->[$FolderIdx];

  # Reinitialize the global variable back to 0.
  my $NrMessages      = 0;
  my @MessagesFrom    = ();
  my @MessagesSubject = ();
  my @MessagesId      = ();
  my @MessagesDeleted = (); # In support of pop server. Marking deleted.
  my @MessagesPopped  = (); # In support of pop server. Marking popped.

  while ($StillPageToGo) {

    $Page++;
    $StillPageToGo = 0;

    ($PageAsString,$GetPageUrl) = $Self->GetPage(Url => $PageUrl,FollowForward => 1);

    $Self->Log("Handling page $Page.\n",MinVerbosity => 2);

    while ($PageAsString =~ m/(<table id="MessageList_MessageHeaderTable".*?<\/table>)/sig) {
      my $OneMessageTable = $1;
      if ($OneMessageTable !~ m/<span id="MessageList_SenderLabel">(.*?)<\/span>/ ) {
        $Self->Log(__LINE__ . ": Did not find 'MessageList_SenderLabel'\n",stderr => 1);
        return
      }
      my $From = HtmlUnescape($1);
      $MessagesFrom[$NrMessages] = $From;
      if ($OneMessageTable !~ m/<a id="MessageList_SubjectLink".+?href="(.*?)".+?>(.*?)<\/a>/ ) {
        $Self->Log(__LINE__ . ": Did not find correct 'MessageList_SubjectLink'\n",stderr => 1);
      }
      my $Subject = HtmlUnescape($2);
      my $Href    = $1;
      $MessagesSubject[$NrMessages] = $Subject;
      if ($Href !~ /mid=(.*?)&/) {
        $Self->Log(__LINE__ . ": Href does not contain mid\n",stderr => 1);
      }
      my $MessageId = uc($1);
      $MessagesId[$NrMessages] = $MessageId;
      my $Read;
      if ($OneMessageTable =~ m/i_new_17\.gif/) {
        $Read = 0;
      } elsif ($OneMessageTable =~ m/i_unread_17\.gif/) {
        $Read = 0;
      } elsif ($OneMessageTable =~ m/i_read_17\.gif/) {
        $Read = 1;
      } elsif ($OneMessageTable =~ m/i_read_reply_17\.gif/) {
        $Read = 1;
      } elsif ($OneMessageTable =~ m/i_read_forward_17\.gif/) {
        $Read = 1;
      } elsif ($OneMessageTable =~ m/i_forward_17\.gif/) {
        $Read = 1;
      } elsif ($OneMessageTable =~ m/i_reply_17\.gif/) {
        $Read = 1;
      } else {
        $Self->Log(__LINE__ . ": Could not detect read status. OneMessageTable : \n:$OneMessageTable\n",stderr => 1);
        return;
      }
      # Mark undeleted/Unpopped. In support of pop server.
      $MessagesDeleted[$NrMessages] = 0;
      $MessagesPopped[$NrMessages] = 0;

      my $Downloaded = 0;
      if ($Self->{'MessagesDownloaded'}->{$MessageId}) {
        $Downloaded = 1;
      }

      if ($Self->{'Strategy'} =~ m/NotDownloaded/i) {
        $NrMessages++ unless $Downloaded;
      } elsif ($Self->{'Strategy'} =~ m/UnRead/i) {
        $NrMessages++ unless $Read;
      } else {
        $NrMessages++;
      }

      $Self->Log("$NrMessages - From '$From' - Subject '$Subject' - Read : $Read - Downloaded : $Downloaded\n",
                 MinVerbosity=>3);
    }

    # Search for a next page
    if ($PageAsString =~ m/<a href="(folder\.aspx\?fid=[^"]*?&[^"]*?dir=next[^"]*?)"/) {
      my $NextPage = HtmlUnescape($1);
      $PageUrl = $MobBaseUrl.$NextPage;
      $StillPageToGo = 1;
    } 
  }

  $Self->{'NrMessages'}        = $NrMessages;
  $Self->{'MessagesFrom'}      = \@MessagesFrom;
  $Self->{'MessagesSubject'}   = \@MessagesSubject;
  $Self->{'MessagesId'}        = \@MessagesId;
  $Self->{'MessagesDeleted'}   = \@MessagesDeleted;
  $Self->{'MessagesPopped'}    = \@MessagesPopped;
}

########################################################################################################################
# 
# Class method.
# Load DownloadedIds
# 
########################################################################################################################

sub LoadDownloadedIds {
  my $Self               = shift;

  my $DownloadedIdsFile  = $Self->{'DownloadedIdsFile'};

  my %MessagesDownloaded = ();

  return unless $DownloadedIdsFile;

  # First we check and or create the file with the downloaded Ids.
  if (not -e $DownloadedIdsFile) {
    open (DOWNLOADED,">$DownloadedIdsFile") || die "Could not open $DownloadedIdsFile : $!.";
    print DOWNLOADED "-- This is an automatically generated file by $0 containing the id of downloaded messages\n";
    close (DOWNLOADED);
  }
     
  open (DOWNLOADED,"$DownloadedIdsFile") || die "Could not open $DownloadedIdsFile : $!.";
  while(my $Id = <DOWNLOADED>) {
    chomp ($Id);
    $MessagesDownloaded{uc($Id)} = 1;
  }
  close (DOWNLOADED);

  $Self->{'MessagesDownloaded'} = \%MessagesDownloaded;
}
 
########################################################################################################################
# 
# Class method.
# Save DownloadedIds
# 
########################################################################################################################

sub SaveDownloadedIds {
  my $Self               = shift;

  my $DownloadedIdsFile  = $Self->{'DownloadedIdsFile'};
  my $MessagesDownloaded = $Self->{'MessagesDownloaded'}; 

  return unless $DownloadedIdsFile;

  # Remove preexisting file and recreate with new info.
  unlink($DownloadedIdsFile);
  open (DOWNLOADED,">$DownloadedIdsFile") || die "Could not open $DownloadedIdsFile : $!.";
  print DOWNLOADED "-- This is an automatically generated file by $0 containing the id of downloaded messages\n";
  foreach my $Key (sort keys %$MessagesDownloaded) {
    print DOWNLOADED "$Key\n";
  }
  close (DOWNLOADED);
}
 
########################################################################################################################
# 
# Class method.
# Process the messages retrieved from a folder.
# Acts on global variables @Messages ...
# It just takes FolderIdx for knowing the name. (and now also for the MoveToFolder/Delete command)
# 
########################################################################################################################

sub ProcessMessagesFromFolder  {
  my $Self              = shift;
  my $FolderIdx         = shift;

  my $MailProcessor     = $Self->{'MailProcessor'};
  my $FolderNames       = $Self->{'FolderNames'};
  my $NrMessages        = $Self->{'NrMessages'};
  my $Strategy          = $Self->{'Strategy'};
  my $MessagesFrom      = $Self->{'MessagesFrom'};
  my $MessagesSubject   = $Self->{'MessagesSubject'};
  my $MessagesId        = $Self->{'MessagesId'};
  my $MarkRead          = $Self->{'MarkRead'};
  my $MoveToFolder      = $Self->{'MoveToFolder'};
  my $Delete            = $Self->{'Delete'};
  my $FolderName        = $FolderNames->[$FolderIdx];
  # Now let's run through all detected messages ..
  my $MessageIdx;

  for ($MessageIdx = 0; $MessageIdx < $NrMessages; $MessageIdx++) {

    # Identifying a bit the message for the log.
    $Self->Log("Handling mail\n".
               "  from    : '$MessagesFrom->[$MessageIdx]'\n".
               "  subject : '$MessagesSubject->[$MessageIdx]'\n",MinVerbosity => 1);

    # JDLA getEmail , provided that HaveMessageAlready was not set.
    my $Message = $Self->GetEmail($MessageIdx,$FolderName);

    # Pipe it through a processor such as procmail.
    if ($MailProcessor) {
      $Self->Log("Sending mail to '$MailProcessor'.\n",MinVerbosity => 1);
      open PR,"|$MailProcessor";
      print PR $Message;
      close PR || die "Sending mail to '$MailProcessor' did not succeed. See error log.";
    }

    # And maybe we have to mark it read too ?
    if ($MarkRead =~ m/^Yes$/i) {
      $Self->MarkRead($MessageIdx, $FolderIdx);
    }
 
    # Maybe we even have to move it !
    if ($MoveToFolder ne "") {

      # If MoveToFolder is of the format @FileName, get the folder name from that FileName.
      if ($MoveToFolder =~ m/^@(.*)$/) {
        my $MoveToFolderName = $1;
        open(IN,$MoveToFolderName) || die "Could not open '$MoveToFolderName' : $!";
        $MoveToFolder = <IN>;
        chomp $MoveToFolder;
        close(IN);
      }

      # Do the move.
      $Self->MoveToFolder($MessageIdx,$MoveToFolder,$FolderIdx);
    }
   
    # Or maybe we have to remove it.
    if ($Delete =~ m/^Yes$/i) {
      $Self->DeleteMessage($MessageIdx,$FolderIdx);
    }

    # And now also remember it was 'downloaded'
    my $MessageId = $MessagesId->[$MessageIdx];
    $Self->{'MessagesDownloaded'}->{$MessageId} = 1;

    $Self->Log("Done.\n",MinVerbosity => 1);
  }
}

########################################################################################################################
#
# Search for Cookie in the CookiesFile.
# Class method.
# Argument : The cookie to be found.
# Returns its value.
#
########################################################################################################################

sub FindCookie {
  my $Self         = shift;
  my $CookieToFind = shift;
  
  my $TmpCookiesFile = $Self->{'TmpCookiesFile'};

  open (COOKIES,$TmpCookiesFile) || die "Could not open '$TmpCookiesFile'.";
  while (<COOKIES>) {
    chomp;
    next if m/^#/;  # Comment
    next if m/^$/;  # Empty line.
    my @SplittedLine = split /\t/;
    if ($SplittedLine[5] eq $CookieToFind) {
      close COOKIES;
      return $SplittedLine[6];
    }
  }
  close COOKIES;
  return "";
}


########################################################################################################################
# 
# Class method.
# Move the email message to a folder.
# MessageIdx and FolderName as argument.
#
########################################################################################################################

sub MoveToFolder {
  my $Self             = shift;
  my $MessageIdx       = shift;
  my $TargetFolderName = shift;
  my $SourceFolderIdx  = shift;

  my $MessagesId   = $Self->{'MessagesId'};
  my $FolderNames  = $Self->{'FolderNames'};
  my $FolderIds    = $Self->{'FolderIds'};
  my $NrFolders    = $Self->{'NrFolders'};
  my $NParameter   = $Self->{'NParameter'};

  my $MessageId    = $MessagesId->[$MessageIdx];

  # Find out which folder (the index in @FolderIds) is meant.
  my $TargetFolderIdx   = 0;
  my $TargetFolderFound = 0; 
  while ((not $TargetFolderFound) && $TargetFolderIdx<$NrFolders) {
    if (lc $TargetFolderName eq lc $FolderNames->[$TargetFolderIdx]) {
      $TargetFolderFound = 1;
    } else {
      $TargetFolderIdx++;
    }
  }

  # Let's die the hard way if we do not find that folder.
  if ($TargetFolderFound) {
    $Self->Log("Folder with name '$TargetFolderName' used in MoveToFolder could not be located.\n",stderr => 1);
    return;
  }
      
  $Self->Log("Moving email message to folder '$TargetFolderName'.\n",MinVerbosity => 1);
  
  # We go via the mobile part of the site for simplicity.
  my $MobBaseUrl = $Self->{'BaseUrl'};
  if ($MobBaseUrl !~ s/\/mail\//\/md\//) {
    $Self->Log("Unexpected format in $MobBaseUrl.\n", stderr => 1);
    return
  }

  my $ToBox   = $FolderIds->[$TargetFolderIdx];
  my $FromBox = $FolderIds->[$SourceFolderIdx];
  my $MT = $Self->FindCookie("mt");

  my $Url = $MobBaseUrl . 
     "movedeletemessage.aspx?state=Move&msglist=$MessageId&src=$FromBox&dest=$ToBox&mt=$MT&$NParameter";

  # Do The move ...
  my ($EmailPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Url); 
}

########################################################################################################################
# 
# Class method.
# Delete the message.
# MessageIdx as argument.
#
########################################################################################################################

sub DeleteMessage {
  my $Self            = shift;
  my $MessageIdx      = shift;
  my $SourceFolderIdx = shift;

  my $MessagesId = $Self->{'MessagesId'};
  my $FolderIds  = $Self->{'FolderIds'};
  my $NParameter = $Self->{'NParameter'};

  my $MessageId  = $MessagesId->[$MessageIdx];

  $Self->Log("Deleting email message.\n",MinVerbosity => 1);
 
  # We go via the mobile part of the site for simplicity.
  my $MobBaseUrl = $Self->{'BaseUrl'};
  if ($MobBaseUrl !~ s/\/mail\//\/md\//) {
    $Self->Log("Unexpected format in $MobBaseUrl.\n", stderr => 1);
    return
  }

  my $FromBox = $FolderIds->[$SourceFolderIdx];
  my $MT = $Self->FindCookie("mt");

  my $Url = $MobBaseUrl .
     "movedeletemessage.aspx?state=Delete&msglist=$MessageId&src=$FromBox&dest=$TrashFolderId&mt=$MT&$NParameter";

  # Do The Delete ...
  my ($EmailPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Url); 
}

########################################################################################################################
# 
# Class method.
# Mark the email message as read
# MessageIdxIdx as argument.
#
########################################################################################################################

sub MarkRead {
  my $Self       = shift;
  my $MessageIdx = shift;

  my $NParameter = $Self->{'NParameter'};
  my $MessagesId = $Self->{'MessagesId'};
  my $MessageId  = $MessagesId->[$MessageIdx];
  
  $Self->Log("Marking email message as read.\n",MinVerbosity => 1);
  
  # We go via the mobile part of the site for simplicity.
  my $MobBaseUrl = $Self->{'BaseUrl'};
  if ($MobBaseUrl !~ s/\/mail\//\/md\//) {
    $Self->Log("Unexpected format in $MobBaseUrl.\n", stderr => 1);
    return
  }

  my $MT = $Self->FindCookie("mt");

  my $Url = $MobBaseUrl . "movedeletemessage.aspx?state=MarkAsRead&msglist=$MessageId&mt=$MT&$NParameter";

  # Mark as Read ...
  my ($EmailPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Url); 
}

########################################################################################################################
# 
# Class method.
# Return the email message (mbox format) as one big string.
# MessageIdx and FolderName as argument.
#
########################################################################################################################

sub GetEmail {
  my $Self       = shift;
  my $MessageIdx = shift;
  my $FolderName = shift;

  my $MessagesId = $Self->{'MessagesId'};
  my $Login      = $Self->{'Login'};
  my $Domain     = $Self->{'Domain'};
  my $BaseUrl    = $Self->{'BaseUrl'};

  my $MessageId  = $MessagesId->[$MessageIdx];

  $Self->Log("Getting email message.\n",MinVerbosity => 1);

  my $Url = "${BaseUrl}GetMessageSource.aspx?msgid=$MessageId";
  my ($EmailPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Url,FollowForward => 1);

  $EmailPageAsString =~ s/^[\s\n]*//; 
  $EmailPageAsString = HtmlUnescape($EmailPageAsString); # Strips all HTML artifacts from the message body.
  $EmailPageAsString =~ s/\r\n/\n/gs; # Force unix line endings.

  if ($EmailPageAsString !~ /<pre>[\s\n]*(.*?)<[^<]+$/si) {
    $Self->Log("Unable to download email message.\n",stderr => 1);
    return;
  }
  $EmailPageAsString = $1;

  # Fallback envelope sender and date, case it would not be in the message.
  my $FromAddress = "$Login\@$Domain";
  my $FromDate    = scalar gmtime;

  # Strip "From whoever" when found on the first line- the format is wrong for mbox files anyway.
  if ($EmailPageAsString =~ s/^From ([^ ]*) [^\n]*\n//s) { 
    $FromAddress = $1; 
  } elsif ($EmailPageAsString =~ m/^From:[^<]*<([^>]*)>/m) { 
    $FromAddress = $1;  
  }

  # Apply >From quoting
  $EmailPageAsString =~ s/^From ([^\n]*)\n/>From $1/gm;

  # If an mboxheader was desired, make up one
  if ($EmailPageAsString =~ m/^\t (\w+), (\d+) (\w+) (\d+) (\d+):(\d+):(\d+) ([+-]?.+)/m) {
    my $DayOfWeek = $1;
    my $Month     = $3;
    my $Day       = $2;
    my $Hour      = $5;
    my $Minute    = $6;
    my $Second    = $7;
    my $Year      = $4;
    my $TimeZone  = $8;

    # Put date in mboxheader in UTC time
    $Hour -= $TimeZone;
    while ($Hour < 0)  { $Hour += 24; }
    while ($Hour > 23) { $Hour -= 24; }

    $FromDate = sprintf ("%s %s %02d %02d:%02d:%02d %d",$DayOfWeek,$Month,$Day,$Hour,$Minute,$Second,$Year);
  }

  # Add an mbox-compatible header
  # And add some identifying headers.
  $EmailPageAsString =~ s/^/From $FromAddress $FromDate\nX-$ProgramName-Version: $Revision\nX-$ProgramName-Folder: $FolderName\nX-$ProgramName-User: $Login\n/;

  return $EmailPageAsString;
}

########################################################################################################################
# 
# Standard return for a correct package.
#
########################################################################################################################

1;

########################################################################################################################
#
# PopLive package
# This is a POP3 server object that takes services of GetLive to interface between a POP client and hotmail.
#
########################################################################################################################

package PopLive;

use strict;
use vars qw(@ISA);
use Net::Server::Fork; # any personality will do
@ISA = qw(Net::Server::Fork);

my $ConnectionTimeOut = 600;

########################################################################################################################
# 
# Inherited Class method.
# A new request (connection) has come in. Process it.
#
########################################################################################################################

sub process_request {
  my $Self = shift;

  $Self->{'LoggedIn'}          = 0;
  $Self->{'Username'}          = "";
  $Self->{'Password'}          = "";
  $Self->{'FolderToProcess'}   = "";
  $Self->{'FolderIdToProcess'} = 1;
  $Self->{'MarkRead'}          = 0;

  $Self->Respond("+OK POP3 server ready.\r\n");

  my $Remote = $Self->{'server'}->{'peeraddr'};
  print LOG "INFO - Client : $Remote - Established connection.\n" if ($Verbosity);

  # Handy for debugging purposes. All 'values' of this self object.
  #my $ServerHash = $Self->{'server'};
  #foreach my $Key (keys %$ServerHash) {
  #  print "Key : '$Key' - Value : $ServerHash->{$Key}\r\n";
  #}

  my $PreviousAlarm = alarm($ConnectionTimeOut);
  my $Input;

  while (1) {

    # Wait on input, but with a TimeOut.
    eval {
      local $SIG{ALRM} = sub { die "TimeOut"; };
      $Input = <STDIN>;
      die "NoInput" unless $Input;
      alarm($ConnectionTimeOut);
    };

    if ( $@=~/TimeOut/i ) {
      $Self->Respond("-ERR Timed out.\r\n");
      return;
    } elsif ( $@=~/NoInput/i ) {
      $Self->Respond("-ERR No input.\r\n");
      return;
    }

    chomp($Input);
    $Input =~ s/\r$//;

    $Self->{'Input'} = $Input;
 
    print LOG "INFO - Client : $Remote - Handling input '$Input'.\n" if ($Verbosity);

    # Commands in the POP3 consist of a case-insensitive keyword, possibly
    # followed by one or more arguments.
    my ($Command,$Argument,$Argument2) = split(/ /,$Input);
    $Command   = "" unless defined $Command;
    $Argument  = "" unless defined $Argument;
    $Argument2 = "" unless defined $Argument2;

    if (!$Command) {
      $Self->PopCmdUnknown();
      next;
    }

    $Command =~ tr/a-z/A-Z/;  # Convert commands to uppercase

    # Handle the different potential POP commands.
    # Mostly by handing off to a sub.

    if ($Command eq "USER") {

      if (!defined($Argument)) {
        $Self->PopCmdUnknown();
        next;
      }

      if ($Self->{'LoggedIn'} == 1) {
        $Self->Respond("-ERR Already logged in.\r\n");
        next;
      }

      # An easy trick to circumvent problems with spaces in quoted strings like "Postvak IN" that get splitted.
      my $RestoredArgument = $Input;
      $RestoredArgument =~ s/USER\s+//i;
      $Self->{'Username'} = $RestoredArgument;
 
      $Self->Respond("+OK Password ?\r\n");
    }

    # We start action of retrieving on the "PASS" command.

    elsif ($Command eq "PASS") {

      if ($Self->{'LoggedIn'} == 1) {
        $Self->Respond("-ERR Already logged in.\r\n");
        next;
      }
      $Self->{'Password'} = $Argument;

      # All of the action starts with creating a GetLive object.
      my $GetLive         = GetLive->new();
      $Self->{'GetLive'}  = $GetLive;
      print LOG "INFO - Client : $Remote - GetLive serving with tmp in $GetLive->{'TmpDir'}\n" if ($Verbosity);

      # Now we parse the username (like jos@hotmail.com?folder=Sent&markread=0

      my $Username =  $Self->{'Username'};
      if ($Username !~ m/^([^@]+?)@/) {
        $Self->Respond("-ERR Username '$Self->{'Username'}' malformed ('Login').\r\n");
        return;
      }
      $Username = $';
      $GetLive->{'Login'} = $1;

      if ($Username !~ m/^([^\?]+?)(\?|$)/) {
        $Self->Respond("-ERR Username '$Self->{'Username'}' malformed ('Domain').\r\n");
        return;
      }
      $Username = $';
      $GetLive->{'Domain'} = $1;

      my @ParameterPairs = split(/&/,$Username);
      foreach my $ParameterPair (@ParameterPairs) {
        chomp($ParameterPair);
        if ($ParameterPair !~ m/(.+)=(.+)/) {
          $Self->Respond("-ERR Username '$Self->{'Username'}' malformed (ParameterPair '$ParameterPair').\r\n");
          return;
        }
        my $Key   = $1;
        my $Value = $2;
        $Value =~ s/"(.+?)"/$1/;
        if ($Key =~ m/folder/i) {
          $Self->{'FolderToProcess'} = $Value;
        } elsif ($Key =~ m/folderid/i) {
          $Self->{'FolderIdToProcess'} = $Value;
        } elsif ($Key =~ m/markread/i) {
          $Self->{'MarkRead'} = $Value;
        } elsif ($Key =~ m/keepmsgstatus/i) {
          ; # just accept as a dummy do nothing.
        } else {
          $Self->Respond("-ERR Username '$Self->{'Username'}' malformed ('$ParameterPair' : unknown key).\r\n");
          return;
        }
      }

      print LOG "INFO - Client : $Remote - ".
        "Username parsed to '$GetLive->{'Login'}' @ '$GetLive->{'Domain'}' - Folder : '$Self->{'FolderToProcess'}' - FolderId : '$Self->{'FolderIdToProcess'}'\n" if ($Verbosity);

      # Some other loose ends we have to deliver to GetLive object before it can do its job.
      $GetLive->{'Password'}    =  $Self->{'Password'};
      $GetLive->{'Strategy'}    =  "All";
      $GetLive->{'LogToStdout'} = 0; # Because in a server it would go to the client ...
      $GetLive->{'DieOnError'}  = 0; # Because in a server it stops the server ...

      # Try to login and obtain the folders. Success will be seen as a login.
      my $LoggedIn = $GetLive->Login();
      if ($LoggedIn) {
        $LoggedIn = $GetLive->GetFolders();
      }
      $Self->{'LoggedIn'} = $LoggedIn;

      # Return appropriate status if not logged in. And get the messages if correctly logged in.
      if (!$LoggedIn) {
        $Self->Respond("-ERR Login incorrect.\r\n");
        exit(0); # This closes (intentionally) the connection on a wrong password.
      } elsif ($LoggedIn) {
        my $FolderNames       = $GetLive->{'FolderNames'};
        my $FolderIds         = $GetLive->{'FolderIds'};
        my $NrFolders         = $GetLive->{'NrFolders'};
        my $FolderToProcess   = $Self->{'FolderToProcess'};
        my $FolderIdToProcess = $FolderToProcess ? 0 : $Self->{'FolderIdToProcess'};
        for (my $FolderIdx=0;$FolderIdx<$NrFolders;$FolderIdx++) {
          next if ($FolderToProcess && (lc($FolderToProcess) ne lc($FolderNames->[$FolderIdx])));
          next if ($FolderIdToProcess && ($FolderIds->[$FolderIdx] !~ m/^(0|-)*$FolderIdToProcess$/));
          $Self->{'FolderToProcessIdx'} = $FolderIdx;
          # JDLA hack. Drafts folder does not work, also not in real. Assuming 000-...-4 is the draft folder.
          next if ($FolderIds->[$FolderIdx] =~ m/^(0|-)*4$/);
          print LOG "INFO - Client : $Remote - Processing folder $FolderNames->[$FolderIdx].\n" if ($Verbosity);
          $GetLive->GetMessagesFromFolder($FolderIdx);
          my $NrMessages = $GetLive->{'NrMessages'};
          print LOG "INFO - Client : $Remote - $NrMessages Messages.\n" if ($Verbosity);
        }
        my $NrMessages = $GetLive->{'NrMessages'};
        # OK Logged in and number of messages known. Octet count is fake.
        $Self->Respond("+OK $NrMessages messages (1302 octets)\r\n");
      }
    }

    # AUTH

    elsif ($Command eq "AUTH") {
      $Self->Respond("+OK\r\n.\r\n");
    }

    # Unexisting FOLD extension

    elsif ($Command eq "FOLD") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdFold();
    }

    # STAT

    elsif ($Command eq "STAT") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdStat();
    }

    # LIST

    elsif ($Command eq "LIST") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdList($Argument);
    }

    # RETR

    elsif ($Command eq "RETR") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdRetr($Argument);
    }

    # TOP

    elsif ($Command eq "TOP") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdTop($Argument,$Argument2);
    }

    # DELE

    elsif ($Command eq "DELE") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdDele($Argument);
    }

    # NOOP

    elsif ($Command eq "NOOP") {
      $Self->Respond("+OK No operation\r\n");
    }

    # RSET

    elsif ($Command eq "RSET") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdRset();
    }

    # QUIT

    elsif ($Command eq "QUIT") {
      $Self->PopCmdQuit();
    }

    # UIDL

    elsif ($Command eq "UIDL") {
      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdUidl($Argument);
    }

    # CAPA

    elsif ($Command eq "CAPA") {
      $Self->Respond( "+OK Capability list follows\r\n".
             "TOP\r\n".
             "USER\r\n".
             "UIDL\r\n".
             "EXPIRE NEVER\r\n".
             ".\r\n");
    }

    # Unkown ???
 
    else {
      $Self->PopCmdUnknown();
    }

  }; # while(1) loop receiving commands.
  
  # Reinstating previous alarm.
  alarm($PreviousAlarm);
}

########################################################################################################################
# 
# Class method.
# POP3 command unknown.
# 
########################################################################################################################

sub PopCmdUnknown {
  my $Self   = shift;

  my $Input  = $Self->{'Input'} || "";

  $Self->Respond("-ERR Unknown command : '$Input'.\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 command DELE.
# 
########################################################################################################################

sub PopCmdDele {
  my $Self       = shift;
  my $MessageIdx = shift;

  $MessageIdx = 0 if (!defined $MessageIdx || $MessageIdx eq "");

  my $GetLive         = $Self->{'GetLive'};
  my $NrMessages      = $GetLive->{'NrMessages'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

  if ($MessageIdx < 1 || $MessageIdx > $NrMessages) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  $MessagesDeleted->[$MessageIdx-1] = 1;

  $Self->Respond("+OK message $MessageIdx deleted.\r\n");
}                        

########################################################################################################################
# 
# Class method.
# POP3 command RSET.
# 
########################################################################################################################

sub PopCmdRset {
  my $Self       = shift;

  my $GetLive         = $Self->{'GetLive'};
  my $NrMessages      = $GetLive->{'NrMessages'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

  # The only requirement on RSET is unmarking deleted.
  for (my $Message = 0; $Message < $NrMessages; $Message++) {
    $MessagesDeleted->[$Message] = 0;
  }

  $Self->Respond("+OK $NrMessages 1302\r\n");
}                        

 
########################################################################################################################
# 
# Class method.
# POP3 command LIST.
# 
########################################################################################################################

sub PopCmdList {
  my $Self       = shift;
  my $MessageIdx = shift;

  $MessageIdx = 0 if (!defined $MessageIdx || $MessageIdx eq "");

  my $GetLive         = $Self->{'GetLive'};
  my $NrMessages      = $GetLive->{'NrMessages'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

  if ($MessageIdx == 0) {
    $Self->Respond("+OK $NrMessages messages (1302 octets)\r\n");
    for (my $i=0;$i<$NrMessages;$i++) {
      my $j = $i+1;
      $Self->Respond("$j 1302\r\n") unless $MessagesDeleted->[$i];
    }
    $Self->Respond(".\r\n");
  } else {
    $Self->Respond("+OK $MessageIdx 1302\r\n") unless $MessagesDeleted->[$MessageIdx-1];
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n") if $MessagesDeleted->[$MessageIdx-1];
  }
}
   
########################################################################################################################
# 
# Class method.
# POP3 command QUIT.
# 
########################################################################################################################

sub PopCmdQuit {
  my $Self       = shift;

  if (!$Self->{'LoggedIn'}) {
    $Self->Respond("+OK POP3 Quit.\r\n");
    exit(0);
  }

  my $Remote          = $Self->{'server'}->{'peeraddr'};
  my $GetLive         = $Self->{'GetLive'};
  my $SourceFolderIdx = $Self->{'FolderToProcessIdx'};
  my $NrMessages      = $GetLive->{'NrMessages'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};
  my $MessagesPopped  = $GetLive->{'MessagesPopped'};
 
  # Effectively delete messages that are marked deleted.
  # MarkRead if asked so.
  for (my $Message = 0; $Message < $NrMessages; $Message++) {
    if ($MessagesDeleted->[$Message]) {
      $GetLive->DeleteMessage($Message,$SourceFolderIdx);
      print LOG "INFO - Client : $Remote - DeleteMessage($Message,$SourceFolderIdx)\n" if ($Verbosity);
    }
    if ($Self->{'MarkRead'} && $MessagesPopped->[$Message]) {
      $GetLive->MarkRead($Message);
      print LOG "INFO - Client : $Remote - MarkRead($Message)\n" if ($Verbosity);
    }
  }

  $Self->Respond("+OK POP3 Quit.\r\n");
  exit(0);
}

########################################################################################################################
# 
# Class method.
# POP3 command RETR.
# 
########################################################################################################################

sub PopCmdRetr {
  my $Self       = shift;
  my $MessageIdx = shift;

  $MessageIdx = 0 if (!defined $MessageIdx || $MessageIdx eq "");

  my $GetLive         = $Self->{'GetLive'};
  my $NrMessages      = $GetLive->{'NrMessages'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};
  my $MessagesPopped  = $GetLive->{'MessagesPopped'};

  if ($MessageIdx < 1 || $MessageIdx > $NrMessages) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  if ($MessagesDeleted->[$MessageIdx-1]) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  $Self->Respond("+OK 1302 octets\r\n");
  my $Message = $GetLive->GetEmail($MessageIdx-1,$Self->{'FolderToProcess'});
  $Message =~ s/^\.$/\.\./g;     # Avoid . on its own. Replace by ..
  $Message =~ s/\n/\r\n/g;  # CR/LF endings.
  $Self->Respond($Message,1); # 1 suppresses log
  $Self->Respond(".\r\n");

  # Mark popped.
  $MessagesPopped->[$MessageIdx-1] = 1;
}

########################################################################################################################
# 
# Class method.
# POP3 command STAT.
# 
########################################################################################################################

sub PopCmdStat {
  my $Self       = shift;

  my $GetLive    = $Self->{'GetLive'};
  my $NrMessages = $GetLive->{'NrMessages'};
  $Self->Respond("+OK $NrMessages 1302\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 command TOP.
# 
########################################################################################################################

sub PopCmdTop {
  my $Self       = shift;
  my $MessageIdx = shift;
  my $Lines      = shift;

  $MessageIdx = 0  if (!defined $MessageIdx || $MessageIdx eq "");
  $Lines      = -1 if (!defined $Lines      || $Lines      eq "");

  my $GetLive         = $Self->{'GetLive'};
  my $NrMessages      = $GetLive->{'NrMessages'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

  if ($Lines < 0) {
    $Self->PopCmdUnknown();
    return;
  }

  if ($MessageIdx < 1 || $MessageIdx > $NrMessages) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  if ($MessagesDeleted->[$MessageIdx-1]) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  $Self->Respond("+OK\r\n");
  my $Message = $GetLive->GetEmail($MessageIdx-1,$Self->{'FolderToProcess'});
  my $FoundNewLine = 0;
  my @Lines = split(/\n/,$Message,-1);
  pop(@Lines); # Last is always \n in a mail. Drop.
  foreach my $Line (@Lines) {
    $Line =~ s/^\.$/\.\./g;     # Avoid . on its own. Replace by ..
    $Self->Respond("$Line\r\n");
    $FoundNewLine |= ($Line eq "");
    last if ($FoundNewLine && ($Lines-- <= 0))
  }
  $Self->Respond(".\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 command UIDL.
# 
########################################################################################################################

sub PopCmdUidl {
  my $Self       = shift;
  my $MessageIdx = shift;

  $MessageIdx = 0  if (!defined $MessageIdx || $MessageIdx eq "");

  my $Remote          = $Self->{'server'}->{'peeraddr'};
  my $GetLive         = $Self->{'GetLive'};
  my $NrMessages      = $GetLive->{'NrMessages'};
  my $MessagesId      = $GetLive->{'MessagesId'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

  if ($MessageIdx == 0) {
    $Self->Respond("+OK UIDL listing follows.\r\n");
    for (my $i=0;$i<$NrMessages;$i++) {
      my $j = $i+1;
      $Self->Respond("$j $MessagesId->[$i]\r\n") unless $MessagesDeleted->[$i];
    }
    $Self->Respond(".\r\n");
  } else {
    $Self->Respond("+OK $MessageIdx $MessagesId->[$MessageIdx-1]\r\n") unless $MessagesDeleted->[$MessageIdx-1];
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n") if $MessagesDeleted->[$MessageIdx-1];
  }
}

########################################################################################################################
# 
# Class method.
# Pseudo POP3 command FOLD.
# 
########################################################################################################################

sub PopCmdFold {
  my $Self = shift;

  my $GetLive     = $Self->{'GetLive'};
  my $FolderNames = $GetLive->{'FolderNames'};
  my $FolderIds   = $GetLive->{'FolderIds'};
  my $NrFolders   = $GetLive->{'NrFolders'};

  $Self->Respond("+OK Folders follow.\r\n");
  for (my $Folder=0;$Folder<$NrFolders;$Folder++) {
    $Self->Respond("$FolderIds->[$Folder] - $FolderNames->[$Folder]\r\n");
  }
  $Self->Respond(".\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 Error : not logged in.
# 
########################################################################################################################

sub PopCmdNotLoggedIn {
  my $Self   = shift;

  my $Input  = $Self->{'Input'};
  $Self->Respond("-ERR You are not logged in.\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 Give Response. We didn't use simple print to enable logging facility if needed.
# 
########################################################################################################################

sub Respond {
  my $Self        = shift;
  my $What        = shift;
  my $SuppressLog = shift;

  $SuppressLog = 0 if (!defined $SuppressLog || $SuppressLog eq "");

  binmode STDOUT; # Needed for avoiding protocol errors by \n -> \r\n issues f.i. in Windows.
  print $What;
  if (!$SuppressLog) {
    my $Remote = $Self->{'server'}->{'peeraddr'};
    print LOG "INFO - Client : $Remote - $What" if ($Verbosity);
  }
}

########################################################################################################################
# 
# Standard return for a correct package.
#
########################################################################################################################

1;

########################################################################################################################
# 
# Here starts the 'main' stuff and routines
#
########################################################################################################################

########################################################################################################################
# 
# Display some text.
# First parameter : text to be displayed.
# Then a number of named parameters that are optional. 
# See %args.
#
########################################################################################################################

sub Display($%) {
  my $Text = shift;
  my %Args = (MinVerbosity => 0,
              stderr       => 0,
              @_);

  # stderr messages are under no circumstances suppressed.
  if ($Args{'stderr'}) {
    print STDERR $Text;
    return;
  }

  # Filter out the ones for which the verbosity is too high.
  return if ($Args{'MinVerbosity'} > $Verbosity);

  # And finally print ;-)
  # Stdout is flushed immediate , not to miss error messages.
  my $WasSelected = select(STDOUT);
  $|=1;
  select($WasSelected);

  print STDOUT $Text;

  return;
}

########################################################################################################################
# 
# Display the introduction text.
# Text as argument, stderr as optional named argument to redirect to stderr.
#
########################################################################################################################

sub DisplayIntroText(%) {
  my %Args = (stderr => 0,
              MinVerbosity => 1,
              @_);
  my $Text = 
    "\n\n".
    "$ProgramName $Revision Copyright (C)2007-2010 Jos De Laender.\n".
    "$ProgramName comes with ABSOLUTELY NO WARRANTY.\n".
    "This is free software, and you are welcome to redistribute it\n".
    "under certain conditions; see the file License for details.\n".
    '$Name: Release_2_3 $' . "\n".
    '$Id: GetLive.pl,v 2.16 2010/11/09 19:21:33 jdla Exp $' . "\n".
    "Running at ".localtime(time)."\n\n";
  Display($Text,%Args);
}

########################################################################################################################
# 
# This is only called in error conditions. Output will go to stderr.
#
########################################################################################################################

sub DisplayUsageAndExit() {
  Display("Usage: $ProgramName --config-file ConfigFile [--verbosity -1..100]\n".
          "Usage: $ProgramName --port PortNumber [--verbosity 0..100]\n",
          stderr => 1);
  exit(1);
}

########################################################################################################################
# 
# Parse the command line
#
########################################################################################################################

sub ParseArgs() {
  my $ArgvAsString =  join(" ",@ARGV);

  # --config-file or --port is a mandatory argument.
  if ($ArgvAsString !~ m/--(config-file|port)\s+([\w\/\\~\.\-]+)/si) {
    DisplayUsageAndExit();
  }
  my $OrigArgvAsString = $ArgvAsString;
  $ArgvAsString = $` . $';   # The matched stuff removed.

  if ($OrigArgvAsString =~ m/--config-file\s+([\w\/\\~\.\-]+)/si) {
    $ConfigFile =  $1;
  } else {
    $ServerMode = 1;
  }

  # --verbosity is an optional argument.
  if ($ArgvAsString =~ m/--verbosity\s+(\d+)/si) {
    $Verbosity = $1;
    $ArgvAsString = $` . $'; # The matched stuff removed.
  }
  # Should have no other arguments.
  $ArgvAsString =~ s/\s//sg;
  if ($ArgvAsString ne "") {
    Display("Wrong command line arguments '$ArgvAsString'.\n",stderr => 1);
    DisplayUsageAndExit();
  }
}

########################################################################################################################
# 
# The 'main' program.
#
########################################################################################################################

DisplayIntroText();
ParseArgs();

if ($ServerMode) {
  # Open a log file and make it line buffered.
  my $LogFileName = File::Spec->tmpdir() . "/$ProgramName.$$.$^T.log";
  if ($Verbosity) {
    open (LOG,">$LogFileName") || die "Could not open '$LogFileName' : $!";
    my $OldFileHandle = select LOG;
    $| =1;
    select $OldFileHandle;
    print "INFO : Logging to $LogFileName\n\n";
  }

  # Start a POP3 server object and have it run. Never finishes.
  my $Server = PopLive->new();
  $Server->run();
  exit(0);
}

# Here goes the normal GetLive, but now via object.
my $GetLive = GetLive->new();
$GetLive->ParseConfig();
$GetLive->Login();
$GetLive->GetFolders();
$GetLive->LoadDownloadedIds();

my $NrFolders   = $GetLive->{'NrFolders'};
my $FolderNames = $GetLive->{'FolderNames'};
my $FolderIds   = $GetLive->{'FolderIds'};
my $SkipTrash   = $GetLive->{'SkipTrash'};

for (my $FolderIdx=0;$FolderIdx<$NrFolders;$FolderIdx++) {
  next if (scalar keys %FoldersToProcess && not exists $FoldersToProcess{lc $FolderNames->[$FolderIdx]});
  next if ( ($SkipTrash =~ m/^Yes$/i) && ($FolderIds->[$FolderIdx] eq $TrashFolderId) );
  # JDLA hack. Drafts folder does not work, also not in real. Assuming 000-...-4 is the draft folder.
  next if ($FolderIds->[$FolderIdx] =~ m/^(0|-)*4$/);
  Display("\nProcessing folder $FolderNames->[$FolderIdx].\n",MinVerbosity => 1);
  $GetLive->GetMessagesFromFolder($FolderIdx);
  my $NrMessages = $GetLive->{'NrMessages'};
  Display("$NrMessages Messages.\n",MinVerbosity => 1);
  $GetLive->ProcessMessagesFromFolder($FolderIdx);  # FolderIdx just for name calculation.
}

$GetLive->SaveDownloadedIds();

Display("All done.\n",MinVerbosity => 1);

exit(0);

########################################################################################################################
