* ltelnet subcommand first character improvements. Mantis #27273

git-svn-id: trunk@29466 -
This commit is contained in:
marco 2015-01-13 14:51:32 +00:00
parent b21d30b3aa
commit d47aae933c
2 changed files with 113 additions and 33 deletions

View File

@ -37,15 +37,18 @@ type
private private
FItems: array of Char; FItems: array of Char;
FIndex: Byte; FIndex: Byte;
FAllowInflation: Boolean;
FOnFull: TLOnFull; FOnFull: TLOnFull;
function GetFull: Boolean; function GetFull: Boolean;
function GetItem(const i: Byte): Char; function GetItem(const i: Byte): Char;
procedure SetItem(const i: Byte; const Value: Char); procedure SetItem(const i: Byte; const Value: Char);
procedure SetAllowInflation(const b: boolean);
public public
constructor Create; constructor Create;
procedure Clear; procedure Clear;
procedure Push(const Value: Char); procedure Push(const Value: Char);
property ItemIndex: Byte read FIndex; property ItemIndex: Byte read FIndex;
property AllowInflation: Boolean read FAllowInflation write SetAllowInflation;
property Items[i: Byte]: Char read GetItem write SetItem; default; property Items[i: Byte]: Char read GetItem write SetItem; default;
property Full: Boolean read GetFull; property Full: Boolean read GetFull;
property OnFull: TLOnFull read FOnFull write FOnFull; property OnFull: TLOnFull read FOnFull write FOnFull;
@ -56,46 +59,79 @@ implementation
uses uses
lTelnet; lTelnet;
(* The normal situation is that there are up to TL_CSLENGTH items on the stack. *)
(* However this may be relaxed in cases (assumed to be rare) where subcommand *)
(* parameters are being accumulated. *)
constructor TLControlStack.Create; constructor TLControlStack.Create;
begin begin
FOnFull:=nil; FOnFull:=nil;
FIndex:=0; FIndex:=0; (* Next insertion point, [0] when empty *)
FAllowInflation := false;
SetLength(FItems, TL_CSLENGTH); SetLength(FItems, TL_CSLENGTH);
end; end;
function TLControlStack.GetFull: Boolean; function TLControlStack.GetFull: Boolean;
begin begin
Result:=False; Result:=False; (* It's full when it has a complete *)
if FIndex >= TL_CSLENGTH then if FIndex >= TL_CSLENGTH then (* command, irrespective of whether the *)
Result:=True; Result:=True; (* stack's inflated by a subcommand. *)
end; end;
function TLControlStack.GetItem(const i: Byte): Char; function TLControlStack.GetItem(const i: Byte): Char;
begin begin
Result:=TS_NOP; Result:=TS_NOP;
if i < TL_CSLENGTH then if not FAllowInflation then begin
Result:=FItems[i]; if i < TL_CSLENGTH then
Result:=FItems[i]
end else
if i < Length(FItems) then
Result:=FItems[i]
end; end;
procedure TLControlStack.SetItem(const i: Byte; const Value: Char); procedure TLControlStack.SetItem(const i: Byte; const Value: Char);
begin begin
if i < TL_CSLENGTH then if not FAllowInflation then begin
FItems[i]:=Value; if i < TL_CSLENGTH then
FItems[i]:=Value
end else begin
while i >= Length(FItems) do begin
SetLength(FItems, Length(FItems) + 1);
FItems[Length(FItems) - 1] := TS_NOP
end;
FItems[i] := Value
end
end;
procedure TLControlStack.SetAllowInflation(const b: boolean);
begin
FAllowInflation := b;
if not b then (* No more funny stuff please *)
Clear
end; end;
procedure TLControlStack.Clear; procedure TLControlStack.Clear;
begin begin
FIndex:=0; FIndex:=0;
FAllowInflation := false;
SetLength(FItems, TL_CSLENGTH) (* In case inflation was allowed *)
end; end;
procedure TLControlStack.Push(const Value: Char); procedure TLControlStack.Push(const Value: Char);
begin begin
if FIndex < TL_CSLENGTH then begin if not FAllowInflation then
FItems[FIndex]:=Value; if FIndex < TL_CSLENGTH then begin
Inc(FIndex); FItems[FIndex]:=Value;
if Full and Assigned(FOnFull) then Inc(FIndex)
FOnFull; end else begin end
else begin
SetLength(FItems, Length(FItems) + 1);
FItems[Length(FItems) - 1] := Value;
FIndex := Length(FItems)
end; end;
if Full and Assigned(FOnFull) then
FOnFull;
end; end;
end. end.

View File

@ -27,7 +27,7 @@ unit lTelnet;
interface interface
uses uses
Classes, lNet, lControlStack; Classes, SysUtils, lNet, lControlStack;
const const
// Telnet printer signals // Telnet printer signals
@ -72,9 +72,11 @@ type
TLSubcommandCallback= function(command: char; const parameters, defaultResponse: string): string; TLSubcommandCallback= function(command: char; const parameters, defaultResponse: string): string;
TLSubcommandEntry= record TLSubcommandEntry= record
callback: TLSubcommandCallback; callback: TLSubcommandCallback;
defaultResponse: string defaultResponse: string;
requiredParams: integer
end; end;
TLSubcommandArray= array[#$00..#$ff] of TLSubcommandEntry; TLSubcommandArray= array[#$00..#$ff] of TLSubcommandEntry;
EInsufficientSubcommandParameters= class(Exception);
{ TLTelnet } { TLTelnet }
@ -117,7 +119,7 @@ type
procedure StackFull; procedure StackFull;
procedure DoubleIAC(var s: string); procedure DoubleIAC(var s: string);
function TelnetParse(const msg: string): Integer; function TelnetParse(const msg: string): Integer;
procedure React(const Operation, Command: Char); virtual; abstract; function React(const Operation, Command: Char): boolean; virtual; abstract;
procedure SendCommand(const Command: Char; const Value: Boolean); virtual; abstract; procedure SendCommand(const Command: Char; const Value: Boolean); virtual; abstract;
procedure OnCs(aSocket: TLSocket); procedure OnCs(aSocket: TLSocket);
@ -136,7 +138,8 @@ type
procedure SetOption(const Option: Char); procedure SetOption(const Option: Char);
procedure UnSetOption(const Option: Char); procedure UnSetOption(const Option: Char);
function RegisterSubcommand(aOption: char; callback: TLSubcommandCallback; const defaultResponse: string= ''): boolean; function RegisterSubcommand(aOption: char; callback: TLSubcommandCallback;
const defaultResponse: string= ''; requiredParams: integer= 0): boolean;
procedure Disconnect(const Forced: Boolean = True); override; procedure Disconnect(const Forced: Boolean = True); override;
@ -164,7 +167,7 @@ type
procedure OnRe(aSocket: TLSocket); procedure OnRe(aSocket: TLSocket);
procedure OnCo(aSocket: TLSocket); procedure OnCo(aSocket: TLSocket);
procedure React(const Operation, Command: Char); override; function React(const Operation, Command: Char): boolean; override;
procedure SendCommand(const Command: Char; const Value: Boolean); override; procedure SendCommand(const Command: Char; const Value: Boolean); override;
public public
@ -190,7 +193,9 @@ function LTelnetSubcommandCallback(command: char; const parameters, defaultRespo
implementation implementation
uses uses
SysUtils, Math; Math;
const subcommandEndLength= 2;
var var
zz: Char; zz: Char;
@ -306,8 +311,10 @@ begin
begin begin
FOutput.WriteByte(Byte(FStack[1])); FOutput.WriteByte(Byte(FStack[1]));
FOutput.WriteByte(Byte(FStack[2])); FOutput.WriteByte(Byte(FStack[2]));
end else React(FStack[1], FStack[2]); FStack.Clear
FStack.Clear; end else
if React(FStack[1], FStack[2]) then
FStack.Clear
end; end;
procedure TLTelnet.DoubleIAC(var s: string); procedure TLTelnet.DoubleIAC(var s: string);
@ -394,15 +401,22 @@ end;
(* If already set, the callback can be reverted to nil but it can't be changed *) (* If already set, the callback can be reverted to nil but it can't be changed *)
(* in a single step. The default response, if specified, is used by the *) (* in a single step. The default response, if specified, is used by the *)
(* LTelnetSubcommandCallback() function and is available to others. *) (* LTelnetSubcommandCallback() function and is available to others; the *)
(* callback will not be invoked until there is at least the indicated number of *)
(* parameter bytes available. *)
// //
function TLTelnet.RegisterSubcommand(aOption: char; callback: TLSubcommandCallback; const defaultResponse: string= ''): boolean; function TLTelnet.RegisterSubcommand(aOption: char; callback: TLSubcommandCallback;
const defaultResponse: string= ''; requiredParams: integer= 0): boolean;
begin begin
result := (not Assigned(FSubcommandCallbacks[aOption].callback)) or (@callback = nil); result := (not Assigned(FSubcommandCallbacks[aOption].callback)) or (@callback = nil);
if result then begin if result then begin
FSubcommandCallbacks[aOption].callback := callback; FSubcommandCallbacks[aOption].callback := callback;
FSubcommandCallbacks[aOption].defaultResponse := defaultResponse FSubcommandCallbacks[aOption].defaultResponse := defaultResponse;
Inc(requiredParams, subcommandEndLength);
if requiredParams < 0 then (* Assume -subcommandEndLength is a *)
requiredParams := 0; (* valid parameter. *)
FSubcommandCallbacks[aOption].requiredParams := requiredParams;
end end
end { TLTelnet.RegisterSubcommand } ; end { TLTelnet.RegisterSubcommand } ;
@ -464,7 +478,7 @@ begin
FOnConnect(aSocket); FOnConnect(aSocket);
end; end;
procedure TLTelnetClient.React(const Operation, Command: Char); function TLTelnetClient.React(const Operation, Command: Char): boolean;
procedure Accept(const Operation, Command: Char); procedure Accept(const Operation, Command: Char);
begin begin
@ -487,17 +501,28 @@ procedure TLTelnetClient.React(const Operation, Command: Char);
end; end;
(* Retrieve the parameters from the current instance, and pass them explicitly *) (* Retrieve the parameters from the current instance, and pass them explicitly *)
(* to the callback. *) (* to the callback. Return false if there are insufficient parameters on the *)
(* stack. *)
// //
procedure subcommand(command: char); function subcommand(command: char): boolean;
var parameters, response: string; var parameters, response: string;
i: integer; i: integer;
begin begin
if FStack.ItemIndex > 5 then begin FStack.AllowInflation := true; (* We might need more than the standard *)
SetLength(parameters, FStack.ItemIndex - 5); if FStack.ItemIndex > 65536 then (* command, but protect against parse *)
Move(FStack[3], parameters[1], FStack.ItemIndex - 5); {%H- 6018 } exit(true); (* failure which could be a DoS attack. *)
i := FStack.ItemIndex - TL_CSLENGTH; (* Number of parameter bytes available.*)
if i < FSubcommandCallbacks[command].requiredParams then
exit(false); (* Early insufficient-parameters decision *)
result := true;
if FStack.ItemIndex > TL_CSLENGTH then begin
SetLength(parameters, FStack.ItemIndex - TL_CSLENGTH );
Move(FStack[3], parameters[1], FStack.ItemIndex - TL_CSLENGTH );
if (Length(parameters) >= 2) and (parameters[Length(parameters)] = TS_IAC) and
(parameters[Length(parameters) - 1] <> TS_IAC) then
exit(false); (* Special case: need at least one more *)
i := 1; i := 1;
while i <= Length(parameters) - 1 do (* Undouble IACs *) while i <= Length(parameters) - 1 do (* Undouble IACs *)
if (parameters[i] = TS_IAC) and (parameters[i + 1] = TS_IAC) then if (parameters[i] = TS_IAC) and (parameters[i + 1] = TS_IAC) then
@ -506,13 +531,27 @@ procedure TLTelnetClient.React(const Operation, Command: Char);
Inc(i) Inc(i)
end else end else
parameters := ''; parameters := '';
response := FSubcommandCallbacks[command].callback(command, parameters, FSubcommandCallbacks[command].defaultResponse); if Length(parameters) < FSubcommandCallbacks[command].requiredParams then
exit(false); (* Insufficient params after IAC undouble *)
if (FSubcommandCallbacks[command].requiredParams >= subcommandEndLength) and
(Length(parameters) >= subcommandEndLength) then
SetLength(parameters, Length(parameters) - subcommandEndLength);
try
response := FSubcommandCallbacks[command].callback(command, parameters,
FSubcommandCallbacks[command].defaultResponse)
except
on e: EInsufficientSubcommandParameters do
Exit(false) (* Late insufficient-parameters decision *)
else
Raise (* Application-specific error *)
end;
DoubleIAC(response); DoubleIAC(response);
AddToBuffer(TS_IAC + TS_SB + command + response + TS_IAC + TS_SE); AddToBuffer(TS_IAC + TS_SB + command + response + TS_IAC + TS_SE);
OnCs(nil) OnCs(nil)
end { subcommand } ; end { subcommand } ;
begin begin
result := true; (* Stack will normally be cleared *)
{$ifdef debug} {$ifdef debug}
Writeln('**GOT** ', TNames[Operation], ' ', TNames[Command]); Writeln('**GOT** ', TNames[Operation], ' ', TNames[Command]);
{$endif} {$endif}
@ -529,7 +568,12 @@ begin
TS_SB : if not Assigned(FSubcommandCallbacks[command].callback) then TS_SB : if not Assigned(FSubcommandCallbacks[command].callback) then
refuse(TS_WONT, command) refuse(TS_WONT, command)
else else
subcommand(command) result := subcommand(command)
(* In the final case above, the stack will not be cleared if sufficient *)
(* parameters to keep the subcommand happy have not yet been parsed out of the *)
(* message. *)
end; end;
end; end;
@ -559,7 +603,7 @@ end;
function TLTelnetClient.Get(out aData; const aSize: Integer; aSocket: TLSocket): Integer; function TLTelnetClient.Get(out aData; const aSize: Integer; aSocket: TLSocket): Integer;
begin begin
Result := FOutput.Read(aData, aSize); Result := FOutput.Read(aData {%H- 5058 } , aSize);
if FOutput.Position = FOutput.Size then if FOutput.Position = FOutput.Size then
FOutput.Clear; FOutput.Clear;
end; end;