mORMot and Open Source friends
Check-in [d9099ee94b]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:{923} added TSQLRestServerURIContext.InputAsMultiPart() method
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: d9099ee94bb1e430f27f11f891bd5dd540b4d394
User & Date: ab 2015-02-18 08:14:13
Context
2015-02-18
14:40
{924} introducing detailed SOA statistics for method-based and interface-based services, available from the TSQLRestServer.ServiceMethodStat[] property or the associated TServiceFactoryServer.Stats / Stat[] methods, or remotely as an option to the TSQLRestServer.Stat() service check-in: 616d7de50c user: ab tags: trunk
08:14
{923} added TSQLRestServerURIContext.InputAsMultiPart() method check-in: d9099ee94b user: ab tags: trunk
07:57
{922} added MultiPartFormDataDecode() function to decode multipart/form-data POST requests check-in: 2358980405 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/mORMot.pas.

790
791
792
793
794
795
796

797
798
799
800
801
802
803
....
4861
4862
4863
4864
4865
4866
4867



4868
4869
4870
4871
4872
4873
4874
.....
30013
30014
30015
30016
30017
30018
30019

30020
30021
30022
30023
30024
30025
30026
30027
.....
30787
30788
30789
30790
30791
30792
30793
30794
30795
30796
30797
30798
30799
30800
30801
30802
30803
30804
30805
30806
30807
30808
30809
30810
30811
30812
30813
30814
30815
30816
30817
30818
30819
30820
30821
30822
30823
30824
30825
30826
30827
30828
30829





30830

30831
30832
30833







30834
30835
30836
30837
30838
30839
30840
      response to save bandwidth, in TSQLRestServerURIContext.Returns/Results
    - added TSQLRestServerURIContext.ReturnFile() and ReturnFileFromFolder()
      methods, for direct fast transmission to a HTTP client, handling
      "304 Not Modified" and proper mime type recognition
    - added TSQLRestServerURIContext.Input*OrVoid[] properties
    - added TSQLRestServerURIContext.SessionRemoteIP, SessionConnectionID,
      SessionUserName and ResourceFileName properties

    - added TSQLRestServerURIContext.Redirect() method for HTTP 301 commands
    - added TSQLRestServer.ServiceMethodRegister() low-level method
    - added TSQLRestServer.ServiceMethodRegisterPublishedMethods() to allow
      multi-class method-based services (e.g. for implementing MVC model)
    - ServiceContext threadvar will now be set in all ORM and SOA process, to
      allow access to the execution context
    - to make the implicit explicit, TSQLRestServerURIContext.ID has been
................................................................................
    // some characters at decoding from UTF-8 input buffer
    // - returns Unassigned if the parameter is not found
    property InputOrVoid[const ParamName: RawUTF8]: variant read GetInputOrVoid;
    /// retrieve all input paramters from URI as a variant JSON object 
    // - returns Unassigned if no parameter was defined
    property InputAsTDocVariant: variant read GetInputAsTDocVariant;
    {$endif}



    /// retrieve an incoming HTTP header
    // - the supplied header name is case-insensitive
    // - you could call e.g. InHeader['remoteip'] to retrieve the caller IP
    property InHeader[const HeaderName: RawUTF8]: RawUTF8 read GetInHeader;
    /// retrieve an incoming HTTP cookie value
    // - the supplied cookie name is case-insensitive
    property InCookie[CookieName: RawUTF8]: RawUTF8 read GetInCookie write SetInCookie;
................................................................................
    exit; // bad ModelRoot -> caller can try another TSQLRestServer
  end;
  URI := copy(Call^.url,j+i+2,maxInt); // trim any '/root/' left side of Ctxt.URI
  ParametersPos := PosEx(RawUTF8('?'),Call^.url,1);
  if ParametersPos>0 then // '?select=...&where=...' or '?where=...'
    Parameters := @Call^.url[ParametersPos+1] else
    if Method=mPost then begin

      fInputPostContentType := FindIniNameValue(pointer(Call.InHead),HEADER_CONTENT_TYPE_UPPER);
      if IdemPChar(pointer(fInputPostContentType),'APPLICATION/X-WWW-FORM-URLENCODED') then
        Parameters := pointer(Call^.InBody);
    end; 
  // compute Table, TableID and URIBlobFieldName
  i := PosEx(RawUTF8('/'),URI,1);
  if i>0 then begin
    Par := @URI[i+1];
................................................................................
end;

function TSQLRestServerURIContext.GetInputOrVoid(const ParamName: RawUTF8): variant;
begin
  GetVariantFromJSON(pointer(GetInputUTF8OrVoid(ParamName)),false,Result);
end;

