611 lines
16 KiB
Plaintext
611 lines
16 KiB
Plaintext
{
|
|
$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.0 15/04/2005 7:25:02 AM GGrieve
|
|
first ported to INdy
|
|
}
|
|
|
|
unit IdASN1Coder;
|
|
|
|
interface
|
|
|
|
{$i IdCompilerDefines.inc}
|
|
|
|
uses
|
|
Classes,
|
|
Contnrs;
|
|
|
|
type
|
|
|
|
TIdASN1IdentifierType = (aitUnknown, aitSequence, aitBoolean, aitInteger, aitEnum, aitString, aitOID, aitReal);
|
|
|
|
TIdASN1IdentifierClass = (aicUniversal, aicApplication, aicContextSpecific, aicPrivate);
|
|
|
|
TIdASN1Identifier = record
|
|
Position : Integer;
|
|
IdClass : TIdASN1IdentifierClass;
|
|
Constructed : Boolean;
|
|
TagValue : Integer;
|
|
TagType : TIdASN1IdentifierType;
|
|
ContentLength : integer;
|
|
end;
|
|
|
|
TIdASN1Sequence = Class
|
|
Private
|
|
FIdClass : TIdASN1IdentifierClass;
|
|
FTag : Integer;
|
|
FContents : String;
|
|
Public
|
|
Property IdClass : TIdASN1IdentifierClass Read FIdClass Write FIdClass;
|
|
Property Tag : integer Read FTag Write FTag;
|
|
Property Contents : String Read FContents Write FContents;
|
|
End;
|
|
|
|
TIdASN1Sequences = Class(TObjectList)
|
|
Private
|
|
Function GetElement(Const iIndex : Integer) : TIdASN1Sequence;
|
|
function GetLast: TIdASN1Sequence;
|
|
Public
|
|
Property LastElement : TIdASN1Sequence read GetLast;
|
|
procedure Pop;
|
|
Property Elements[Const iIndex : Integer] : TIdASN1Sequence Read GetElement; Default;
|
|
End;
|
|
|
|
TIdASN1Encoder = class
|
|
private
|
|
FSequences : TIdASN1Sequences;
|
|
FReadyToWrite : Boolean;
|
|
function FormatEncoding(aClass : TIdASN1IdentifierClass; bConstructed : Boolean; iTag : integer; const sContent : String) : String;
|
|
procedure AddEncoding(const sContent : String);
|
|
|
|
procedure WriteInt(iTag : integer; iValue : integer);
|
|
function EncodeLength(iLen : Integer):String;
|
|
protected
|
|
// must call this as an outer wrapper
|
|
Procedure StartWriting;
|
|
Procedure StopWriting;
|
|
|
|
// sequences
|
|
procedure StartSequence; overload;
|
|
procedure StartSequence(iTag : Integer); overload;
|
|
procedure StartSequence(aClass : TIdASN1IdentifierClass; iTag : Integer); overload;
|
|
procedure StopSequence;
|
|
|
|
// primitives
|
|
procedure WriteBoolean(bValue : Boolean);
|
|
procedure WriteInteger(iValue : Integer);
|
|
procedure WriteEnum(iValue : Integer);
|
|
procedure WriteString(sValue : String); overload;
|
|
procedure WriteString(iTag : integer; sValue : String); overload;
|
|
public
|
|
Constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
procedure WriteToStream(Stream : TStream);
|
|
end;
|
|
|
|
TIntegerList = class (TList)
|
|
private
|
|
function GetValue(iIndex: integer): Integer;
|
|
procedure SetValue(Index: integer; const Value: Integer);
|
|
public
|
|
procedure AddInt(value : integer);
|
|
procedure InsertInt(Index, Value : integer);
|
|
property Value[iIndex : integer]:Integer read GetValue write SetValue; default;
|
|
end;
|
|
|
|
TIdASN1Decoder = class
|
|
private
|
|
FLengths : TIntegerList;
|
|
FPosition : Integer;
|
|
FNextHeader : TIdASN1Identifier;
|
|
FNextHeaderUsed : Boolean;
|
|
FStream: TStream;
|
|
function ReadHeader : TIdASN1Identifier; // -1 in length means that no definite length was specified
|
|
function DescribeIdentifier(const aId : TIdASN1Identifier) : String;
|
|
Function ReadByte : Byte;
|
|
function ReadChar : Char;
|
|
function ReadContentLength : Integer;
|
|
protected
|
|
procedure Check(bCondition : Boolean; const sMethod, sMessage : String); overload; virtual;
|
|
|
|
// must call this as an outer wrapper
|
|
Procedure StartReading;
|
|
Procedure StopReading;
|
|
|
|
// sequences and choices
|
|
procedure ReadSequenceBegin;
|
|
function SequenceEnded : Boolean;
|
|
procedure ReadSequenceEnd;
|
|
function NextTag : integer;
|
|
function NextTagType : TIdASN1IdentifierType;
|
|
|
|
// primitives
|
|
function ReadBoolean : Boolean;
|
|
Function ReadInteger : Integer;
|
|
function ReadEnum : Integer;
|
|
Function ReadString : String;
|
|
|
|
public
|
|
Constructor Create;
|
|
destructor Destroy; override;
|
|
property Stream : TStream read FStream write FStream;
|
|
end;
|
|
|
|
|
|
const
|
|
NAMES_ASN1IDENTIFIERTYPE : array [TIdASN1IdentifierType] of String = ('Unknown', 'Sequence', 'Boolean', 'Integer', 'Enum', 'String', 'OID', 'Real');
|
|
TAGS_ASN1IDENTIFIERTYPE : array [TIdASN1IdentifierType] of Integer = (0, $10, $01, $02, $0A, $04, $06, 0 {?});
|
|
NAMES_ASN1IDENTIFIERCLASS : array [TIdASN1IdentifierClass] of String = ('Universal', 'Application', 'ContextSpecific', 'Private');
|
|
|
|
function ToIdentifierType(iTag : integer) : TIdASN1IdentifierType;
|
|
|
|
implementation
|
|
|
|
uses
|
|
IdGlobal, IdException, SysUtils;
|
|
|
|
function ToIdentifierType(iTag : integer) : TIdASN1IdentifierType;
|
|
begin
|
|
case iTag of
|
|
$10 : result := aitSequence;
|
|
$01 : result := aitBoolean;
|
|
$02 : result := aitInteger;
|
|
$04 : result := aitString;
|
|
$06 : result := aitOID;
|
|
$0A : result := aitEnum;
|
|
else
|
|
result := aitUnknown;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TIdASN1Encoder }
|
|
|
|
constructor TIdASN1Encoder.Create;
|
|
begin
|
|
inherited Create;
|
|
FSequences := TIdASN1Sequences.create;
|
|
end;
|
|
|
|
destructor TIdASN1Encoder.Destroy;
|
|
begin
|
|
FSequences.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.WriteToStream(Stream : TStream);
|
|
begin
|
|
Assert(FReadyToWrite, 'not ready to write');
|
|
if Length(FSequences[0].Contents) <> 0 then
|
|
WriteStringToStream(Stream, FSequences[0].Contents, IndyTextEncoding_8Bit);
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.StartWriting;
|
|
begin
|
|
FSequences.Clear;
|
|
StartSequence(aicUniversal, 0);
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.StopWriting;
|
|
begin
|
|
assert(FSequences.Count = 1, 'Writing left an open Sequence');
|
|
FReadyToWrite := true;
|
|
// todo - actually commit to stream Produce(Fsequences[0].Contents);
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.StartSequence(aClass: TIdASN1IdentifierClass; iTag: Integer);
|
|
var
|
|
oSequence : TIdASN1Sequence;
|
|
begin
|
|
oSequence := TIdASN1Sequence.create;
|
|
try
|
|
oSequence.IdClass := aClass;
|
|
oSequence.Tag := iTag;
|
|
oSequence.Contents := '';
|
|
FSequences.add(oSequence);
|
|
finally
|
|
oSequence.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.StartSequence(iTag: Integer);
|
|
begin
|
|
if iTag = -1 then
|
|
StartSequence(aicUniversal, TAGS_ASN1IDENTIFIERTYPE[aitSequence])
|
|
else
|
|
StartSequence(aicApplication, iTag);
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.StartSequence;
|
|
begin
|
|
StartSequence(aicUniversal, TAGS_ASN1IDENTIFIERTYPE[aitSequence]);
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.StopSequence;
|
|
var
|
|
sSequence : String;
|
|
begin
|
|
sSequence := FormatEncoding(FSequences.LastElement.IdClass, true, FSequences.LastElement.Tag, FSequences.LastElement.Contents);
|
|
FSequences.Pop;
|
|
AddEncoding(sSequence);
|
|
end;
|
|
|
|
|
|
procedure TIdASN1Encoder.WriteBoolean(bValue: Boolean);
|
|
begin
|
|
// RLebeau 1/7/09: using Char() for #128-#255 because in D2009, the compiler
|
|
// may change characters >= #128 from their Ansi codepage value to their true
|
|
// Unicode codepoint value, depending on the codepage used for the source code.
|
|
// For instance, #128 may become #$20AC...
|
|
if bValue then
|
|
AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitBoolean], Char($FF)))
|
|
else
|
|
AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitBoolean], #$00));
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.WriteEnum(iValue: Integer);
|
|
begin
|
|
WriteInt(TAGS_ASN1IDENTIFIERTYPE[aitEnum], iValue);
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.WriteInteger(iValue: Integer);
|
|
begin
|
|
WriteInt(TAGS_ASN1IDENTIFIERTYPE[aitInteger], iValue);
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.WriteInt(iTag, iValue: integer);
|
|
var
|
|
sValue : String;
|
|
x, y: Cardinal;
|
|
bNeg: Boolean;
|
|
begin
|
|
bNeg := iValue < 0;
|
|
x := Abs(iValue);
|
|
if bNeg then
|
|
x := not (x - 1);
|
|
sValue := ''; {Do not Localize}
|
|
repeat
|
|
y := x mod 256;
|
|
x := x div 256;
|
|
sValue := Char(y) + sValue;
|
|
until x = 0;
|
|
if (not bNeg) and (sValue[1] > #$7F) then
|
|
sValue := #0 + sValue;
|
|
|
|
AddEncoding(FormatEncoding(aicUniversal, False, iTag, sValue))
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.WriteString(sValue: String);
|
|
begin
|
|
AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitString], sValue))
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.WriteString(iTag : integer; sValue: String);
|
|
begin
|
|
AddEncoding(FormatEncoding(aicContextSpecific, False, iTag, sValue))
|
|
end;
|
|
|
|
procedure TIdASN1Encoder.AddEncoding(const sContent: String);
|
|
begin
|
|
FSequences.LastElement.Contents := FSequences.LastElement.Contents + sContent;
|
|
end;
|
|
|
|
function TIdASN1Encoder.FormatEncoding(aClass: TIdASN1IdentifierClass; bConstructed : Boolean; iTag: integer; const sContent: String): String;
|
|
begin
|
|
if bConstructed then
|
|
result := chr((ord(aClass) shl 6) or $20 or iTag) + EncodeLength(length(sContent)) + sContent
|
|
else
|
|
result := chr((ord(aClass) shl 6) or iTag) + EncodeLength(length(sContent)) + sContent;
|
|
end;
|
|
|
|
function TIdASN1Encoder.EncodeLength(iLen: Integer): String;
|
|
var
|
|
x, y: Integer;
|
|
begin
|
|
if iLen < $80 then
|
|
Result := Char(iLen)
|
|
else
|
|
begin
|
|
x := iLen;
|
|
Result := '';
|
|
repeat
|
|
y := x mod 256;
|
|
x := x div 256;
|
|
Result := Char(y) + Result;
|
|
until x = 0;
|
|
y := Length(Result);
|
|
y := y or $80;
|
|
Result := Char(y) + Result;
|
|
end;
|
|
end;
|
|
|
|
{ TIdASN1Sequences }
|
|
|
|
function TIdASN1Sequences.GetElement(const iIndex: Integer): TIdASN1Sequence;
|
|
begin
|
|
result := TIdASN1Sequence(items[iIndex]);
|
|
end;
|
|
|
|
function TIdASN1Sequences.GetLast: TIdASN1Sequence;
|
|
begin
|
|
if Count = 0 then
|
|
result := nil
|
|
else
|
|
result := GetElement(Count - 1);
|
|
end;
|
|
|
|
procedure TIdASN1Sequences.Pop;
|
|
begin
|
|
if Count > 0 then
|
|
Delete(Count-1);
|
|
end;
|
|
|
|
{ TIdASN1Decoder }
|
|
|
|
Constructor TIdASN1Decoder.Create;
|
|
begin
|
|
inherited Create;
|
|
FLengths := TIntegerList.create;
|
|
end;
|
|
|
|
destructor TIdASN1Decoder.Destroy;
|
|
begin
|
|
FLengths.Free;
|
|
Inherited Destroy;
|
|
end;
|
|
|
|
procedure TIdASN1Decoder.Check(bCondition: Boolean; const sMethod, sMessage: String);
|
|
begin
|
|
if not bCondition then
|
|
raise EIdException.create(sMessage);
|
|
end;
|
|
|
|
Procedure TIdASN1Decoder.StartReading;
|
|
begin
|
|
FLengths.Clear;
|
|
FLengths.AddInt(-1);
|
|
FNextHeaderUsed := False;
|
|
FPosition := 0;
|
|
end;
|
|
|
|
Procedure TIdASN1Decoder.StopReading;
|
|
begin
|
|
Check(FLengths.Count = 1, 'StopReading', 'Reading was incomplete');
|
|
FLengths.Clear;
|
|
end;
|
|
|
|
function TIdASN1Decoder.DescribeIdentifier(const aId : TIdASN1Identifier) : String;
|
|
begin
|
|
result := '[Pos '+IntToStr(aId.Position)+', Type '+NAMES_ASN1IDENTIFIERTYPE[aId.TagType]+', '+
|
|
'Tag '+IntToStr(aId.TagValue)+', Class '+NAMES_ASN1IDENTIFIERCLASS[aId.IdClass]+']';
|
|
end;
|
|
|
|
Function TIdASN1Decoder.ReadByte : Byte;
|
|
begin
|
|
Check(FLengths[0] <> 0, 'ReadByte', 'Attempt to read past end of Sequence');
|
|
Stream.Read(result, 1);
|
|
inc(FPosition);
|
|
FLengths[0] := FLengths[0] - 1;
|
|
end;
|
|
|
|
function TIdASN1Decoder.ReadChar : Char;
|
|
begin
|
|
result := Chr(readByte);
|
|
end;
|
|
|
|
function TIdASN1Decoder.ReadContentLength: Integer;
|
|
var
|
|
iNext : Byte;
|
|
iLoop: Integer;
|
|
begin
|
|
iNext := ReadByte;
|
|
if iNext < $80 then
|
|
Result := iNext
|
|
else
|
|
begin
|
|
Result := 0;
|
|
iNext := iNext and $7F;
|
|
if iNext = 0 then
|
|
raise EIdException.create('Indefinite lengths are not yet handled');
|
|
for iLoop := 1 to iNext do
|
|
begin
|
|
Result := Result * 256;
|
|
iNext := ReadByte;
|
|
Result := Result + iNext;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TIdASN1Decoder.ReadHeader : TIdASN1Identifier;
|
|
var
|
|
iNext : Byte;
|
|
begin
|
|
if FNextHeaderUsed then
|
|
begin
|
|
result := FNextHeader;
|
|
FNextHeaderUsed := False;
|
|
end
|
|
else
|
|
begin
|
|
FillChar(result, sizeof(TIdASN1Identifier), #0);
|
|
result.Position := FPosition;
|
|
iNext := ReadByte;
|
|
result.Constructed := iNext and $20 > 0;
|
|
result.IdClass := TIdASN1IdentifierClass(iNext shr 6);
|
|
if iNext and $1F = $1F then
|
|
begin
|
|
raise EIdException.create('Todo');
|
|
end
|
|
else
|
|
result.TagValue := iNext and $1F;
|
|
result.TagType := ToIdentifierType(result.TagValue);
|
|
result.ContentLength := ReadContentLength;
|
|
end;
|
|
end;
|
|
|
|
function TIdASN1Decoder.NextTag: integer;
|
|
begin
|
|
if not FNextHeaderUsed then
|
|
begin
|
|
FNextHeader := ReadHeader;
|
|
FNextHeaderUsed := true;
|
|
end;
|
|
result := FNextHeader.TagValue;
|
|
end;
|
|
|
|
function TIdASN1Decoder.NextTagType: TIdASN1IdentifierType;
|
|
begin
|
|
if not FNextHeaderUsed then
|
|
begin
|
|
FNextHeader := ReadHeader;
|
|
FNextHeaderUsed := true;
|
|
end;
|
|
result := FNextHeader.TagType;
|
|
end;
|
|
|
|
function TIdASN1Decoder.ReadBoolean : Boolean;
|
|
var
|
|
aId : TIdASN1Identifier;
|
|
begin
|
|
aId := ReadHeader;
|
|
Check((aId.IdClass = aicApplication) or (aId.TagType = aitBoolean), 'ReadBoolean', 'Found '+DescribeIdentifier(aId)+' expecting a Boolean');
|
|
Check(aId.ContentLength = 1, 'ReadBoolean', 'Boolean Length should be 1');
|
|
result := ReadByte <> 0;
|
|
end;
|
|
|
|
Function TIdASN1Decoder.ReadInteger : Integer;
|
|
var
|
|
aId : TIdASN1Identifier;
|
|
iVal : Integer;
|
|
iNext : Byte;
|
|
bNeg : Boolean;
|
|
iLoop : integer;
|
|
begin
|
|
aId := ReadHeader;
|
|
Check((aId.IdClass = aicApplication) or (aId.TagType = aitInteger), 'ReadInteger', 'Found '+DescribeIdentifier(aId)+' expecting an Integer');
|
|
Check(aId.ContentLength >= 1, 'ReadInteger', 'Boolean Length should not be 0');
|
|
|
|
iVal := 0;
|
|
bNeg := False;
|
|
for iLoop := 1 to aId.ContentLength do
|
|
begin
|
|
iNext := ReadByte;
|
|
if (iLoop = 1) and (iNext > $7F) then
|
|
bNeg := True;
|
|
if bNeg then
|
|
iNext := not iNext;
|
|
iVal := iVal * 256 + iNext;
|
|
end;
|
|
if bNeg then
|
|
iVal := -(iVal + 1);
|
|
|
|
Result := iVal;
|
|
end;
|
|
|
|
function TIdASN1Decoder.ReadEnum : Integer;
|
|
var
|
|
aId : TIdASN1Identifier;
|
|
iVal : Integer;
|
|
iNext : Byte;
|
|
bNeg : Boolean;
|
|
iLoop : integer;
|
|
begin
|
|
aId := ReadHeader;
|
|
Check((aId.IdClass = aicApplication) or (aId.TagType = aitEnum), 'ReadEnum', 'Found '+DescribeIdentifier(aId)+' expecting an Enum');
|
|
Check(aId.ContentLength >= 1, 'ReadEnum', 'Boolean Length should not be 0');
|
|
|
|
iVal := 0;
|
|
bNeg := False;
|
|
for iLoop := 1 to aId.ContentLength do
|
|
begin
|
|
iNext := ReadByte;
|
|
if (iLoop = 1) and (iNext > $7F) then
|
|
bNeg := True;
|
|
if bNeg then
|
|
iNext := not iNext;
|
|
iVal := iVal * 256 + iNext;
|
|
end;
|
|
if bNeg then
|
|
iVal := -(iVal + 1);
|
|
|
|
Result := iVal;
|
|
end;
|
|
|
|
Function TIdASN1Decoder.ReadString : String;
|
|
var
|
|
aId : TIdASN1Identifier;
|
|
iLoop : integer;
|
|
begin
|
|
aId := ReadHeader;
|
|
Check((aId.IdClass = aicApplication) or (aId.TagType in [aitUnknown, aitString]), 'ReadString', 'Found '+DescribeIdentifier(aId)+' expecting a String');
|
|
SetLength(result, aId.ContentLength);
|
|
for iLoop := 1 to aId.ContentLength do
|
|
result[iLoop] := ReadChar;
|
|
end;
|
|
|
|
|
|
procedure TIdASN1Decoder.ReadSequenceBegin;
|
|
var
|
|
aId : TIdASN1Identifier;
|
|
begin
|
|
aId := ReadHeader;
|
|
Check((aId.IdClass = aicApplication) or (aId.TagType in [aitUnknown, aitSequence]), 'ReadSequenceBegin', 'Found '+DescribeIdentifier(aId)+' expecting a Sequence');
|
|
FLengths[0] := FLengths[0] - aId.ContentLength;
|
|
FLengths.InsertInt(0, aId.ContentLength);
|
|
end;
|
|
|
|
function TIdASN1Decoder.SequenceEnded: Boolean;
|
|
begin
|
|
Check(FLengths.Count > 1, 'SequenceEnded', 'Not in a Sequence');
|
|
result := FLengths[0] <= 0;
|
|
end;
|
|
|
|
procedure TIdASN1Decoder.ReadSequenceEnd;
|
|
begin
|
|
Check(SequenceEnded, 'ReadSequenceEnd', 'Sequence has not ended');
|
|
FLengths.Delete(0);
|
|
end;
|
|
|
|
{ TIntegerList }
|
|
|
|
procedure TIntegerList.AddInt(value: integer);
|
|
begin
|
|
Add(pointer(value));
|
|
end;
|
|
|
|
function TIntegerList.GetValue(iIndex: integer): Integer;
|
|
begin
|
|
result := integer(items[iIndex]);
|
|
end;
|
|
|
|
procedure TIntegerList.InsertInt(Index, Value: integer);
|
|
begin
|
|
insert(Index, pointer(value));
|
|
end;
|
|
|
|
procedure TIntegerList.SetValue(Index: integer; const Value: Integer);
|
|
begin
|
|
items[Index] := pointer(value);
|
|
end;
|
|
|
|
end.
|
|
|
|
|
|
|