blob: 61cad8b627c197cf4616087957baae8aea82feb7 [file] [log] [blame]
(*
* 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.