function MultiPartFormDataDecode(const MimeType,Body: RawUTF8;
  var Names,ContentTypes: TRawUTF8DynArray; var Values: TRawByteStringDynArray): boolean;
var boundary: RawUTF8;
    i: integer;
begin
  result := false;
  i := PosEx('boundary=',MimeType);
  if i=0 then
    exit;
  boundary := '--'+trim(copy(MimeType,i+9,200));
  // to be done
  raise ECommunicationException.Create('multipart/form-data not implemented yet');
end;

function TSQLRestServerURIContext.GetInputAsTDocVariant: variant;
var i: integer;
    v: variant;
    Names,ContentTypes: TRawUTF8DynArray;
    Values: TRawByteStringDynArray;
//    Variants: TVariantDynArray;
begin
  VarClear(result);
  FillInput;
  if fInput<>nil then begin
    with TDocVariantData(result) do begin
      Init(JSON_OPTIONS[true]);
      for i := 0 to (length(fInput) shr 1)-1 do begin
        GetVariantFromJSON(pointer(fInput[i*2+1]),false,v,@JSON_OPTIONS[true]);
        AddValue(fInput[i*2],v);
      end;
    end;
  end else
  if (Method=mPOST) and
     IdemPChar(pointer(fInputPostContentType),'MULTIPART/FORM-DATA') and
     MultiPartFormDataDecode(fInputPostContentType,Call^.InBody,Names,ContentTypes,Values) then begin
    //TDocVariantData(result).InitObjectFromVariants(Names,Variants);





  end;

end;

{$endif NOVARIANTS}








function TSQLRestServerURIContext.GetInHeader(const HeaderName: RawUTF8): RawUTF8;
var up: array[byte] of AnsiChar;
begin
  PWord(UpperCopy255(up,HeaderName))^ := ord(':');
  result := Trim(FindIniNameValue(pointer(Call.InHead),up));
end;






>







 







>
>
>







 







>
|







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<



|
<
<












|
<
<
|
>
>
>
>
>
|
>



>
>
>
>
>
>
>







790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
....
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
.....
30017
30018
30019
30020
30021
30022
30023
30024
30025
30026
30027
30028
30029
30030
30031
30032
.....
30792
30793
30794
30795
30796
30797
30798














30799
30800
30801
30802


30803
30804
30805
30806
30807
30808
30809
30810
30811
30812
30813
30814
30815


30816
30817
30818
30819
30820
30821
30822
30823
30824
30825
30826
30827
30828
30829
30830
30831
30832
30833
30834
30835
30836
30837
30838
30839
30840
      response to save bandwidth, in TSQLRestServerURIContext.Returns/Results
    - added TSQLRestServerURIContext.ReturnFile() and ReturnFileFromFolder()
      methods, for direct fast transmission to a HTTP client, handling
      "304 Not Modified" and proper mime type recognition
    - added TSQLRestServerURIContext.Input*OrVoid[] properties
    - added TSQLRestServerURIContext.SessionRemoteIP, SessionConnectionID,
      SessionUserName and ResourceFileName properties
    - added TSQLRestServerURIContext.InputAsMultiPart() method
    - added TSQLRestServerURIContext.Redirect() method for HTTP 301 commands
    - added TSQLRestServer.ServiceMethodRegister() low-level method
    - added TSQLRestServer.ServiceMethodRegisterPublishedMethods() to allow
      multi-class method-based services (e.g. for implementing MVC model)
    - ServiceContext threadvar will now be set in all ORM and SOA process, to
      allow access to the execution context
    - to make the implicit explicit, TSQLRestServerURIContext.ID has been
................................................................................
    // some characters at decoding from UTF-8 input buffer
    // - returns Unassigned if the parameter is not found
    property InputOrVoid[const ParamName: RawUTF8]: variant read GetInputOrVoid;
    /// retrieve all input paramters from URI as a variant JSON object 
    // - returns Unassigned if no parameter was defined
    property InputAsTDocVariant: variant read GetInputAsTDocVariant;
    {$endif}
    /// decode any multipart/form-data POST request input
    // - returns TRUE and set MultiPart array as expected, on success 
    function InputAsMultiPart(var MultiPart: TMultiPartDynArray): Boolean;
    /// retrieve an incoming HTTP header
    // - the supplied header name is case-insensitive
    // - you could call e.g. InHeader['remoteip'] to retrieve the caller IP
    property InHeader[const HeaderName: RawUTF8]: RawUTF8 read GetInHeader;
    /// retrieve an incoming HTTP cookie value
    // - the supplied cookie name is case-insensitive
    property InCookie[CookieName: RawUTF8]: RawUTF8 read GetInCookie write SetInCookie;
