{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.21    10/26/2004 10:39:54 PM  JPMugaas
  Updated refs.

  Rev 1.20    5/17/04 9:50:52 AM  RLebeau
  Changed TIdRepliesPOP3 constructor to use 'reintroduce' instead

  Rev 1.19    5/16/04 5:26:58 PM  RLebeau
  Added TIdRepliesPOP3 class

  Rev 1.18    2004.04.15 12:49:46 PM  czhower
  Fixed bug in TIdReplyPOP3.IsEndMarker

  Rev 1.17    2004.02.03 5:45:44 PM  czhower
  Name changes

  Rev 1.16    2004.01.29 12:07:52 AM  czhower
  .Net constructor problem fix.

  Rev 1.15    2004.01.22 5:52:54 PM  czhower
  Visibilty fix + TextIsSame

  Rev 1.14    1/3/2004 8:05:50 PM  JPMugaas
  Bug fix:  Sometimes, replies will appear twice due to the way functionality
  was enherited.

  Rev 1.13    22/12/2003 00:45:58  CCostelloe
  .NET fixes

  Rev 1.12    2003.10.18 9:42:12 PM  czhower
  Boatload of bug fixes to command handlers.

  Rev 1.11    2003.09.20 10:38:40 AM  czhower
  Bug fix to allow clearing code field (Return to default value)

  Rev 1.10    6/8/2003 03:26:00 AM  JPMugaas
  AssignTo added for object assignment.

  Rev 1.9    6/8/2003 02:59:24 AM  JPMugaas
  RFC 2449 and RFC 3206 support.

  Rev 1.8    6/5/2003 04:54:22 AM  JPMugaas
  Reworkings and minor changes for new Reply exception framework.

  Rev 1.7    6/4/2003 04:06:52 PM  JPMugaas
  Started preliminary worki on RFC 3206 and RFC 2449.

  Removed an old GetInternetResponse override that is no longer needed and
  causes its own problems.

  Now uses string reply codes using Kudzu's new overloaded methods so mapping
  to integers is no longer needed.  The integers used in mapping have been
  removed.

    Rev 1.6    5/30/2003 9:06:44 PM  BGooijen
  uses CheckIfCodeIsValid now

  Rev 1.5    5/26/2003 04:28:28 PM  JPMugaas
  Removed GenerateReply and ParseResponse calls because those functions are
  being removed.

  Rev 1.4    2003.05.26 10:51:42 PM  czhower
  Removed RFC / non POP3 parsing

  Rev 1.3    5/26/2003 12:22:06 PM  JPMugaas

  Rev 1.2    5/25/2003 02:40:56 AM  JPMugaas

  Rev 1.1    5/20/2003 10:58:28 AM  JPMugaas
  SetReplyExceptionCode now validated by TIdReplyPOP3.  This way, it can only
  accept our integer codes for +OK, -ERR, and +.

  Rev 1.0    5/19/2003 04:28:10 PM  JPMugaas
  TIdReply decendant for POP3.
}

unit IdReplyPOP3;

interface
{$i IdCompilerDefines.inc}

uses
  Classes,
  IdException,
  IdReply;

const
  {do not change these strings unless you know what you are doing}
  ST_OK = '+OK';    {Do not translate}
  ST_ERR = '-ERR';   {Do not translate}
  ST_SASLCONTINUE = '+';  {Do not translate}

  //note that for extended codes, we do not put the ] ending as
  //error code may be hierarchical in the future with a / separating levels
  // RFC 2449
  ST_ERR_IN_USE = 'IN-USE';     {Do not translate}  //already in use by another program
  ST_ERR_LOGIN_DELAY = 'LOGIN-DELAY';  {Do not translate}  //login delay time
  // RFC 3206
  ST_ERR_SYS_TEMP = 'SYS/PERM';  {Do not translate}  //system failure - permenent
  ST_ERR_SYS_PERM = 'SYS/TEMP'; {Do not translate} //system failure - temporary
  ST_ERR_AUTH = 'AUTH'; {Do not translate}  //authentication credential problem

const
  VALID_ENH_CODES : array[0..4] of string = (
    ST_ERR_IN_USE,
    ST_ERR_LOGIN_DELAY,
    ST_ERR_SYS_TEMP,
    ST_ERR_SYS_PERM,
    ST_ERR_AUTH
  );

