Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2020-01

Parameter Checking in fpjsonrpc

5 messages · 2 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Simon Ameis

Hello all,

is it correct, that TJSONRPCHandler only checks for parameter beeing object or array (according to TJSONRPCHandler.Options) or should it also check for individual parameters according to ParamDefs?

For the latter I didn't find any implementation, so it is missing?

Regards
Simon

Michael Van Canneyt
to Simon Ameis
Simon Ameis wrote 7 lines
Hello all, is it correct, that TJSONRPCHandler only checks for parameter beeing object or array (according to TJSONRPCHandler.Options) or should it also check for individual parameters according to ParamDefs? For the latter I didn't find any implementation, so it is missing?

It is mising, but not forgotten; I have it on my todo list still, together with some other items for JSON-RPC
If you feel like implementing some rudimentary checks, please feel free.

Michael.

Simon Ameis
to Michael Van Canneyt
It is mising, but not forgotten; I have it on my todo list still, together with some other items for JSON-RPC If you feel like implementing some rudimentary checks, please feel free.

Let me see, what I can do next week.

At least some checks if a parameter object has the required parameters should be doable.

Maybe you could give me some hints on details:
- How should an array correspond to ParamDef? Ignore, trigger exception, if array of objects, check parameters on each object?
- What to do on additional parameters of an object? Silently ignore them or shall they raise an exception?

Simon

Michael Van Canneyt
to Simon Ameis
Simon Ameis wrote 13 lines
> It is mising, but not forgotten; I have it on my todo list still, > together > with some other items for JSON-RPC > If you feel like implementing some rudimentary checks, please feel free. Let me see, what I can do next week. At least some checks if a parameter object has the required parameters should be doable. Maybe you could give me some hints on details: - How should an array correspond to ParamDef? Ignore, trigger exception, if array of objects, check parameters on each object?

Not sure I understand what you mean. If the params are passed as an array, then the elements of the array should reflect the paramdefs

- What to do on additional parameters of an object? Silently ignore them or shall they raise an exception?

I would make this an additional option in the TJSONRPCOPTIONS enumerated: jsroIgnoreExtraFIelds

Michael.

Simon Ameis
to Michael Van Canneyt

Hello Michael,

as the bugtracker is still not reachable, I'll send you my patch via the mailing list.

As you suggested, there is a new option jroIgnoreExtraFields to ignore extra fields in the parameter objets.

Objects in arrays are now validated against ParamDefs the same way a single object is validated. You can force all array elements to be objects, if you include both jroObjectParams and jroArrayParams in options. If only jroArrayParams is given, the array may contain all JSON types but only objects insided a mixed array are validated against ParamDefs; simple types like numbers or strings are not checked.

Simon

----

 packages/fcl-web/src/jsonrpc/fpjsonrpc.pp | 121
++++++++++++++++++++++++++----
 1 file changed, 105 insertions(+), 16 deletions(-)

diff --git a/packages/fcl-web/src/jsonrpc/fpjsonrpc.pp
b/packages/fcl-web/src/jsonrpc/fpjsonrpc.pp
index 47d0ecf5e6..dd70e63ada 100644
--- a/packages/fcl-web/src/jsonrpc/fpjsonrpc.pp
+++ b/packages/fcl-web/src/jsonrpc/fpjsonrpc.pp
@@ -54,7 +54,7 @@ Type
     function GetP(AIndex : Integer): TJSONParamDef;
     procedure SetP(AIndex : Integer; const AValue: TJSONParamDef);
   Public
-    Function AddParamDef(Const AName : TJSONStringType; AType :
TJSONType = jtString) : TJSONParamDef;
+    Function AddParamDef(Const AName : TJSONStringType; AType :
TJSONType = jtString; ARequired: Boolean = False) : TJSONParamDef;
     Function IndexOfParamDef(Const AName : TJSONStringType) : Integer;
     Function FindParamDef(Const AName : TJSONStringType) : TJSONParamDef;
     Function ParamDefByName(Const AName : TJSONStringType) : TJSONParamDef;
@@ -63,7 +63,7 @@ Type
 
   { TCustomJSONRPCHandler }
   TJSONParamErrorEvent = Procedure (Sender : TObject; Const E :
Exception; Var Fatal : boolean) of Object;
-  TJSONRPCOption = (jroCheckParams,jroObjectParams,jroArrayParams);
+  TJSONRPCOption =
(jroCheckParams,jroObjectParams,jroArrayParams,jroIgnoreExtraFields);
   TJSONRPCOptions = set of TJSONRPCOption;
 
   { TJSONRPCCallContext }