................................................................................
    exit; // bad ModelRoot -> caller can try another TSQLRestServer
  end;
  URI := copy(Call^.url,j+i+2,maxInt); // trim any '/root/' left side of Ctxt.URI
  ParametersPos := PosEx(RawUTF8('?'),Call^.url,1);
  if ParametersPos>0 then // '?select=...&where=...' or '?where=...'
    Parameters := @Call^.url[ParametersPos+1] else
    if Method=mPost then begin
      fInputPostContentType := FindIniNameValue(
        pointer(Call.InHead),HEADER_CONTENT_TYPE_UPPER);
      if IdemPChar(pointer(fInputPostContentType),'APPLICATION/X-WWW-FORM-URLENCODED') then
        Parameters := pointer(Call^.InBody);
    end; 
  // compute Table, TableID and URIBlobFieldName
  i := PosEx(RawUTF8('/'),URI,1);
  if i>0 then begin
    Par := @URI[i+1];
................................................................................
end;

function TSQLRestServerURIContext.GetInputOrVoid(const ParamName: RawUTF8): variant;
begin
  GetVariantFromJSON(pointer(GetInputUTF8OrVoid(ParamName)),false,Result);
end;















function TSQLRestServerURIContext.GetInputAsTDocVariant: variant;
var i: integer;
    v: variant;
    MultiPart: TMultiPartDynArray;


begin
  VarClear(result);
  FillInput;
  if fInput<>nil then begin
    with TDocVariantData(result) do begin
      Init(JSON_OPTIONS[true]);
      for i := 0 to (length(fInput) shr 1)-1 do begin
        GetVariantFromJSON(pointer(fInput[i*2+1]),false,v,@JSON_OPTIONS[true]);
        AddValue(fInput[i*2],v);
      end;
    end;
  end else
  if InputAsMultiPart(MultiPart) then


    with TDocVariantData(result) do begin
      Init(JSON_OPTIONS[true]);
      for i := 0 to high(MultiPart) do
        with MultiPart[i] do begin
          RawUTF8ToVariant(Content,v);
          AddValue(Name,v);
        end;
    end;
end;

{$endif NOVARIANTS}

function TSQLRestServerURIContext.InputAsMultiPart(var MultiPart: TMultiPartDynArray): Boolean;
begin
  result := (Method=mPOST) and
     IdemPChar(pointer(fInputPostContentType),'MULTIPART/FORM-DATA') and
     MultiPartFormDataDecode(fInputPostContentType,Call^.InBody,MultiPart);
end;

function TSQLRestServerURIContext.GetInHeader(const HeaderName: RawUTF8): RawUTF8;
var up: array[byte] of AnsiChar;
begin
  PWord(UpperCopy255(up,HeaderName))^ := ord(':');
  result := Trim(FindIniNameValue(pointer(Call.InHead),up));
end;

Changes to SynCommons.pas.

3178
3179
3180
3181
3182
3183
3184

3185
3186
3187
3188
3189
3190
3191
.....
24318
24319
24320
24321
24322
24323
24324
24325
24326
24327
24328
24329
24330
24331
24332
.....
24336
24337
24338
24339
24340
24341
24342
24343
24344
24345
24346
24347
24348
24349
24350
24351
24352
24353
24354
24355
24356
24357
24358

24359


24360
24361
24362
24363
24364
24365
24366
24367
24368
24369
24370
24371
24372
24373
type
  /// used by MultiPartFormDataDecode() to return one item of its data
  TMultiPart = record
    Name: RawUTF8;
    FileName: RawUTF8;
    ContentType: RawUTF8;

    Content: RawByteString;
  end;
  /// used by MultiPartFormDataDecode() to return all its data items
  TMultiPartDynArray = array of TMultiPart;

/// decode multipart/form-data POST request content
// - following RFC1867
................................................................................
  result := ExistsIniNameValue(Headers,HEADER_CONTENT_TYPE_UPPER,
    ['TEXT/','APPLICATION/JSON','APPLICATION/XML',
     'APPLICATION/X-JAVASCRIPT','IMAGE/SVG+XML']);
end;

function MultiPartFormDataDecode(const MimeType,Body: RawUTF8;
  var MultiPart: TMultiPartDynArray): boolean;
var boundary,encoding: RawUTF8;
    i,j: integer;
    P: PUTF8Char;
    part: TMultiPart;
begin
  result := false;
  i := PosEx('boundary=',MimeType);
  if i=0 then