type
  TIdReplyPOP3 = class(TIdReply)
  protected
    FEnhancedCode : String;
    //
    class function FindCodeTextDelim(const AText : String) : Integer;
    class function IsValidEnhancedCode(const AText : String; const AStrict : Boolean = False) : Boolean;
    class function ExtractTextPosArray(const AStr : String):Integer;
    function GetFormattedReply: TStrings; override;
    procedure SetFormattedReply(const AValue: TStrings); override;
    function CheckIfCodeIsValid(const ACode: string): Boolean; override;
    procedure SetEnhancedCode(const AValue : String);
  public
    constructor CreateWithReplyTexts(
      ACollection: TCollection = nil;
      AReplyTexts: TIdReplies = nil
      ); override;
    procedure Assign(ASource: TPersistent); override;
    procedure RaiseReplyError; override;
    class function IsEndMarker(const ALine: string): Boolean; override;
  published
    property EnhancedCode : String read FEnhancedCode write SetEnhancedCode;
  end;

  TIdRepliesPOP3 = class(TIdReplies)
  public
    constructor Create(AOwner: TPersistent); reintroduce;
  end;

  //This error is for POP3 Protocol reply exceptions
  // SendCmd / GetResponse
  EIdReplyPOP3Error = class(EIdReplyError)
  protected
    FErrorCode : String;
    FEnhancedCode : String;
  public
    constructor CreateError(const AErrorCode: String;
     const AReplyMessage: string; const AEnhancedCode : String = ''); reintroduce; virtual;
    property ErrorCode : String read FErrorCode;
    property EnhancedCode : String read FEnhancedCode;
  end;

const
  VALID_POP3_STR : Array [0..2] of String = (
     ST_OK,
     ST_ERR,
     ST_SASLCONTINUE);

type
  EIdPOP3ReplyException = class(EIdException);
  EIdPOP3ReplyInvalidEnhancedCode = class(EIdPOP3ReplyException);

implementation

uses
  IdGlobal,
  IdGlobalProtocols,
  IdResourceStringsProtocols,
  SysUtils;

{ TIdReplyPOP3 }

procedure TIdReplyPOP3.Assign(ASource: TPersistent);
var
  LR: TIdReplyPOP3;
begin
  if ASource is TIdReplyPOP3 then begin
    LR := TIdReplyPOP3(ASource);
    //set code first as it possibly clears the reply
    Code := LR.Code;
    FEnhancedCode := LR.EnhancedCode;
    FText.Assign(LR.Text);
  end else begin
    inherited Assign(ASource);
  end;
end;

function TIdReplyPOP3.CheckIfCodeIsValid(const ACode: string): Boolean;
var
  LOrd: Integer;
begin
  LOrd := PosInStrArray(ACode, VALID_POP3_STR, False);
  Result := (LOrd > -1) or (Trim(ACode) = '');
end;

constructor TIdReplyPOP3.CreateWithReplyTexts(ACollection: TCollection = nil; AReplyTexts: TIdReplies = nil);
begin
  inherited CreateWithReplyTexts(ACollection, AReplyTexts);
  FCode := ST_OK;
end;

class function TIdReplyPOP3.ExtractTextPosArray(const AStr: String): Integer;
begin
  Result := PosInStrArray(Copy(AStr, 1, FindCodeTextDelim(AStr) - 1), VALID_POP3_STR, False);
end;

class function TIdReplyPOP3.FindCodeTextDelim(const AText: String): Integer;
var
  LMin, LSpace: Integer;
  LBuf: String;
  LAddBackFlag: Boolean; //if we deleted a begging -, we need to add it back
begin
  LAddBackFlag := False;
  //we do things this way because a line can start with a minus as in
  //-ERR [IN-USE] Mail box in use
  LBuf := AText;
  if TextStartsWith(LBuf, '-') then begin
    Delete(LBuf, 1, 1);
    LAddBackFlag := True;
  end;
  LMin := IndyPos(' ', LBuf);
  LSpace := IndyPos('-', LBuf);
  if LMin > 0 then begin
    if (LSpace <> 0) and (LMin > LSpace) then begin
      Result := LSpace;
    end else begin
      Result := LMin;
    end;
  end else begin
    if LSpace <> 0 then begin
      Result := LSpace;
    end else begin
      Result := Length(AText) + 1;
    end;
  end;
  if LAddBackFlag then begin
    Inc(Result);
  end;
end;

function TIdReplyPOP3.GetFormattedReply: TStrings;
var
  i: Integer;