@@ -94,6 +94,8 @@ Type
   Protected
     function CreateParamDefs: TJSONParamDefs; virtual;
     Procedure DoCheckParams(Const Params : TJSONData); virtual;
+    Procedure DoCheckParamDefsOnObject(Const ParamObject: TJSONObject);
virtual;
+    Procedure DoCheckParamArray(const ParamArray: TJSONArray); virtual;
     Function DoExecute(Const Params : TJSONData; AContext :
TJSONRPCCallContext): TJSONData; virtual;
     Property BeforeExecute : TNotifyEvent Read FBeforeExecute Write
FBeforeExecute;
     Property AfterExecute : TNotifyEvent Read FAfterExecute Write
FAfterExecute;
@@ -332,8 +334,10 @@ Type
   TJSONErrorObject = Class(TJSONObject);
 
 // Raise EJSONRPC exceptions.
-Procedure JSONRPCError(Msg : String);
-Procedure JSONRPCError(Fmt : String; Args : Array of const);
+Procedure JSONRPCError(const Msg : String);
+Procedure JSONRPCError(const Fmt : String; const Args : Array of const);
+Procedure JSONRPCParamError(const Msg: String);
+Procedure JSONRPCParamError(const Fmt: String; const Args: array of const);
 
 // Create an 'Error' object for an error response.
 function CreateJSONErrorObject(Const AMessage : String; Const ACode :
Integer) : TJSONObject;
@@ -371,6 +375,10 @@ resourcestring
   SErrParamsMustBeArrayorObject = 'Parameters must be passed in an
object or an array.';
   SErrParamsMustBeObject = 'Parameters must be passed in an object.';
   SErrParamsMustBeArray  = 'Parameters must be passed in an array.';
+  SErrParamsRequiredParamNotFound = 'Required parameter "%s" not found.';
+  SErrParamsDataTypeMismatch = 'Expected parameter "%s" having type
"%s", got "%s".';
+  SErrParamsNotAllowd = 'Parameter "%s" is not allowed.';
+  SErrParamsOnlyObjectsInArray = 'Array elements must be objects, got
%s at position %d.';
   SErrRequestMustBeObject = 'JSON-RPC Request must be an object.';
   SErrNoIDProperty = 'No "id" property found in request.';
   SErrInvalidIDProperty = 'Type of "id" property is not correct.';
@@ -402,13 +410,15 @@ implementation
 uses dbugintf;
 {$ENDIF}
 
-function CreateJSONErrorObject(Const AMessage : String; Const ACode :
Integer) : TJSONObject;
+function CreateJSONErrorObject(const AMessage: String; const ACode: Integer
+  ): TJSONObject;
 
 begin
   Result:=TJSONErrorObject.Create(['code',ACode,'message',AMessage])
 end;
 
-function CreateJSON2ErrorResponse(Const AMessage : String; Const ACode
: Integer; ID : TJSONData = Nil; idname : TJSONStringType = 'id' ) :
TJSONObject;
+function CreateJSON2ErrorResponse(const AMessage: String; const ACode:
Integer;
+  ID: TJSONData; idname: TJSONStringType): TJSONObject;
 
 begin
   If (ID=Nil) then
@@ -418,7 +428,8 @@ begin
  
Result:=TJSONErrorObject.Create(['jsonrpc','2.0','error',CreateJSONErrorObject(AMessage,ACode),idname,ID]);
 end;
 
-function CreateJSON2ErrorResponse(Const AFormat : String; Args : Array
of const; Const ACode : Integer; ID : TJSONData = Nil; idname :
TJSONStringType = 'id' ) : TJSONObject;
+function CreateJSON2ErrorResponse(const AFormat: String; Args: array of
const;
+  const ACode: Integer; ID: TJSONData; idname: TJSONStringType):
TJSONObject;
 
 begin
   If (ID=Nil) then
@@ -428,7 +439,7 @@ begin
  
Result:=TJSONErrorObject.Create(['jsonrpc','2.0','error',CreateJSONErrorObject(Format(AFormat,Args),ACode),idname,ID]);
 end;
 
-Function CreateErrorForRequest(Const Req,Error : TJSONData) : TJSONData;
+function CreateErrorForRequest(const Req, Error: TJSONData): TJSONData;
 
 Var
   I : Integer;
@@ -456,18 +467,29 @@ begin
   JSONRPCHandlerManager:=TheHandler;
 end;
 
-Procedure JSONRPCError(Msg : String);
+procedure JSONRPCError(const Msg: String);
 
 begin
   Raise EJSONRPC.Create(Msg);
 end;
 
