{
  $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.13    27.08.2004 22:04:00  Andreas Hausladen
  speed optimization ("const" for string parameters)

  Rev 1.12    2004.05.20 1:39:16 PM  czhower
  Last of the IdStream updates

  Rev 1.11    2004.05.20 11:36:58 AM  czhower
  IdStreamVCL

  Rev 1.10    2004.05.20 11:13:02 AM  czhower
  More IdStream conversions

  Rev 1.9    2004.05.19 3:06:42 PM  czhower
  IdStream / .NET fix

  Rev 1.8    2004.02.03 5:44:04 PM  czhower
  Name changes

  Rev 1.7    2004.01.22 5:56:54 PM  czhower
  TextIsSame

  Rev 1.6    1/21/2004 2:20:24 PM  JPMugaas
  InitComponent

  Rev 1.5    1/21/2004 1:30:10 PM  JPMugaas
  InitComponent

    Rev 1.4    10/17/2003 12:41:16 AM  DSiders
  Added localization comments.

  Rev 1.3    26/09/2003 01:06:18  CCostelloe
  CodingType property added so caller can find out if it was UUE or XXE encoded

  Rev 1.2    6/14/2003 03:07:20 PM  JPMugaas
  Fixed MessageDecoder so that DecodeBegin is called right after creation and
  DecodeEnd is called just before destruction.  I also fixed where the code was
  always assuming that LDecode was always being created.

  Rev 1.1    6/13/2003 07:58:44 AM  JPMugaas
  Should now compile with new decoder design.

  Rev 1.0    11/13/2002 07:57:14 AM  JPMugaas

2003-Sep-20 Ciaran Costelloe
  CodingType property added so caller can find out if it was UUE or XXE encoded
}

unit IdMessageCoderUUE;

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdCoder3to4,
  IdMessageCoder,
  IdMessage,
  IdGlobal;

type
  TIdMessageDecoderUUE = class(TIdMessageDecoder)
  protected
    FCodingType: string;
  public
    function ReadBody(ADestStream: TStream; var AMsgEnd: Boolean): TIdMessageDecoder; override;
    property CodingType: string read FCodingType;
  end;

  TIdMessageDecoderInfoUUE = class(TIdMessageDecoderInfo)
  public
    function CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder; override;
  end;

  TIdMessageEncoderUUEBase = class(TIdMessageEncoder)
  protected
    FEncoderClass: TIdEncoder3to4Class; // TODO: change to "class of TIdEncoder00E" instead
  public
    procedure Encode(ASrc: TStream; ADest: TStream); override;
  end;

  TIdMessageEncoderUUE = class(TIdMessageEncoderUUEBase)
  protected
    procedure InitComponent; override;
  end;

  TIdMessageEncoderInfoUUE = class(TIdMessageEncoderInfo)
  public
    constructor Create; override;
  end;

implementation

uses
  IdCoderUUE, IdCoderXXE, IdException, IdGlobalProtocols, IdResourceStringsProtocols, SysUtils;

{ TIdMessageDecoderInfoUUE }

function TIdMessageDecoderInfoUUE.CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder;
var
  LUUE: TIdMessageDecoderUUE;
begin
  Result := nil;
  if TextStartsWith(ALine, 'begin ') and CharEquals(ALine, 10, ' ') and IsNumeric(ALine, 3, 7) then begin {Do not Localize}
    LUUE := TIdMessageDecoderUUE.Create(ASender);
    LUUE.FFilename := Copy(ALine, 11, MaxInt);
    LUUE.FPartType := mcptAttachment;
    Result := LUUE;
  end;
end;

{ TIdMessageDecoderUUE }

function TIdMessageDecoderUUE.ReadBody(ADestStream: TStream; var AMsgEnd: Boolean): TIdMessageDecoder;
var
  LDecoder: TIdDecoder4to3;
  LLine: string;
  LMsgEnd: Boolean;
  LEncoding: IIdTextEncoding;
  LFillCharStr: string;