begin
  Result := GetFormattedReplyStrings;
  if Code <> '' then begin
    if FText.Count > 0 then begin
      for i := 0 to FText.Count - 1 do begin
        if i < FText.Count - 1 then begin
          if (Code = ST_ERR) and (FEnhancedCode <> '') then begin
            Result.Add(Code + '-' + FEnhancedCode + ' ' + FText[i]);
          end else begin
            Result.Add(Code + '-' + FText[i]);
          end;
        end else begin
          if (Code = ST_ERR) and (FEnhancedCode <> '') then begin
            Result.Add(Code + ' ' + FEnhancedCode + ' ' + FText[i]);
          end else begin
            Result.Add(Code + ' ' + FText[i]);
          end;
        end;
      end;
    end else begin
      Result.Add(Code);
    end;
  end else if FText.Count > 0 then begin
    Result.AddStrings(FText);
  end;
end;

class function TIdReplyPOP3.IsEndMarker(const ALine: string): Boolean;
var
  LPos: Integer;
begin
  Result := False;
  LPos := FindCodeTextDelim(ALine);
  if LPos > 0 then begin
    if LPos > Length(ALine) then begin
      Result := True;
    end else begin
      Result := ALine[LPos] <> '-';
    end;
  end;
end;

class function TIdReplyPOP3.IsValidEnhancedCode(const AText : String; const AStrict : Boolean = False): Boolean;
var
  LBuf : String;
  i : integer;
begin
  Result := Trim(AText) = '';
  if not Result then begin
    LBuf := AText;
    if (LBuf <> '') and TextStartsWith(LBuf, '[') then begin
      Delete(LBuf, 1, 1);
      if (LBuf <> '') and TextEndsWith(LBuf, ']') then begin
        LBuf := Fetch(LBuf, ']');
        if AStrict then begin
          Result := PosInStrArray(LBuf, VALID_ENH_CODES) > -1;
        end else begin
          {We don't use PosInStrArray because we only want the fist
          charactors in our string to match.  This is necessary because
          the POP3 enhanced codes will be hierarchical as time goes on.
          }
          for i := Low(VALID_ENH_CODES) to High(VALID_ENH_CODES) do begin
            if TextStartsWith(LBuf, VALID_ENH_CODES[i]) then begin
              Result := True;
              Exit;
            end;
          end;
        end;
      end;
    end;
  end;
end;

procedure TIdReplyPOP3.RaiseReplyError;
begin
  raise EIdReplyPOP3Error.CreateError(Code, Text.Text);
end;

procedure TIdReplyPOP3.SetEnhancedCode(const AValue: String);
var
  LBuf : String;
begin
  LBuf := AValue;
  if LBuf = '' then begin
    FEnhancedCode := '';
  end else begin
    LBuf := UpperCase(LBuf);
    if (LBuf[1] <> '[') then begin
      LBuf := '[' + LBuf;
    end;
    if (LBuf[Length(LBuf)] <> ']') then begin
      LBuf := LBuf + ']';
    end;
    if IsValidEnhancedCode(LBuf, True) then begin
      FEnhancedCode := LBuf;
    end else begin
      raise EIdPOP3ReplyInvalidEnhancedCode.Create(RSPOP3ReplyInvalidEnhancedCode + AValue);
    end;
  end;
end;

procedure TIdReplyPOP3.SetFormattedReply(const AValue: TStrings);
var
  i: Integer;
  idx : Integer;
  LOrd : Integer;
  LBuf : String;
begin
  Clear;
  if AValue.Count > 0 then begin
    LOrd := ExtractTextPosArray(AValue[0]);
    if LOrd > -1 then begin
      Code := VALID_POP3_STR[LOrd];
    end;
    for i := 0 to AValue.Count - 1 do begin
      if LOrd = -1 then begin
        LOrd := ExtractTextPosArray(AValue[i]);
      end;
      idx := FindCodeTextDelim(AValue[i]);
      LBuf := Copy(AValue[i], idx+1, MaxInt);
      if (Code = ST_ERR) and IsValidEnhancedCode(Fetch(LBuf,' ',False)) then begin
        //don't use EnhancedCode property set method because that does
        //a tighter validation than we should use for parsing replies
        //from a server.
        FEnhancedCode := Fetch(LBuf);
      end;
      Text.Add(LBuf);
    end;
    if LOrd = -1 then begin
      Code := ST_ERR;
    end;
  end;
end;

{ TIdRepliesPOP3 }

constructor TIdRepliesPOP3.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner, TIdReplyPOP3);
end;

{ EIdReplyPOP3Error }

constructor EIdReplyPOP3Error.CreateError(const AErrorCode, AReplyMessage: string;
  const AEnhancedCode : String = '');
begin
  inherited Create(AReplyMessage);
  FErrorCode := AErrorCode;
  FEnhancedCode := AEnhancedCode;
end;

end.