-Procedure JSONRPCError(Fmt : String; Args : Array of const);
+procedure JSONRPCError(const Fmt: String; const Args: array of const);
 
 begin
   Raise EJSONRPC.CreateFmt(Fmt,Args);
 end;
 
+procedure JSONRPCParamError(const Msg: String);
+begin
+  raise EJSONRPC.CreateFmt(SErrParams, [Msg]);
+end;
+
+procedure JSONRPCParamError(const Fmt: String; const Args: array of const);
+begin
+  raise EJSONRPC.CreateFmt(SErrParams, [Format(Fmt, Args)]);
+end;
+
+
 { TJSONParamDef }
 
 procedure TJSONParamDef.SetName(const AValue: TJSONStringType);
@@ -529,13 +551,14 @@ begin
   Items[AIndex]:=AValue;
 end;
 
-function TJSONParamDefs.AddParamDef(const AName: TJSONStringType;
AType: TJSONType
-  ): TJSONParamDef;
+function TJSONParamDefs.AddParamDef(const AName: TJSONStringType;
+  AType: TJSONType; ARequired: Boolean): TJSONParamDef;
 begin
   Result:=Add as TJSONParamDef;
   try
     Result.Name:=AName;
     Result.DataType:=Atype;
+    Result.Required:=ARequired;
   except
     FReeAndNil(Result);
     Raise;
@@ -626,10 +649,76 @@ end;
 
 procedure TCustomJSONRPCHandler.DoCheckParams(const Params: TJSONData);
 begin
-  If (jroObjectParams in Options) and Not (Params is TJSONobject) then
-    JSONRPCError(SErrParams,[SErrParamsMustBeObject]);
-  If (jroArrayParams in Options) and Not (Params is TJSONArray) then
-    JSONRPCError(SErrParams,[SErrParamsMustBeArray]);
+  if (Params is TJSONObject) then
+  begin
+    if (jroArrayParams in Options) then
+      JSONRPCParamError(SErrParamsMustBeArray);
+
+    DoCheckParamDefsOnObject(Params as TJSONObject);
+  end else
+  if (Params is TJSONArray) then
+  begin
+    If (jroObjectParams in Options) then
+      JSONRPCParamError(SErrParamsMustBeArray);
+
+    DoCheckParamArray(Params as TJSONArray);
+  end;
+end;
+
+procedure TCustomJSONRPCHandler.DoCheckParamDefsOnObject(
+  const ParamObject: TJSONObject);
+var
+  def: TJSONParamDef;
+  Param: TJSONData;
+  PropEnum: TJSONEnum;
+begin
+  for TCollectionItem(def) in ParamDefs do
+  begin
+    // assert the typecast in for loop
+    Assert(def is TJSONParamDef,'Unexpected ParamDef item class.');
+
+    Param:=ParamObject.Find(def.Name);
+    // check required parameters
+    if not Assigned(Param) then
+    begin
+      if def.Required then
+        JSONRPCParamError(SErrParamsRequiredParamNotFound,[def.Name])
+      else
+        Continue;
+    end;
+
+    // jtUnkown accepts all data types
+    if (def.DataTypejtUnknown) and not (Param.JSONType=def.DataType) then
+     
JSONRPCParamError(SErrParamsDataTypeMismatch,[def.Name,JSONTypeName(def.DataType),JSONTypeName(Param.JSONType)]);
+  end;
+
+  // check if additional parameters are given
+  if not (jroIgnoreExtraFields in Options) then
+  begin
+    for PropEnum in ParamObject do
+    begin
+      // only check for name is required other specs are checked before
+      if ParamDefs.FindParamDef(PropEnum.Key)=nil then
+        JSONRPCParamError(SErrParamsNotAllowd,[PropEnum.Key]);
+    end;
+  end;
+end;
+
+procedure TCustomJSONRPCHandler.DoCheckParamArray(const ParamArray:
TJSONArray);
+var
+  element: TJSONEnum;
+begin
+  for element in ParamArray do
+  begin
+    // check object parameters if objects given
+    if (element.Value.JSONType=jtObject) then
+    begin
+      DoCheckParamDefsOnObject(element.Value as TJSONObject);
+    end else
+    // not an object
+    if (jroObjectParams in Options) then
+     
JSONRPCParamError(SErrParamsOnlyObjectsInArray,[JSONTypeName(element.Value.JSONType),element.KeyNum]);
+  end;
 end;
 
 function TCustomJSONRPCHandler.DoExecute(Const Params:
TJSONData;AContext : TJSONRPCCallContext): TJSONData;