................................................................................
  if i<>0 then
  repeat
    inc(i,length(boundary));
    if i=length(body) then
      exit; // reached the end
    P := PUTF8Char(Pointer(Body))+i-1;
    Finalize(part);
    encoding := '';
    repeat
      if IdemPCharAndGetNextItem(P,'CONTENT-DISPOSITION: FORM-DATA; NAME="',
         part.Name,'"') then
        IdemPCharAndGetNextItem(P,'; FILENAME="',part.FileName,'"') else
      if IdemPCharAndGetNextItem(P,'CONTENT-TYPE: ',part.ContentType) or
         IdemPCharAndGetNextItem(P,'CONTENT-TRANSFER-ENCODING: ',encoding) then;
      GetNextLineBegin(P,P);
      if P=nil then
        exit;
   until PWord(P)^=13+10 shl 8;
   i := P-PUTF8Char(Pointer(Body))+3; // i = just after header
   j := PosEx(Body,boundary,i);
   if j=0 then
     exit;
   part.Content := copy(Body,i,j-i);

   if (encoding<>'7bit') and (encoding<>'8bit') and (encoding<>'binary') then


     if encoding='base64' then
       part.Content := Base64ToBin(part.Content) else
       exit; // unknown encoding - e.g. "quoted-printable"
   SetLength(MultiPart,length(MultiPart)+1);
   MultiPart[high(MultiPart)] := part;
   result := true;
   i := j;
  until false;
end;

function FastLocatePUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char): PtrInt;
var L,i,cmp: PtrInt;
begin
  if R<0 then






>







 







|







 







<

|
|


|



|
|
|
|
|
|
>
|
>
>
|
|
<
|
|
|
|







3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
.....
24319
24320
24321
24322
24323
24324
24325
24326
24327
24328
24329
24330
24331
24332
24333
.....
24337
24338
24339
24340
24341
24342
24343

24344
24345
24346
24347
24348
24349
24350
24351
24352
24353
24354
24355
24356
24357
24358
24359
24360
24361
24362
24363
24364

24365
24366
24367
24368
24369
24370
24371
24372
24373
24374
24375
type
  /// used by MultiPartFormDataDecode() to return one item of its data
  TMultiPart = record
    Name: RawUTF8;
    FileName: RawUTF8;
    ContentType: RawUTF8;
    Encoding: RawUTF8;
    Content: RawByteString;
  end;
  /// used by MultiPartFormDataDecode() to return all its data items
  TMultiPartDynArray = array of TMultiPart;

/// decode multipart/form-data POST request content
// - following RFC1867
................................................................................
  result := ExistsIniNameValue(Headers,HEADER_CONTENT_TYPE_UPPER,
    ['TEXT/','APPLICATION/JSON','APPLICATION/XML',
     'APPLICATION/X-JAVASCRIPT','IMAGE/SVG+XML']);
end;

function MultiPartFormDataDecode(const MimeType,Body: RawUTF8;
  var MultiPart: TMultiPartDynArray): boolean;
var boundary: RawUTF8;
    i,j: integer;
    P: PUTF8Char;
    part: TMultiPart;
begin
  result := false;
  i := PosEx('boundary=',MimeType);
  if i=0 then
................................................................................
  if i<>0 then
  repeat
    inc(i,length(boundary));
    if i=length(body) then
      exit; // reached the end
    P := PUTF8Char(Pointer(Body))+i-1;
    Finalize(part);

    repeat
      if IdemPCharAndGetNextItem(P,
         'CONTENT-DISPOSITION: FORM-DATA; NAME="',part.Name,'"') then
        IdemPCharAndGetNextItem(P,'; FILENAME="',part.FileName,'"') else
      if IdemPCharAndGetNextItem(P,'CONTENT-TYPE: ',part.ContentType) or
         IdemPCharAndGetNextItem(P,'CONTENT-TRANSFER-ENCODING: ',part.Encoding) then;
      GetNextLineBegin(P,P);
      if P=nil then
        exit;
    until PWord(P)^=13+10 shl 8;
    i := P-PUTF8Char(Pointer(Body))+3; // i = just after header
    j := PosEx(Body,boundary,i);
    if j=0 then
      exit;
    part.Content := copy(Body,i,j-i-2); // -2 to ignore latest #13#10
    {$ifdef UNICODE}
    if part.ContentType='' then
      SetCodePage(part.Content,CP_UTF8) else // ensure raw field value is UTF-8
    {$endif}
    if part.Encoding='base64' then // "quoted-printable" not yet handled here
      part.Content := Base64ToBin(part.Content);

    SetLength(MultiPart,length(MultiPart)+1);
    MultiPart[high(MultiPart)] := part;
    result := true;
    i := j;
  until false;
end;

function FastLocatePUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char): PtrInt;
var L,i,cmp: PtrInt;
begin
  if R<0 then

Changes to SynopseCommit.inc.

1
'1.18.922'
|
1
'1.18.923'