unit PostCont;
{ Class to parse the data from a web server's QUERY_STRING variables and the
stdin data during a POST. The .Create method used determines how this
loads the data.
by Dave Wedwick
}
interface
uses SysUtils, Classes;
type
EPostContentError = class(Exception);
TPostContent = class
private
FList: TList;
function GetValue(Index: Integer): String;
function GetKey(Index: Integer): String;
function GetCount: Word;
procedure ParseStream(MemStr: TMemoryStream);
procedure FreeItems;
public
constructor Create(ContentLen: Integer);
constructor CreateFromString(Str: String);
destructor Destroy; override;
property Value[Index: Integer]: String read GetValue;
property Key[Index: Integer]: String read GetKey;
property Count: Word read GetCount;
function ValueForKey(Key: String; Occurance: Word): String;
end;
implementation
type
PValueRec = ^ValueRec;
ValueRec = record
Name: String[100];
Value: String[200];
end;
{ Support functions }
function StrToHex(const HexVal: String): Char;
const
StartingLetter = ord('A');
StartingNumber = ord('0');
var
Val, Counter: Byte;
begin
{ Find the hex value of the passed two byte string }
Val := 0;
for Counter := 1 to 2 do begin
if Counter = 2 then
Val := Val shl 4;
case HexVal[Counter] of
'0'..'9': Val := Val + (ord(HexVal[Counter]) - StartingNumber);
'A'..'F': Val := Val + (ord(HexVal[Counter]) - StartingLetter + 10);
end;
end;
Result := Char(Val);
end;
{ Class methods }
constructor TPostContent.Create(ContentLen: Integer);
var
MemStr: TMemoryStream;
Counter: Word;
NextChar: Char;
begin
FList := TList.Create;
MemStr := TMemoryStream.Create;
Counter := 1;
while Counter <= ContentLen do begin
Read(NextChar);
MemStr.Write(NextChar, 1);
{ Add one to the count }
Inc(Counter);
end;
ParseStream(MemStr);
MemStr.Free;
end;
constructor TPostContent.CreateFromString(Str: String);
{ This creates the value pairs by parsing out a string, rather than
reading from stdin. Used with the QUERY_STRING. }
var
MemStr: TMemoryStream;
StartPos: Word;
begin
FList := TList.Create;
MemStr := TMemoryStream.Create;
{ The query data starts after the ? in the query. If none is found, start
at position 1. Convenient, since Pos returns 0 if not found. }
StartPos := Pos('?', Str) + 1;
MemStr.Write(Str[StartPos], Length(Str)-StartPos+1);
ParseStream(MemStr);
MemStr.Free;
end;
destructor TPostContent.Destroy;
begin
FreeItems; { See below }
FList.Free;
inherited;
end;
procedure TPostContent.ParseStream(MemStr: TMemoryStream);
type
InType = (itName, itValue);
var
VRecPtr: PValueRec;
NextChar: Char;
Counter: Word;
CurrType: InType;
VRec: ValueRec;
HexVal: String[2];
begin
Counter := 1;
CurrType := itName;
{ Clear the structure to where the value are going to go }
VRec.Name := '';
VRec.Value := '';
MemStr.Seek(0, soFromBeginning);
while Counter <= MemStr.Size do begin
{ Get the next character from the stream }
MemStr.Read(NextChar, 1);
{ Plus signs are spaces }
if NextChar = '+' then
NextChar := ' ';
case NextChar of
'=': CurrType := itValue;
'%':
begin
{ The next two bytes are a hex value for an ASCII character. Decode
the character, add it to the appropriate place, and increment
the counter by three}
HexVal := '';
MemStr.Read(NextChar, 1); HexVal := HexVal + NextChar;
MemStr.Read(NextChar, 1); HexVal := HexVal + NextChar;
NextChar := StrToHex(HexVal);
if CurrType = itName then
VRec.Name := VRec.Name + NextChar
else
VRec.Value := VRec.Value + NextChar;
{ Add two to the counter here -- there is one more added below
at the bottom of the loop, making a total of three added }
Inc(Counter, 2);
end;
'&':
begin
{ Finished with this variable name/value pair. Allocate memory
and add it to the list }
New(VRecPtr);
VRecPtr^ := VRec;
FList.Add(VRecPtr);
{ Get ready for the next values }
CurrType := itName;
VRec.Name := '';
VRec.Value := '';
end;
else
with VRec do begin
if CurrType = itName then
Name := Name + NextChar
else
Value := Value + NextChar;
end;
end;
{ Add one to the count }
Inc(Counter);
end;
{ Add the last one }
if MemStr.Size > 0 then begin
New(VRecPtr);
VRecPtr^ := VRec;
FList.Add(VRecPtr);
end;
end;
procedure TPostContent.FreeItems;
var
Counter: Word;
begin
{ Free all items in the list }
for Counter := 1 to FList.Count do
Dispose(PValueRec(FList[Counter-1]));
end;
function TPostContent.GetValue(Index: Integer): String;
begin
if Index < 0 then
raise EPostContentError.Create('Can''t have negative numbers');
if Index > FList.Count-1 then
raise EPostContentError.Create('Index value too high');
Result := PValueRec(FList[Index])^.Value;
end;
function TPostContent.GetKey(Index: Integer): String;
begin
if Index < 0 then
raise EPostContentError.Create('Can''t have negative numbers');
if Index > FList.Count-1 then
raise EPostContentError.Create('Index value too high');
Result := PValueRec(FList[Index])^.Name;
end;
function TPostContent.GetCount: Word;
begin
Result := FList.Count;
end;
function TPostContent.ValueForKey(Key: String; Occurance: Word): String;
var
Counter, HitCount: Word;
begin
{ Find the Occurance of Key in the list }
if Occurance < 1 then
raise EPostContentError.Create('Occurance value must be > 0');
Result := '';
HitCount := 0;
for Counter := 1 to FList.Count do begin
with PValueRec(FList[Counter-1])^ do begin
{ If the key passed matches the name of the value, and the occurance
is found, return the value }
if UpperCase(Key) = UpperCase(Name) then begin
Inc(HitCount);
if HitCount = Occurance then begin
Result := Value;
Exit;
end;
end;
end;
end;
{ If we're here, then we didn't find the value for the key -- raise
an exception }
raise EPostContentError.Create('Key not found');
end;
end.