begin
  Result := nil;
  AMsgEnd := False;
  LEncoding := IndyTextEncoding_8Bit;
  LLine := ReadLnRFC(LMsgEnd, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
  if LMsgEnd then begin
    Exit;
  end;
  LDecoder := nil;
  if Length(LLine) > 0 then
  begin
    case LLine[1] of
      #32..#85: begin    {Do not Localize}
        // line length may be from 2 (first char + newline) to 65,
        // this is 0 useable to 63 usable bytes, + #32 gives this as a range.
        // (yes, the line length is encoded in the first bytes of each line!)

        // TODO: need to do a better job of differentiating between UUE and XXE.
        //
        // UUE and XXE both encode 45 bytes max per line. Each line starts with
        // an encoded character that indicates the number of encoded bytes in the
        // line, not counting padding.
        //
        // UUE encodes the byte count as one of the following characters:
        //
        //    0         1         2         3         4
        //    0123456789012345678901234567890123456789012345
        //    `!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLM
        //
        // XXE encodes the byte count as one of the following characters:
        //
        //    0         1         2         3         4
        //    0123456789012345678901234567890123456789012345
        //    +-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh
        //    (some encoders use [space] instead of +)
        //
        // Without external information to indicate the actual encoding, if we
        // get input whose encoded byte length is encoded as one of the following
        // characters:
        //
        //    -0123456789ABCDEFGHIJKLM
        //
        // We will not know if it is actually UUE or XXE.  About all we could do
        // is calculate the expected input length in both encodings based on the
        // starting character and then check if the input actually matches one
        // of them...

        LDecoder := TIdDecoderUUE.Create(nil);
        LDecoder.DecodeBegin(ADestStream);
        FCodingType := 'UUE'; {do not localize}
      end;
      'h': begin  {do not localize}
        LDecoder := TIdDecoderXXE.Create(nil);
        LDecoder.DecodeBegin(ADestStream);
        FCodingType := 'XXE'; {do not localize}
      end;
      else begin
        raise EIdException.Create(RSUnrecognizedUUEEncodingScheme);
      end;
    end;
  end;
  try
    if Assigned(LDecoder) then
    begin
      LFillCharStr := String(LDecoder.FillChar);
      repeat
        if (Length(Trim(LLine)) = 0) or (LLine = LFillCharStr) then begin
          // UUE: Comes on the line before end. Supposed to be `, but some put a
          // blank line instead
        end else begin
          LDecoder.Decode(LLine);
        end;
        LLine := ReadLnRFC(LMsgEnd, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
      until TextIsSame(Trim(LLine), 'end') or LMsgEnd;    {Do not Localize}
      LDecoder.DecodeEnd;
    end;
  finally
    FreeAndNil(LDecoder);
  end;
end;

{ TIdMessageEncoderInfoUUE }

constructor TIdMessageEncoderInfoUUE.Create;
begin
  inherited;
  FMessageEncoderClass := TIdMessageEncoderUUE;
end;

{ TIdMessageEncoderUUEBase }

procedure TIdMessageEncoderUUEBase.Encode(ASrc: TStream; ADest: TStream);
var
  LEncoder: TIdEncoder3to4;
begin
  ASrc.Position := 0;
  WriteStringToStream(ADest, 'begin ' + IntToStr(PermissionCode) + ' ' + Filename + EOL); {Do not Localize}
  LEncoder := FEncoderClass.Create(nil); try
    while ASrc.Position < ASrc.Size do begin
      LEncoder.Encode(ASrc, ADest, 45);
      WriteStringToStream(ADest, EOL);
    end;
    WriteStringToStream(ADest, String(LEncoder.FillChar) + EOL + 'end' + EOL); {Do not Localize}
  finally FreeAndNil(LEncoder); end;
end;

{ TIdMessageEncoderUUE }

procedure TIdMessageEncoderUUE.InitComponent;
begin
  inherited;
  FEncoderClass := TIdEncoderUUE;
end;

initialization
  TIdMessageDecoderList.RegisterDecoder('UUE', TIdMessageDecoderInfoUUE.Create);    {Do not Localize}
  TIdMessageEncoderList.RegisterEncoder('UUE', TIdMessageEncoderInfoUUE.Create);    {Do not Localize}
end.
