| (* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| *) |
| |
| {$SCOPEDENUMS ON} |
| |
| unit Thrift.Protocol.JSON; |
| |
| interface |
| |
| uses |
| Character, |
| Classes, |
| SysUtils, |
| Math, |
| Generics.Collections, |
| Thrift.Configuration, |
| Thrift.Transport, |
| Thrift.Protocol, |
| Thrift.Utils; |
| |
| type |
| IJSONProtocol = interface( IProtocol) |
| ['{F0DAFDBD-692A-4B71-9736-F5D485A2178F}'] |
| // Read a byte that must match b; otherwise an exception is thrown. |
| procedure ReadJSONSyntaxChar( b : Byte); |
| end; |
| |
| // JSON protocol implementation for thrift. |
| // This is a full-featured protocol supporting Write and Read. |
| // Please see the C++ class header for a detailed description of the protocol's wire format. |
| // Adapted from the C# version. |
| TJSONProtocolImpl = class( TProtocolImpl, IJSONProtocol) |
| public |
| type |
| TFactory = class( TInterfacedObject, IProtocolFactory) |
| public |
| function GetProtocol( const trans: ITransport): IProtocol; |
| end; |
| |
| strict private |
| class function GetTypeNameForTypeID(typeID : TType) : string; |
| class function GetTypeIDForTypeName( const name : string) : TType; |
| |
| strict protected |
| type |
| // Base class for tracking JSON contexts that may require |
| // inserting/Reading additional JSON syntax characters. |
| // This base context does nothing. |
| TJSONBaseContext = class |
| strict protected |
| FProto : Pointer; // weak IJSONProtocol; |
| public |
| constructor Create( const aProto : IJSONProtocol); |
| procedure Write; virtual; |
| procedure Read; virtual; |
| function EscapeNumbers : Boolean; virtual; |
| end; |
| |
| // Context for JSON lists. |
| // Will insert/Read commas before each item except for the first one. |
| TJSONListContext = class( TJSONBaseContext) |
| strict private |
| FFirst : Boolean; |
| public |
| constructor Create( const aProto : IJSONProtocol); |
| procedure Write; override; |
| procedure Read; override; |
| end; |
| |
| // Context for JSON records. Will insert/Read colons before the value portion of each record |
| // pair, and commas before each key except the first. In addition, will indicate that numbers |
| // in the key position need to be escaped in quotes (since JSON keys must be strings). |
| TJSONPairContext = class( TJSONBaseContext) |
| strict private |
| FFirst, FColon : Boolean; |
| public |
| constructor Create( const aProto : IJSONProtocol); |
| procedure Write; override; |
| procedure Read; override; |
| function EscapeNumbers : Boolean; override; |
| end; |
| |
| // Holds up to one byte from the transport |
| TLookaheadReader = class |
| strict protected |
| FProto : Pointer; // weak IJSONProtocol; |
| |
| protected |
| constructor Create( const aProto : IJSONProtocol); |
| |
| strict private |
| FHasData : Boolean; |
| FData : Byte; |
| |
| public |
| // Return and consume the next byte to be Read, either taking it from the |
| // data buffer if present or getting it from the transport otherwise. |
| function Read : Byte; |
| |
| // Return the next byte to be Read without consuming, filling the data |
| // buffer if it has not been filled alReady. |
| function Peek : Byte; |
| end; |
| |
| strict protected |
| // Stack of nested contexts that we may be in |
| FContextStack : TStack<TJSONBaseContext>; |
| |
| // Current context that we are in |
| FContext : TJSONBaseContext; |
| |
| // Reader that manages a 1-byte buffer |
| FReader : TLookaheadReader; |
| |
| // Push/pop a new JSON context onto/from the stack. |
| procedure ResetContextStack; |
| procedure PushContext( const aCtx : TJSONBaseContext); |
| procedure PopContext; |
| |
| strict protected |
| function GetMinSerializedSize( const aType : TType) : Integer; override; |
| procedure Reset; override; |
| |
| public |
| // TJSONProtocolImpl Constructor |
| constructor Create( const aTrans : ITransport); |
| destructor Destroy; override; |
| |
| strict protected |
| // IJSONProtocol |
| // Read a byte that must match b; otherwise an exception is thrown. |
| procedure ReadJSONSyntaxChar( b : Byte); |
| |
| strict private |
| // Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its corresponding hex value |
| class function HexVal( ch : Byte) : Byte; |
| |
| // Convert a byte containing a hex value to its corresponding hex character |
| class function HexChar( val : Byte) : Byte; |
| |
| // Write the bytes in array buf as a JSON characters, escaping as needed |
| procedure WriteJSONString( const b : TBytes); overload; |
| procedure WriteJSONString( const str : string); overload; |
| |
| // Write out number as a JSON value. If the context dictates so, it will be |
| // wrapped in quotes to output as a JSON string. |
| procedure WriteJSONInteger( const num : Int64); |
| |
| // Write out a double as a JSON value. If it is NaN or infinity or if the |
| // context dictates escaping, Write out as JSON string. |
| procedure WriteJSONDouble( const num : Double); |
| |
| // Write out contents of byte array b as a JSON string with base-64 encoded data |
| procedure WriteJSONBase64( const b : TBytes); |
| |
| procedure WriteJSONObjectStart; |
| procedure WriteJSONObjectEnd; |
| procedure WriteJSONArrayStart; |
| procedure WriteJSONArrayEnd; |
| |
| public |
| // IProtocol |
| procedure WriteMessageBegin( const aMsg : TThriftMessage); override; |
| procedure WriteMessageEnd; override; |
| procedure WriteStructBegin( const struc: TThriftStruct); override; |
| procedure WriteStructEnd; override; |
| procedure WriteFieldBegin( const field: TThriftField); override; |
| procedure WriteFieldEnd; override; |
| procedure WriteFieldStop; override; |
| procedure WriteMapBegin( const map: TThriftMap); override; |
| procedure WriteMapEnd; override; |
| procedure WriteListBegin( const list: TThriftList); override; |
| procedure WriteListEnd(); override; |
| procedure WriteSetBegin( const set_: TThriftSet ); override; |
| procedure WriteSetEnd(); override; |
| procedure WriteBool( b: Boolean); override; |
| procedure WriteByte( b: ShortInt); override; |
| procedure WriteI16( i16: SmallInt); override; |
| procedure WriteI32( i32: Integer); override; |
| procedure WriteI64( const i64: Int64); override; |
| procedure WriteDouble( const d: Double); override; |
| procedure WriteString( const s: string ); override; |
| procedure WriteBinary( const b: TBytes); override; |
| // |
| function ReadMessageBegin: TThriftMessage; override; |
| procedure ReadMessageEnd(); override; |
| function ReadStructBegin: TThriftStruct; override; |
| procedure ReadStructEnd; override; |
| function ReadFieldBegin: TThriftField; override; |
| procedure ReadFieldEnd(); override; |
| function ReadMapBegin: TThriftMap; override; |
| procedure ReadMapEnd(); override; |
| function ReadListBegin: TThriftList; override; |
| procedure ReadListEnd(); override; |
| function ReadSetBegin: TThriftSet; override; |
| procedure ReadSetEnd(); override; |
| function ReadBool: Boolean; override; |
| function ReadByte: ShortInt; override; |
| function ReadI16: SmallInt; override; |
| function ReadI32: Integer; override; |
| function ReadI64: Int64; override; |
| function ReadDouble:Double; override; |
| function ReadString : string; override; |
| function ReadBinary: TBytes; override; |
| |
| |
| strict private |
| // Reading methods. |
| |
| // Read in a JSON string, unescaping as appropriate. |
| // Skip Reading from the context if skipContext is true. |
| function ReadJSONString( skipContext : Boolean) : TBytes; |
| |
| // Return true if the given byte could be a valid part of a JSON number. |
| function IsJSONNumeric( b : Byte) : Boolean; |
| |
| // Read in a sequence of characters that are all valid in JSON numbers. Does |
| // not do a complete regex check to validate that this is actually a number. |
| function ReadJSONNumericChars : String; |
| |
| // Read in a JSON number. If the context dictates, Read in enclosing quotes. |
| function ReadJSONInteger : Int64; |
| |
| // Read in a JSON double value. Throw if the value is not wrapped in quotes |
| // when expected or if wrapped in quotes when not expected. |
| function ReadJSONDouble : Double; |
| |
| // Read in a JSON string containing base-64 encoded data and decode it. |
| function ReadJSONBase64 : TBytes; |
| |
| procedure ReadJSONObjectStart; |
| procedure ReadJSONObjectEnd; |
| procedure ReadJSONArrayStart; |
| procedure ReadJSONArrayEnd; |
| end; |
| |
| |
| implementation |
| |
| var |
| COMMA : TBytes; |
| COLON : TBytes; |
| LBRACE : TBytes; |
| RBRACE : TBytes; |
| LBRACKET : TBytes; |
| RBRACKET : TBytes; |
| QUOTE : TBytes; |
| BACKSLASH : TBytes; |
| ESCSEQ : TBytes; |
| |
| const |
| VERSION = 1; |
| JSON_CHAR_TABLE : array[0..$2F] of Byte |
| = (0,0,0,0, 0,0,0,0, Byte('b'),Byte('t'),Byte('n'),0, Byte('f'),Byte('r'),0,0, |
| 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, |
| 1,1,Byte('"'),1, 1,1,1,1, 1,1,1,1, 1,1,1,1); |
| |
| ESCAPE_CHARS = '"\/btnfr'; |
| ESCAPE_CHAR_VALS = '"\/'#8#9#10#12#13; |
| |
| DEF_STRING_SIZE = 16; |
| |
| NAME_BOOL = 'tf'; |
| NAME_BYTE = 'i8'; |
| NAME_I16 = 'i16'; |
| NAME_I32 = 'i32'; |
| NAME_I64 = 'i64'; |
| NAME_DOUBLE = 'dbl'; |
| NAME_STRUCT = 'rec'; |
| NAME_STRING = 'str'; |
| NAME_MAP = 'map'; |
| NAME_LIST = 'lst'; |
| NAME_SET = 'set'; |
| |
| INVARIANT_CULTURE : TFormatSettings |
| = ( ThousandSeparator: ','; |
| DecimalSeparator: '.'); |
| |
| |
| |
| //--- TJSONProtocolImpl ---------------------- |
| |
| |
| function TJSONProtocolImpl.TFactory.GetProtocol( const trans: ITransport): IProtocol; |
| begin |
| result := TJSONProtocolImpl.Create( trans); |
| end; |
| |
| class function TJSONProtocolImpl.GetTypeNameForTypeID(typeID : TType) : string; |
| begin |
| case typeID of |
| TType.Bool_: result := NAME_BOOL; |
| TType.Byte_: result := NAME_BYTE; |
| TType.I16: result := NAME_I16; |
| TType.I32: result := NAME_I32; |
| TType.I64: result := NAME_I64; |
| TType.Double_: result := NAME_DOUBLE; |
| TType.String_: result := NAME_STRING; |
| TType.Struct: result := NAME_STRUCT; |
| TType.Map: result := NAME_MAP; |
| TType.Set_: result := NAME_SET; |
| TType.List: result := NAME_LIST; |
| else |
| raise TProtocolExceptionNotImplemented.Create('Unrecognized type ('+IntToStr(Ord(typeID))+')'); |
| end; |
| end; |
| |
| |
| class function TJSONProtocolImpl.GetTypeIDForTypeName( const name : string) : TType; |
| begin |
| if name = NAME_BOOL then result := TType.Bool_ |
| else if name = NAME_BYTE then result := TType.Byte_ |
| else if name = NAME_I16 then result := TType.I16 |
| else if name = NAME_I32 then result := TType.I32 |
| else if name = NAME_I64 then result := TType.I64 |
| else if name = NAME_DOUBLE then result := TType.Double_ |
| else if name = NAME_STRUCT then result := TType.Struct |
| else if name = NAME_STRING then result := TType.String_ |
| else if name = NAME_MAP then result := TType.Map |
| else if name = NAME_LIST then result := TType.List |
| else if name = NAME_SET then result := TType.Set_ |
| else raise TProtocolExceptionNotImplemented.Create('Unrecognized type ('+name+')'); |
| end; |
| |
| |
| constructor TJSONProtocolImpl.TJSONBaseContext.Create( const aProto : IJSONProtocol); |
| begin |
| inherited Create; |
| FProto := Pointer(aProto); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.TJSONBaseContext.Write; |
| begin |
| // nothing |
| end; |
| |
| |
| procedure TJSONProtocolImpl.TJSONBaseContext.Read; |
| begin |
| // nothing |
| end; |
| |
| |
| function TJSONProtocolImpl.TJSONBaseContext.EscapeNumbers : Boolean; |
| begin |
| result := FALSE; |
| end; |
| |
| |
| constructor TJSONProtocolImpl.TJSONListContext.Create( const aProto : IJSONProtocol); |
| begin |
| inherited Create( aProto); |
| FFirst := TRUE; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.TJSONListContext.Write; |
| begin |
| if FFirst |
| then FFirst := FALSE |
| else IJSONProtocol(FProto).Transport.Write( COMMA); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.TJSONListContext.Read; |
| begin |
| if FFirst |
| then FFirst := FALSE |
| else IJSONProtocol(FProto).ReadJSONSyntaxChar( COMMA[0]); |
| end; |
| |
| |
| constructor TJSONProtocolImpl.TJSONPairContext.Create( const aProto : IJSONProtocol); |
| begin |
| inherited Create( aProto); |
| FFirst := TRUE; |
| FColon := TRUE; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.TJSONPairContext.Write; |
| begin |
| if FFirst then begin |
| FFirst := FALSE; |
| FColon := TRUE; |
| end |
| else begin |
| if FColon |
| then IJSONProtocol(FProto).Transport.Write( COLON) |
| else IJSONProtocol(FProto).Transport.Write( COMMA); |
| FColon := not FColon; |
| end; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.TJSONPairContext.Read; |
| begin |
| if FFirst then begin |
| FFirst := FALSE; |
| FColon := TRUE; |
| end |
| else begin |
| if FColon |
| then IJSONProtocol(FProto).ReadJSONSyntaxChar( COLON[0]) |
| else IJSONProtocol(FProto).ReadJSONSyntaxChar( COMMA[0]); |
| FColon := not FColon; |
| end; |
| end; |
| |
| |
| function TJSONProtocolImpl.TJSONPairContext.EscapeNumbers : Boolean; |
| begin |
| result := FColon; |
| end; |
| |
| |
| constructor TJSONProtocolImpl.TLookaheadReader.Create( const aProto : IJSONProtocol); |
| begin |
| inherited Create; |
| FProto := Pointer(aProto); |
| FHasData := FALSE; |
| end; |
| |
| |
| function TJSONProtocolImpl.TLookaheadReader.Read : Byte; |
| begin |
| if FHasData |
| then FHasData := FALSE |
| else begin |
| IJSONProtocol(FProto).Transport.ReadAll( @FData, SizeOf(FData), 0, 1); |
| end; |
| result := FData; |
| end; |
| |
| |
| function TJSONProtocolImpl.TLookaheadReader.Peek : Byte; |
| begin |
| if not FHasData then begin |
| IJSONProtocol(FProto).Transport.ReadAll( @FData, SizeOf(FData), 0, 1); |
| FHasData := TRUE; |
| end; |
| result := FData; |
| end; |
| |
| |
| constructor TJSONProtocolImpl.Create( const aTrans : ITransport); |
| begin |
| inherited Create( aTrans); |
| |
| // Stack of nested contexts that we may be in |
| FContextStack := TStack<TJSONBaseContext>.Create; |
| |
| FContext := TJSONBaseContext.Create( Self); |
| FReader := TLookaheadReader.Create( Self); |
| end; |
| |
| |
| destructor TJSONProtocolImpl.Destroy; |
| begin |
| try |
| ResetContextStack; // free any contents |
| FreeAndNil( FReader); |
| FreeAndNil( FContext); |
| FreeAndNil( FContextStack); |
| finally |
| inherited Destroy; |
| end; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.Reset; |
| begin |
| inherited Reset; |
| ResetContextStack; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ResetContextStack; |
| begin |
| while FContextStack.Count > 0 |
| do PopContext; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.PushContext( const aCtx : TJSONBaseContext); |
| begin |
| FContextStack.Push( FContext); |
| FContext := aCtx; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.PopContext; |
| begin |
| FreeAndNil(FContext); |
| FContext := FContextStack.Pop; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadJSONSyntaxChar( b : Byte); |
| var ch : Byte; |
| begin |
| ch := FReader.Read; |
| if (ch <> b) |
| then raise TProtocolExceptionInvalidData.Create('Unexpected character ('+Char(ch)+')'); |
| end; |
| |
| |
| class function TJSONProtocolImpl.HexVal( ch : Byte) : Byte; |
| var i : Integer; |
| begin |
| i := StrToIntDef( '$0'+Char(ch), -1); |
| if (0 <= i) and (i < $10) |
| then result := i |
| else raise TProtocolExceptionInvalidData.Create('Expected hex character ('+Char(ch)+')'); |
| end; |
| |
| |
| class function TJSONProtocolImpl.HexChar( val : Byte) : Byte; |
| const HEXCHARS = '0123456789ABCDEF'; |
| begin |
| result := Byte( PChar(HEXCHARS)[val and $0F]); |
| ASSERT( Pos( Char(result), HEXCHARS) > 0); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONString( const str : string); |
| begin |
| WriteJSONString( SysUtils.TEncoding.UTF8.GetBytes( str)); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONString( const b : TBytes); |
| var i : Integer; |
| tmp : TBytes; |
| begin |
| FContext.Write; |
| Transport.Write( QUOTE); |
| for i := 0 to Length(b)-1 do begin |
| |
| if (b[i] and $00FF) >= $30 then begin |
| |
| if (b[i] = BACKSLASH[0]) then begin |
| Transport.Write( BACKSLASH); |
| Transport.Write( BACKSLASH); |
| end |
| else begin |
| Transport.Write( b, i, 1); |
| end; |
| |
| end |
| else begin |
| SetLength( tmp, 2); |
| tmp[0] := JSON_CHAR_TABLE[b[i]]; |
| if (tmp[0] = 1) then begin |
| Transport.Write( b, i, 1) |
| end |
| else if (tmp[0] > 1) then begin |
| Transport.Write( BACKSLASH); |
| Transport.Write( tmp, 0, 1); |
| end |
| else begin |
| Transport.Write( ESCSEQ); |
| tmp[0] := HexChar( b[i] div $10); |
| tmp[1] := HexChar( b[i]); |
| Transport.Write( tmp, 0, 2); |
| end; |
| end; |
| end; |
| Transport.Write( QUOTE); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONInteger( const num : Int64); |
| var str : String; |
| escapeNum : Boolean; |
| begin |
| FContext.Write; |
| str := IntToStr(num); |
| |
| escapeNum := FContext.EscapeNumbers; |
| if escapeNum |
| then Transport.Write( QUOTE); |
| |
| Transport.Write( SysUtils.TEncoding.UTF8.GetBytes( str)); |
| |
| if escapeNum |
| then Transport.Write( QUOTE); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONDouble( const num : Double); |
| var str : string; |
| special : Boolean; |
| escapeNum : Boolean; |
| begin |
| FContext.Write; |
| |
| str := FloatToStr( num, INVARIANT_CULTURE); |
| special := FALSE; |
| |
| case UpCase(str[1]) of |
| 'N' : special := TRUE; // NaN |
| 'I' : special := TRUE; // Infinity |
| '-' : special := (UpCase(str[2]) = 'I'); // -Infinity |
| end; |
| |
| escapeNum := special or FContext.EscapeNumbers; |
| |
| |
| if escapeNum |
| then Transport.Write( QUOTE); |
| |
| Transport.Write( SysUtils.TEncoding.UTF8.GetBytes( str)); |
| |
| if escapeNum |
| then Transport.Write( QUOTE); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONBase64( const b : TBytes); |
| var len, off, cnt : Integer; |
| tmpBuf : TBytes; |
| begin |
| FContext.Write; |
| Transport.Write( QUOTE); |
| |
| len := Length(b); |
| off := 0; |
| SetLength( tmpBuf, 4); |
| |
| while len >= 3 do begin |
| // Encode 3 bytes at a time |
| Base64Utils.Encode( b, off, 3, tmpBuf, 0); |
| Transport.Write( tmpBuf, 0, 4); |
| Inc( off, 3); |
| Dec( len, 3); |
| end; |
| |
| // Encode remainder, if any |
| if len > 0 then begin |
| cnt := Base64Utils.Encode( b, off, len, tmpBuf, 0); |
| Transport.Write( tmpBuf, 0, cnt); |
| end; |
| |
| Transport.Write( QUOTE); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONObjectStart; |
| begin |
| FContext.Write; |
| Transport.Write( LBRACE); |
| PushContext( TJSONPairContext.Create( Self)); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONObjectEnd; |
| begin |
| PopContext; |
| Transport.Write( RBRACE); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONArrayStart; |
| begin |
| FContext.Write; |
| Transport.Write( LBRACKET); |
| PushContext( TJSONListContext.Create( Self)); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteJSONArrayEnd; |
| begin |
| PopContext; |
| Transport.Write( RBRACKET); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteMessageBegin( const aMsg : TThriftMessage); |
| begin |
| Reset; |
| ResetContextStack; // THRIFT-1473 |
| |
| WriteJSONArrayStart; |
| WriteJSONInteger(VERSION); |
| |
| WriteJSONString( SysUtils.TEncoding.UTF8.GetBytes( aMsg.Name)); |
| |
| WriteJSONInteger( LongInt( aMsg.Type_)); |
| WriteJSONInteger( aMsg.SeqID); |
| end; |
| |
| procedure TJSONProtocolImpl.WriteMessageEnd; |
| begin |
| WriteJSONArrayEnd; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteStructBegin( const struc: TThriftStruct); |
| begin |
| WriteJSONObjectStart; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteStructEnd; |
| begin |
| WriteJSONObjectEnd; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteFieldBegin( const field : TThriftField); |
| begin |
| WriteJSONInteger(field.ID); |
| WriteJSONObjectStart; |
| WriteJSONString( GetTypeNameForTypeID(field.Type_)); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteFieldEnd; |
| begin |
| WriteJSONObjectEnd; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteFieldStop; |
| begin |
| // nothing to do |
| end; |
| |
| procedure TJSONProtocolImpl.WriteMapBegin( const map: TThriftMap); |
| begin |
| WriteJSONArrayStart; |
| WriteJSONString( GetTypeNameForTypeID( map.KeyType)); |
| WriteJSONString( GetTypeNameForTypeID( map.ValueType)); |
| WriteJSONInteger( map.Count); |
| WriteJSONObjectStart; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteMapEnd; |
| begin |
| WriteJSONObjectEnd; |
| WriteJSONArrayEnd; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteListBegin( const list: TThriftList); |
| begin |
| WriteJSONArrayStart; |
| WriteJSONString( GetTypeNameForTypeID( list.ElementType)); |
| WriteJSONInteger(list.Count); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteListEnd; |
| begin |
| WriteJSONArrayEnd; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteSetBegin( const set_: TThriftSet); |
| begin |
| WriteJSONArrayStart; |
| WriteJSONString( GetTypeNameForTypeID( set_.ElementType)); |
| WriteJSONInteger( set_.Count); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.WriteSetEnd; |
| begin |
| WriteJSONArrayEnd; |
| end; |
| |
| procedure TJSONProtocolImpl.WriteBool( b: Boolean); |
| begin |
| if b |
| then WriteJSONInteger( 1) |
| else WriteJSONInteger( 0); |
| end; |
| |
| procedure TJSONProtocolImpl.WriteByte( b: ShortInt); |
| begin |
| WriteJSONInteger( b); |
| end; |
| |
| procedure TJSONProtocolImpl.WriteI16( i16: SmallInt); |
| begin |
| WriteJSONInteger( i16); |
| end; |
| |
| procedure TJSONProtocolImpl.WriteI32( i32: Integer); |
| begin |
| WriteJSONInteger( i32); |
| end; |
| |
| procedure TJSONProtocolImpl.WriteI64( const i64: Int64); |
| begin |
| WriteJSONInteger(i64); |
| end; |
| |
| procedure TJSONProtocolImpl.WriteDouble( const d: Double); |
| begin |
| WriteJSONDouble( d); |
| end; |
| |
| procedure TJSONProtocolImpl.WriteString( const s: string ); |
| begin |
| WriteJSONString( SysUtils.TEncoding.UTF8.GetBytes( s)); |
| end; |
| |
| procedure TJSONProtocolImpl.WriteBinary( const b: TBytes); |
| begin |
| WriteJSONBase64( b); |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadJSONString( skipContext : Boolean) : TBytes; |
| var buffer : TMemoryStream; |
| ch : Byte; |
| wch : Word; |
| highSurogate: Char; |
| surrogatePairs: Array[0..1] of Char; |
| off : Integer; |
| tmp : TBytes; |
| begin |
| highSurogate := #0; |
| buffer := TMemoryStream.Create; |
| try |
| if not skipContext |
| then FContext.Read; |
| |
| ReadJSONSyntaxChar( QUOTE[0]); |
| |
| while TRUE do begin |
| ch := FReader.Read; |
| |
| if (ch = QUOTE[0]) |
| then Break; |
| |
| // check for escapes |
| if (ch <> ESCSEQ[0]) then begin |
| buffer.Write( ch, 1); |
| Continue; |
| end; |
| |
| // distuinguish between \uNNNN and \? |
| ch := FReader.Read; |
| if (ch <> ESCSEQ[1]) |
| then begin |
| off := Pos( Char(ch), ESCAPE_CHARS); |
| if off < 1 |
| then raise TProtocolExceptionInvalidData.Create('Expected control char'); |
| ch := Byte( ESCAPE_CHAR_VALS[off]); |
| buffer.Write( ch, 1); |
| Continue; |
| end; |
| |
| // it is \uXXXX |
| SetLength( tmp, 4); |
| Transport.ReadAll( tmp, 0, 4); |
| wch := (HexVal(tmp[0]) shl 12) |
| + (HexVal(tmp[1]) shl 8) |
| + (HexVal(tmp[2]) shl 4) |
| + HexVal(tmp[3]); |
| |
| // we need to make UTF8 bytes from it, to be decoded later |
| if CharUtils.IsHighSurrogate(char(wch)) then begin |
| if highSurogate <> #0 |
| then raise TProtocolExceptionInvalidData.Create('Expected low surrogate char'); |
| highSurogate := char(wch); |
| end |
| else if CharUtils.IsLowSurrogate(char(wch)) then begin |
| if highSurogate = #0 |
| then TProtocolExceptionInvalidData.Create('Expected high surrogate char'); |
| surrogatePairs[0] := highSurogate; |
| surrogatePairs[1] := char(wch); |
| tmp := TEncoding.UTF8.GetBytes(surrogatePairs); |
| buffer.Write( tmp[0], Length(tmp)); |
| highSurogate := #0; |
| end |
| else begin |
| tmp := SysUtils.TEncoding.UTF8.GetBytes(Char(wch)); |
| buffer.Write( tmp[0], Length(tmp)); |
| end; |
| end; |
| |
| if highSurogate <> #0 |
| then raise TProtocolExceptionInvalidData.Create('Expected low surrogate char'); |
| |
| SetLength( result, buffer.Size); |
| if buffer.Size > 0 then Move( buffer.Memory^, result[0], Length(result)); |
| |
| finally |
| buffer.Free; |
| end; |
| end; |
| |
| |
| function TJSONProtocolImpl.IsJSONNumeric( b : Byte) : Boolean; |
| const NUMCHARS = ['+','-','.','0','1','2','3','4','5','6','7','8','9','E','e']; |
| begin |
| result := CharInSet( Char(b), NUMCHARS); |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadJSONNumericChars : string; |
| var strbld : TThriftStringBuilder; |
| ch : Byte; |
| begin |
| strbld := TThriftStringBuilder.Create; |
| try |
| while TRUE do begin |
| ch := FReader.Peek; |
| if IsJSONNumeric(ch) |
| then strbld.Append( Char(FReader.Read)) |
| else Break; |
| end; |
| result := strbld.ToString; |
| |
| finally |
| strbld.Free; |
| end; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadJSONInteger : Int64; |
| var str : string; |
| begin |
| FContext.Read; |
| if FContext.EscapeNumbers |
| then ReadJSONSyntaxChar( QUOTE[0]); |
| |
| str := ReadJSONNumericChars; |
| |
| if FContext.EscapeNumbers |
| then ReadJSONSyntaxChar( QUOTE[0]); |
| |
| try |
| result := StrToInt64(str); |
| except |
| on e:Exception do begin |
| raise TProtocolExceptionInvalidData.Create('Bad data encounted in numeric data ('+str+') ('+e.Message+')'); |
| end; |
| end; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadJSONDouble : Double; |
| var dub : Double; |
| str : string; |
| begin |
| FContext.Read; |
| |
| if FReader.Peek = QUOTE[0] |
| then begin |
| str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString( TRUE)); |
| dub := StrToFloat( str, INVARIANT_CULTURE); |
| |
| if not FContext.EscapeNumbers() |
| and not Math.IsNaN(dub) |
| and not Math.IsInfinite(dub) |
| then begin |
| // Throw exception -- we should not be in a string in Self case |
| raise TProtocolExceptionInvalidData.Create('Numeric data unexpectedly quoted'); |
| end; |
| result := dub; |
| Exit; |
| end; |
| |
| // will throw - we should have had a quote if escapeNum == true |
| if FContext.EscapeNumbers |
| then ReadJSONSyntaxChar( QUOTE[0]); |
| |
| try |
| str := ReadJSONNumericChars; |
| result := StrToFloat( str, INVARIANT_CULTURE); |
| except |
| on e:Exception |
| do raise TProtocolExceptionInvalidData.Create('Bad data encounted in numeric data ('+str+') ('+e.Message+')'); |
| end; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadJSONBase64 : TBytes; |
| var b : TBytes; |
| len, off, size : Integer; |
| begin |
| b := ReadJSONString(false); |
| |
| len := Length(b); |
| off := 0; |
| size := 0; |
| |
| // reduce len to ignore fill bytes |
| Dec(len); |
| while (len >= 0) and (b[len] = Byte('=')) do Dec(len); |
| Inc(len); |
| |
| // read & decode full byte triplets = 4 source bytes |
| while (len >= 4) do begin |
| // Decode 4 bytes at a time |
| Inc( size, Base64Utils.Decode( b, off, 4, b, size)); // decoded in place |
| Inc( off, 4); |
| Dec( len, 4); |
| end; |
| |
| // Don't decode if we hit the end or got a single leftover byte (invalid |
| // base64 but legal for skip of regular string type) |
| if len > 1 then begin |
| // Decode remainder |
| Inc( size, Base64Utils.Decode( b, off, len, b, size)); // decoded in place |
| end; |
| |
| // resize to final size and return the data |
| SetLength( b, size); |
| result := b; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadJSONObjectStart; |
| begin |
| FContext.Read; |
| ReadJSONSyntaxChar( LBRACE[0]); |
| PushContext( TJSONPairContext.Create( Self)); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadJSONObjectEnd; |
| begin |
| ReadJSONSyntaxChar( RBRACE[0]); |
| PopContext; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadJSONArrayStart; |
| begin |
| FContext.Read; |
| ReadJSONSyntaxChar( LBRACKET[0]); |
| PushContext( TJSONListContext.Create( Self)); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadJSONArrayEnd; |
| begin |
| ReadJSONSyntaxChar( RBRACKET[0]); |
| PopContext; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadMessageBegin: TThriftMessage; |
| begin |
| Reset; |
| ResetContextStack; // THRIFT-1473 |
| |
| Init( result); |
| ReadJSONArrayStart; |
| |
| if ReadJSONInteger <> VERSION |
| then raise TProtocolExceptionBadVersion.Create('Message contained bad version.'); |
| |
| result.Name := SysUtils.TEncoding.UTF8.GetString( ReadJSONString( FALSE)); |
| result.Type_ := TMessageType( ReadJSONInteger); |
| result.SeqID := ReadJSONInteger; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadMessageEnd; |
| begin |
| ReadJSONArrayEnd; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadStructBegin : TThriftStruct ; |
| begin |
| ReadJSONObjectStart; |
| Init( result); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadStructEnd; |
| begin |
| ReadJSONObjectEnd; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadFieldBegin : TThriftField; |
| var ch : Byte; |
| str : string; |
| begin |
| Init( result); |
| ch := FReader.Peek; |
| if ch = RBRACE[0] |
| then result.Type_ := TType.Stop |
| else begin |
| result.ID := ReadJSONInteger; |
| ReadJSONObjectStart; |
| |
| str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString( FALSE)); |
| result.Type_ := GetTypeIDForTypeName( str); |
| end; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadFieldEnd; |
| begin |
| ReadJSONObjectEnd; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadMapBegin : TThriftMap; |
| var str : string; |
| begin |
| Init( result); |
| ReadJSONArrayStart; |
| |
| str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString(FALSE)); |
| result.KeyType := GetTypeIDForTypeName( str); |
| |
| str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString(FALSE)); |
| result.ValueType := GetTypeIDForTypeName( str); |
| |
| result.Count := ReadJSONInteger; |
| CheckReadBytesAvailable(result); |
| |
| ReadJSONObjectStart; |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadMapEnd; |
| begin |
| ReadJSONObjectEnd; |
| ReadJSONArrayEnd; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadListBegin : TThriftList; |
| var str : string; |
| begin |
| Init( result); |
| ReadJSONArrayStart; |
| |
| str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString(FALSE)); |
| result.ElementType := GetTypeIDForTypeName( str); |
| result.Count := ReadJSONInteger; |
| CheckReadBytesAvailable(result); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadListEnd; |
| begin |
| ReadJSONArrayEnd; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadSetBegin : TThriftSet; |
| var str : string; |
| begin |
| Init( result); |
| ReadJSONArrayStart; |
| |
| str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString(FALSE)); |
| result.ElementType := GetTypeIDForTypeName( str); |
| result.Count := ReadJSONInteger; |
| CheckReadBytesAvailable(result); |
| end; |
| |
| |
| procedure TJSONProtocolImpl.ReadSetEnd; |
| begin |
| ReadJSONArrayEnd; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadBool : Boolean; |
| begin |
| result := (ReadJSONInteger <> 0); |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadByte : ShortInt; |
| begin |
| result := ReadJSONInteger; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadI16 : SmallInt; |
| begin |
| result := ReadJSONInteger; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadI32 : LongInt; |
| begin |
| result := ReadJSONInteger; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadI64 : Int64; |
| begin |
| result := ReadJSONInteger; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadDouble : Double; |
| begin |
| result := ReadJSONDouble; |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadString : string; |
| begin |
| result := SysUtils.TEncoding.UTF8.GetString( ReadJSONString( FALSE)); |
| end; |
| |
| |
| function TJSONProtocolImpl.ReadBinary : TBytes; |
| begin |
| result := ReadJSONBase64; |
| end; |
| |
| |
| function TJSONProtocolImpl.GetMinSerializedSize( const aType : TType) : Integer; |
| // Return the minimum number of bytes a type will consume on the wire |
| begin |
| case aType of |
| TType.Stop: result := 0; |
| TType.Void: result := 0; |
| TType.Bool_: result := 1; |
| TType.Byte_: result := 1; |
| TType.Double_: result := 1; |
| TType.I16: result := 1; |
| TType.I32: result := 1; |
| TType.I64: result := 1; |
| TType.String_: result := 2; // empty string |
| TType.Struct: result := 2; // empty struct |
| TType.Map: result := 2; // empty map |
| TType.Set_: result := 2; // empty set |
| TType.List: result := 2; // empty list |
| else |
| raise TTransportExceptionBadArgs.Create('Unhandled type code'); |
| end; |
| end; |
| |
| |
| |
| //--- init code --- |
| |
| procedure InitBytes( var b : TBytes; aData : array of Byte); |
| begin |
| SetLength( b, Length(aData)); |
| Move( aData, b[0], Length(b)); |
| end; |
| |
| initialization |
| InitBytes( COMMA, [Byte(',')]); |
| InitBytes( COLON, [Byte(':')]); |
| InitBytes( LBRACE, [Byte('{')]); |
| InitBytes( RBRACE, [Byte('}')]); |
| InitBytes( LBRACKET, [Byte('[')]); |
| InitBytes( RBRACKET, [Byte(']')]); |
| InitBytes( QUOTE, [Byte('"')]); |
| InitBytes( BACKSLASH, [Byte('\')]); |
| InitBytes( ESCSEQ, [Byte('\'),Byte('u'),Byte('0'),Byte('0')]); |
| end. |