1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 module thrift.protocol.json;
20 
21 import std.algorithm;
22 import std.array;
23 import std.base64;
24 import std.conv;
25 import std.range;
26 import std..string : format;
27 import std.traits : isIntegral;
28 import std.typetuple : allSatisfy, TypeTuple;
29 import std.utf : toUTF8;
30 import thrift.protocol.base;
31 import thrift.transport.base;
32 
33 alias Base64Impl!('+', '/', Base64.NoPadding) Base64NoPad;
34 
35 /**
36  * Implementation of the Thrift JSON protocol.
37  */
38 final class TJsonProtocol(Transport = TTransport) if (
39   isTTransport!Transport
40 ) : TProtocol {
41   /**
42    * Constructs a new instance.
43    *
44    * Params:
45    *   trans = The transport to use.
46    *   containerSizeLimit = If positive, the container size is limited to the
47    *     given number of items.
48    *   stringSizeLimit = If positive, the string length is limited to the
49    *     given number of bytes.
50    */
51   this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) {
52     trans_ = trans;
53     this.containerSizeLimit = containerSizeLimit;
54     this.stringSizeLimit = stringSizeLimit;
55 
56     context_ = new Context();
57     reader_ = new LookaheadReader(trans);
58   }
59 
60   Transport transport() @property {
61     return trans_;
62   }
63 
64   void reset() {
65     destroy(contextStack_);
66     context_ = new Context();
67     reader_ = new LookaheadReader(trans_);
68   }
69 
70   /**
71    * If positive, limits the number of items of deserialized containers to the
72    * given amount.
73    *
74    * This is useful to avoid allocating excessive amounts of memory when broken
75    * data is received. If the limit is exceeded, a SIZE_LIMIT-type
76    * TProtocolException is thrown.
77    *
78    * Defaults to zero (no limit).
79    */
80   int containerSizeLimit;
81 
82   /**
83    * If positive, limits the length of deserialized strings/binary data to the
84    * given number of bytes.
85    *
86    * This is useful to avoid allocating excessive amounts of memory when broken
87    * data is received. If the limit is exceeded, a SIZE_LIMIT-type
88    * TProtocolException is thrown.
89    *
90    * Note: For binary data, the limit applies to the length of the
91    * Base64-encoded string data, not the resulting byte array.
92    *
93    * Defaults to zero (no limit).
94    */
95   int stringSizeLimit;
96 
97   /*
98    * Writing methods.
99    */
100 
101   void writeBool(bool b) {
102     writeJsonInteger(b ? 1 : 0);
103   }
104 
105   void writeByte(byte b) {
106     writeJsonInteger(b);
107   }
108 
109   void writeI16(short i16) {
110     writeJsonInteger(i16);
111   }
112 
113   void writeI32(int i32) {
114     writeJsonInteger(i32);
115   }
116 
117   void writeI64(long i64) {
118     writeJsonInteger(i64);
119   }
120 
121   void writeDouble(double dub) {
122     context_.write(trans_);
123 
124     string value;
125     if (dub is double.nan) {
126       value = NAN_STRING;
127     } else if (dub is double.infinity) {
128       value = INFINITY_STRING;
129     } else if (dub is -double.infinity) {
130       value = NEG_INFINITY_STRING;
131     }
132 
133     bool escapeNum = value !is null || context_.escapeNum;
134 
135     if (value is null) {
136       /* precision is 17 */
137       value = format("%.17g", dub);
138     }
139 
140     if (escapeNum) trans_.write(STRING_DELIMITER);
141     trans_.write(cast(ubyte[])value);
142     if (escapeNum) trans_.write(STRING_DELIMITER);
143   }
144 
145   void writeString(string str) {
146     context_.write(trans_);
147     trans_.write(STRING_DELIMITER);
148     foreach (c; str) {
149       writeJsonChar(c);
150     }
151     trans_.write(STRING_DELIMITER);
152   }
153 
154   void writeBinary(ubyte[] buf) {
155     context_.write(trans_);
156 
157     trans_.write(STRING_DELIMITER);
158     ubyte[4] b;
159     while (!buf.empty) {
160       auto toWrite = take(buf, 3);
161       Base64NoPad.encode(toWrite, b[]);
162       trans_.write(b[0 .. toWrite.length + 1]);
163       buf.popFrontN(toWrite.length);
164     }
165     trans_.write(STRING_DELIMITER);
166   }
167 
168   void writeMessageBegin(TMessage msg) {
169     writeJsonArrayBegin();
170     writeJsonInteger(THRIFT_JSON_VERSION);
171     writeString(msg.name);
172     writeJsonInteger(cast(byte)msg.type);
173     writeJsonInteger(msg.seqid);
174   }
175 
176   void writeMessageEnd() {
177     writeJsonArrayEnd();
178   }
179 
180   void writeStructBegin(TStruct tstruct) {
181     writeJsonObjectBegin();
182   }
183 
184   void writeStructEnd() {
185     writeJsonObjectEnd();
186   }
187 
188   void writeFieldBegin(TField field) {
189     writeJsonInteger(field.id);
190     writeJsonObjectBegin();
191     writeString(getNameFromTType(field.type));
192   }
193 
194   void writeFieldEnd() {
195     writeJsonObjectEnd();
196   }
197 
198   void writeFieldStop() {}
199 
200   void writeListBegin(TList list) {
201     writeJsonArrayBegin();
202     writeString(getNameFromTType(list.elemType));
203     writeJsonInteger(list.size);
204   }
205 
206   void writeListEnd() {
207     writeJsonArrayEnd();
208   }
209 
210   void writeMapBegin(TMap map) {
211     writeJsonArrayBegin();
212     writeString(getNameFromTType(map.keyType));
213     writeString(getNameFromTType(map.valueType));
214     writeJsonInteger(map.size);
215     writeJsonObjectBegin();
216   }
217 
218   void writeMapEnd() {
219     writeJsonObjectEnd();
220     writeJsonArrayEnd();
221   }
222 
223   void writeSetBegin(TSet set) {
224     writeJsonArrayBegin();
225     writeString(getNameFromTType(set.elemType));
226     writeJsonInteger(set.size);
227   }
228 
229   void writeSetEnd() {
230     writeJsonArrayEnd();
231   }
232 
233 
234   /*
235    * Reading methods.
236    */
237 
238   bool readBool() {
239     return readJsonInteger!byte() ? true : false;
240   }
241 
242   byte readByte() {
243     return readJsonInteger!byte();
244   }
245 
246   short readI16() {
247     return readJsonInteger!short();
248   }
249 
250   int readI32() {
251     return readJsonInteger!int();
252   }
253 
254   long readI64() {
255     return readJsonInteger!long();
256   }
257 
258   double readDouble() {
259     context_.read(reader_);
260 
261     if (reader_.peek() == STRING_DELIMITER) {
262       auto str = readJsonString(true);
263       if (str == NAN_STRING) {
264         return double.nan;
265       }
266       if (str == INFINITY_STRING) {
267         return double.infinity;
268       }
269       if (str == NEG_INFINITY_STRING) {
270         return -double.infinity;
271       }
272 
273       if (!context_.escapeNum) {
274         // Throw exception -- we should not be in a string in this case
275         throw new TProtocolException("Numeric data unexpectedly quoted",
276           TProtocolException.Type.INVALID_DATA);
277       }
278       try {
279         return to!double(str);
280       } catch (ConvException e) {
281         throw new TProtocolException(`Expected numeric value; got "` ~ str ~
282           `".`, TProtocolException.Type.INVALID_DATA);
283       }
284     }
285     else {
286       if (context_.escapeNum) {
287         // This will throw - we should have had a quote if escapeNum == true
288         readJsonSyntaxChar(STRING_DELIMITER);
289       }
290 
291       auto str = readJsonNumericChars();
292       try {
293         return to!double(str);
294       } catch (ConvException e) {
295         throw new TProtocolException(`Expected numeric value; got "` ~ str ~
296           `".`, TProtocolException.Type.INVALID_DATA);
297       }
298     }
299   }
300 
301   string readString() {
302     return readJsonString(false);
303   }
304 
305   ubyte[] readBinary() {
306     return Base64NoPad.decode(readString());
307   }
308 
309   TMessage readMessageBegin() {
310     TMessage msg = void;
311 
312     readJsonArrayBegin();
313 
314     auto ver = readJsonInteger!short();
315     if (ver != THRIFT_JSON_VERSION) {
316       throw new TProtocolException("Message contained bad version.",
317         TProtocolException.Type.BAD_VERSION);
318     }
319 
320     msg.name = readString();
321     msg.type = cast(TMessageType)readJsonInteger!byte();
322     msg.seqid = readJsonInteger!short();
323 
324     return msg;
325   }
326 
327   void readMessageEnd() {
328     readJsonArrayEnd();
329   }
330 
331   TStruct readStructBegin() {
332     readJsonObjectBegin();
333     return TStruct();
334   }
335 
336   void readStructEnd() {
337     readJsonObjectEnd();
338   }
339 
340   TField readFieldBegin() {
341     TField f = void;
342     f.name = null;
343 
344     auto ch = reader_.peek();
345     if (ch == OBJECT_END) {
346       f.type = TType.STOP;
347     } else {
348       f.id = readJsonInteger!short();
349       readJsonObjectBegin();
350       f.type = getTTypeFromName(readString());
351     }
352 
353     return f;
354   }
355 
356   void readFieldEnd() {
357     readJsonObjectEnd();
358   }
359 
360   TList readListBegin() {
361     readJsonArrayBegin();
362     auto type = getTTypeFromName(readString());
363     auto size = readContainerSize();
364     return TList(type, size);
365   }
366 
367   void readListEnd() {
368     readJsonArrayEnd();
369   }
370 
371   TMap readMapBegin() {
372     readJsonArrayBegin();
373     auto keyType = getTTypeFromName(readString());
374     auto valueType = getTTypeFromName(readString());
375     auto size = readContainerSize();
376     readJsonObjectBegin();
377     return TMap(keyType, valueType, size);
378   }
379 
380   void readMapEnd() {
381     readJsonObjectEnd();
382     readJsonArrayEnd();
383   }
384 
385   TSet readSetBegin() {
386     readJsonArrayBegin();
387     auto type = getTTypeFromName(readString());
388     auto size = readContainerSize();
389     return TSet(type, size);
390   }
391 
392   void readSetEnd() {
393     readJsonArrayEnd();
394   }
395 
396 private:
397   void pushContext(Context c) {
398     contextStack_ ~= context_;
399     context_ = c;
400   }
401 
402   void popContext() {
403     context_ = contextStack_.back;
404     contextStack_.popBack();
405     contextStack_.assumeSafeAppend();
406   }
407 
408   /*
409    * Writing functions
410    */
411 
412   // Write the character ch as a Json escape sequence ("\u00xx")
413   void writeJsonEscapeChar(ubyte ch) {
414     trans_.write(ESCAPE_PREFIX);
415     trans_.write(ESCAPE_PREFIX);
416     auto outCh = hexChar(cast(ubyte)(ch >> 4));
417     trans_.write((&outCh)[0 .. 1]);
418     outCh = hexChar(ch);
419     trans_.write((&outCh)[0 .. 1]);
420   }
421 
422   // Write the character ch as part of a Json string, escaping as appropriate.
423   void writeJsonChar(ubyte ch) {
424     if (ch >= 0x30) {
425       if (ch == '\\') { // Only special character >= 0x30 is '\'
426         trans_.write(BACKSLASH);
427         trans_.write(BACKSLASH);
428       } else {
429         trans_.write((&ch)[0 .. 1]);
430       }
431     }
432     else {
433       auto outCh = kJsonCharTable[ch];
434       // Check if regular character, backslash escaped, or Json escaped
435       if (outCh == 1) {
436         trans_.write((&ch)[0 .. 1]);
437       } else if (outCh > 1) {
438         trans_.write(BACKSLASH);
439         trans_.write((&outCh)[0 .. 1]);
440       } else {
441         writeJsonEscapeChar(ch);
442       }
443     }
444   }
445 
446   // Convert the given integer type to a Json number, or a string
447   // if the context requires it (eg: key in a map pair).
448   void writeJsonInteger(T)(T num) if (isIntegral!T) {
449     context_.write(trans_);
450 
451     auto escapeNum = context_.escapeNum();
452     if (escapeNum) trans_.write(STRING_DELIMITER);
453     trans_.write(cast(ubyte[])to!string(num));
454     if (escapeNum) trans_.write(STRING_DELIMITER);
455   }
456 
457   void writeJsonObjectBegin() {
458     context_.write(trans_);
459     trans_.write(OBJECT_BEGIN);
460     pushContext(new PairContext());
461   }
462 
463   void writeJsonObjectEnd() {
464     popContext();
465     trans_.write(OBJECT_END);
466   }
467 
468   void writeJsonArrayBegin() {
469     context_.write(trans_);
470     trans_.write(ARRAY_BEGIN);
471     pushContext(new ListContext());
472   }
473 
474   void writeJsonArrayEnd() {
475     popContext();
476     trans_.write(ARRAY_END);
477   }
478 
479   /*
480    * Reading functions
481    */
482 
483   int readContainerSize() {
484     auto size = readJsonInteger!int();
485     if (size < 0) {
486       throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
487     } else if (containerSizeLimit > 0 && size > containerSizeLimit) {
488       throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
489     }
490     return size;
491   }
492 
493   void readJsonSyntaxChar(ubyte[1] ch) {
494     return readSyntaxChar(reader_, ch);
495   }
496 
497   wchar readJsonEscapeChar() {
498     auto a = reader_.read();
499     auto b = reader_.read();
500     auto c = reader_.read();
501     auto d = reader_.read();
502     return cast(ushort)(
503           (hexVal(a[0]) << 12) + (hexVal(b[0]) << 8) +
504           (hexVal(c[0]) << 4) + hexVal(d[0])
505         );
506   }
507 
508   string readJsonString(bool skipContext = false) {
509     if (!skipContext) context_.read(reader_);
510 
511     readJsonSyntaxChar(STRING_DELIMITER);
512     auto buffer = appender!string();
513 
514     wchar[] wchs;
515     int bytesRead;
516     while (true) {
517       auto ch = reader_.read();
518       if (ch == STRING_DELIMITER) {
519         break;
520       }
521 
522       ++bytesRead;
523       if (stringSizeLimit > 0 && bytesRead > stringSizeLimit) {
524         throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
525       }
526 
527       if (ch == BACKSLASH) {
528         ch = reader_.read();
529         if (ch == ESCAPE_CHAR) {
530           auto wch = readJsonEscapeChar();
531           if (wch >= 0xD800 && wch <= 0xDBFF) {
532             wchs ~= wch;
533           } else if (wch >= 0xDC00 && wch <= 0xDFFF && wchs.length == 0) {
534             throw new TProtocolException("Missing UTF-16 high surrogate.",
535                                          TProtocolException.Type.INVALID_DATA);
536           } else {
537             wchs ~= wch;
538             buffer.put(wchs.toUTF8);
539             wchs = [];
540           }
541           continue;
542         } else {
543           auto pos = countUntil(kEscapeChars[], ch[0]);
544           if (pos == -1) {
545             throw new TProtocolException("Expected control char, got '" ~
546               cast(char)ch[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
547           }
548           ch = kEscapeCharVals[pos];
549         }
550       }
551       if (wchs.length != 0) {
552         throw new TProtocolException("Missing UTF-16 low surrogate.",
553                                      TProtocolException.Type.INVALID_DATA);
554       }
555       buffer.put(ch[0]);
556     }
557 
558     if (wchs.length != 0) {
559       throw new TProtocolException("Missing UTF-16 low surrogate.",
560                                    TProtocolException.Type.INVALID_DATA);
561     }
562     return buffer.data;
563   }
564 
565   // Reads a sequence of characters, stopping at the first one that is not
566   // a valid Json numeric character.
567   string readJsonNumericChars() {
568     string str;
569     while (true) {
570       auto ch = reader_.peek();
571       if (!isJsonNumeric(ch[0])) {
572         break;
573       }
574       reader_.read();
575       str ~= ch;
576     }
577     return str;
578   }
579 
580   // Reads a sequence of characters and assembles them into a number,
581   // returning them via num
582   T readJsonInteger(T)() if (isIntegral!T) {
583     context_.read(reader_);
584     if (context_.escapeNum()) {
585       readJsonSyntaxChar(STRING_DELIMITER);
586     }
587     auto str = readJsonNumericChars();
588     T num;
589     try {
590       num = to!T(str);
591     } catch (ConvException e) {
592       throw new TProtocolException(`Expected numeric value, got "` ~ str ~ `".`,
593         TProtocolException.Type.INVALID_DATA);
594     }
595     if (context_.escapeNum()) {
596       readJsonSyntaxChar(STRING_DELIMITER);
597     }
598     return num;
599   }
600 
601   void readJsonObjectBegin() {
602     context_.read(reader_);
603     readJsonSyntaxChar(OBJECT_BEGIN);
604     pushContext(new PairContext());
605   }
606 
607   void readJsonObjectEnd() {
608     readJsonSyntaxChar(OBJECT_END);
609     popContext();
610   }
611 
612   void readJsonArrayBegin() {
613     context_.read(reader_);
614     readJsonSyntaxChar(ARRAY_BEGIN);
615     pushContext(new ListContext());
616   }
617 
618   void readJsonArrayEnd() {
619     readJsonSyntaxChar(ARRAY_END);
620     popContext();
621   }
622 
623   static {
624     final class LookaheadReader {
625       this(Transport trans) {
626         trans_ = trans;
627       }
628 
629       ubyte[1] read() {
630         if (hasData_) {
631           hasData_ = false;
632         } else {
633           trans_.readAll(data_);
634         }
635         return data_;
636       }
637 
638       ubyte[1] peek() {
639         if (!hasData_) {
640           trans_.readAll(data_);
641           hasData_ = true;
642         }
643         return data_;
644       }
645 
646      private:
647       Transport trans_;
648       bool hasData_;
649       ubyte[1] data_;
650     }
651 
652     /*
653      * Class to serve as base Json context and as base class for other context
654      * implementations
655      */
656     class Context {
657       /**
658        * Write context data to the transport. Default is to do nothing.
659        */
660       void write(Transport trans) {}
661 
662       /**
663        * Read context data from the transport. Default is to do nothing.
664        */
665       void read(LookaheadReader reader) {}
666 
667       /**
668        * Return true if numbers need to be escaped as strings in this context.
669        * Default behavior is to return false.
670        */
671       bool escapeNum() @property {
672         return false;
673       }
674     }
675 
676     // Context class for object member key-value pairs
677     class PairContext : Context {
678       this() {
679         first_ = true;
680         colon_ = true;
681       }
682 
683       override void write(Transport trans) {
684         if (first_) {
685           first_ = false;
686           colon_ = true;
687         } else {
688           trans.write(colon_ ? PAIR_SEP : ELEM_SEP);
689           colon_ = !colon_;
690         }
691       }
692 
693       override void read(LookaheadReader reader) {
694         if (first_) {
695           first_ = false;
696           colon_ = true;
697         } else {
698           auto ch = (colon_ ? PAIR_SEP : ELEM_SEP);
699           colon_ = !colon_;
700           return readSyntaxChar(reader, ch);
701         }
702       }
703 
704       // Numbers must be turned into strings if they are the key part of a pair
705       override bool escapeNum() @property {
706         return colon_;
707       }
708 
709     private:
710       bool first_;
711       bool colon_;
712     }
713 
714     class ListContext : Context {
715       this() {
716         first_ = true;
717       }
718 
719       override void write(Transport trans) {
720         if (first_) {
721           first_ = false;
722         } else {
723           trans.write(ELEM_SEP);
724         }
725       }
726 
727       override void read(LookaheadReader reader) {
728         if (first_) {
729           first_ = false;
730         } else {
731           readSyntaxChar(reader, ELEM_SEP);
732         }
733       }
734 
735     private:
736       bool first_;
737     }
738 
739     // Read 1 character from the transport trans and verify that it is the
740     // expected character ch.
741     // Throw a protocol exception if it is not.
742     void readSyntaxChar(LookaheadReader reader, ubyte[1] ch) {
743       auto ch2 = reader.read();
744       if (ch2 != ch) {
745         throw new TProtocolException("Expected '" ~ cast(char)ch[0] ~ "', got '" ~
746           cast(char)ch2[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
747       }
748     }
749   }
750 
751   // Probably need to implement a better stack at some point.
752   Context[] contextStack_;
753   Context context_;
754 
755   Transport trans_;
756   LookaheadReader reader_;
757 }
758 
759 /**
760  * TJsonProtocol construction helper to avoid having to explicitly specify
761  * the transport type, i.e. to allow the constructor being called using IFTI
762  * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla
763  * enhancement requet 6082)).
764  */
765 TJsonProtocol!Transport tJsonProtocol(Transport)(Transport trans,
766   int containerSizeLimit = 0, int stringSizeLimit = 0
767 ) if (isTTransport!Transport) {
768   return new TJsonProtocol!Transport(trans, containerSizeLimit, stringSizeLimit);
769 }
770 
771 unittest {
772   import std.exception;
773   import thrift.transport.memory;
774 
775   // Check the message header format.
776   auto buf = new TMemoryBuffer;
777   auto json = tJsonProtocol(buf);
778   json.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
779   json.writeMessageEnd();
780 
781   auto header = new ubyte[13];
782   buf.readAll(header);
783   enforce(cast(char[])header == `[1,"foo",1,0]`);
784 }
785 
786 unittest {
787   import std.exception;
788   import thrift.transport.memory;
789 
790   // Check that short binary data is read correctly (the Thrift JSON format
791   // does not include padding chars in the Base64 encoded data).
792   auto buf = new TMemoryBuffer;
793   auto json = tJsonProtocol(buf);
794   json.writeBinary([1, 2]);
795   json.reset();
796   enforce(json.readBinary() == [1, 2]);
797 }
798 
799 unittest {
800   import std.exception;
801   import thrift.transport.memory;
802 
803   auto buf = new TMemoryBuffer(cast(ubyte[])"\"\\u0e01 \\ud835\\udd3e\"");
804   auto json = tJsonProtocol(buf);
805   auto str = json.readString();
806   enforce(str == "ก 𝔾");
807 }
808 
809 unittest {
810   // Thrown if low surrogate is missing.
811   import std.exception;
812   import thrift.transport.memory;
813 
814   auto buf = new TMemoryBuffer(cast(ubyte[])"\"\\u0e01 \\ud835\"");
815   auto json = tJsonProtocol(buf);
816   assertThrown!TProtocolException(json.readString());
817 }
818 
819 unittest {
820   // Thrown if high surrogate is missing.
821   import std.exception;
822   import thrift.transport.memory;
823 
824   auto buf = new TMemoryBuffer(cast(ubyte[])"\"\\u0e01 \\udd3e\"");
825   auto json = tJsonProtocol(buf);
826   assertThrown!TProtocolException(json.readString());
827 }
828 
829 unittest {
830   import thrift.internal.test.protocol;
831   testContainerSizeLimit!(TJsonProtocol!())();
832   testStringSizeLimit!(TJsonProtocol!())();
833 }
834 
835 /**
836  * TProtocolFactory creating a TJsonProtocol instance for passed in
837  * transports.
838  *
839  * The optional Transports template tuple parameter can be used to specify
840  * one or more TTransport implementations to specifically instantiate
841  * TJsonProtocol for. If the actual transport types encountered at
842  * runtime match one of the transports in the list, a specialized protocol
843  * instance is created. Otherwise, a generic TTransport version is used.
844  */
845 class TJsonProtocolFactory(Transports...) if (
846   allSatisfy!(isTTransport, Transports)
847 ) : TProtocolFactory {
848   TProtocol getProtocol(TTransport trans) const {
849     foreach (Transport; TypeTuple!(Transports, TTransport)) {
850       auto concreteTrans = cast(Transport)trans;
851       if (concreteTrans) {
852         auto p = new TJsonProtocol!Transport(concreteTrans);
853         return p;
854       }
855     }
856     throw new TProtocolException(
857       "Passed null transport to TJsonProtocolFactoy.");
858   }
859 }
860 
861 private {
862   immutable ubyte[1] OBJECT_BEGIN = '{';
863   immutable ubyte[1] OBJECT_END = '}';
864   immutable ubyte[1] ARRAY_BEGIN = '[';
865   immutable ubyte[1] ARRAY_END = ']';
866   immutable ubyte[1] NEWLINE = '\n';
867   immutable ubyte[1] PAIR_SEP = ':';
868   immutable ubyte[1] ELEM_SEP = ',';
869   immutable ubyte[1] BACKSLASH = '\\';
870   immutable ubyte[1] STRING_DELIMITER = '"';
871   immutable ubyte[1] ZERO_CHAR = '0';
872   immutable ubyte[1] ESCAPE_CHAR = 'u';
873   immutable ubyte[4] ESCAPE_PREFIX = cast(ubyte[4])r"\u00";
874 
875   enum THRIFT_JSON_VERSION = 1;
876 
877   immutable NAN_STRING = "NaN";
878   immutable INFINITY_STRING = "Infinity";
879   immutable NEG_INFINITY_STRING = "-Infinity";
880 
881   string getNameFromTType(TType typeID) {
882     final switch (typeID) {
883       case TType.BOOL:
884         return "tf";
885       case TType.BYTE:
886         return "i8";
887       case TType.I16:
888         return "i16";
889       case TType.I32:
890         return "i32";
891       case TType.I64:
892         return "i64";
893       case TType.DOUBLE:
894         return "dbl";
895       case TType.STRING:
896         return "str";
897       case TType.STRUCT:
898         return "rec";
899       case TType.MAP:
900         return "map";
901       case TType.LIST:
902         return "lst";
903       case TType.SET:
904         return "set";
905       case TType.STOP: goto case;
906       case TType.VOID:
907         assert(false, "Invalid type passed.");
908     }
909   }
910 
911   TType getTTypeFromName(string name) {
912     TType result;
913     if (name.length > 1) {
914       switch (name[0]) {
915         case 'd':
916           result = TType.DOUBLE;
917           break;
918         case 'i':
919           switch (name[1]) {
920             case '8':
921               result = TType.BYTE;
922               break;
923             case '1':
924               result = TType.I16;
925               break;
926             case '3':
927               result = TType.I32;
928               break;
929             case '6':
930               result = TType.I64;
931               break;
932             default:
933               // Do nothing.
934           }
935           break;
936         case 'l':
937           result = TType.LIST;
938           break;
939         case 'm':
940           result = TType.MAP;
941           break;
942         case 'r':
943           result = TType.STRUCT;
944           break;
945         case 's':
946           if (name[1] == 't') {
947             result = TType.STRING;
948           }
949           else if (name[1] == 'e') {
950             result = TType.SET;
951           }
952           break;
953         case 't':
954           result = TType.BOOL;
955           break;
956         default:
957           // Do nothing.
958       }
959     }
960     if (result == TType.STOP) {
961       throw new TProtocolException("Unrecognized type",
962         TProtocolException.Type.NOT_IMPLEMENTED);
963     }
964     return result;
965   }
966 
967   // This table describes the handling for the first 0x30 characters
968   //  0 : escape using "\u00xx" notation
969   //  1 : just output index
970   // <other> : escape using "\<other>" notation
971   immutable ubyte[0x30] kJsonCharTable = [
972   //  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
973       0,  0,  0,  0,  0,  0,  0,  0,'b','t','n',  0,'f','r',  0,  0, // 0
974       0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 1
975       1,  1,'"',  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1, // 2
976   ];
977 
978   // This string's characters must match up with the elements in kEscapeCharVals.
979   // I don't have '/' on this list even though it appears on www.json.org --
980   // it is not in the RFC
981   immutable kEscapeChars = cast(ubyte[7]) `"\\bfnrt`;
982 
983   // The elements of this array must match up with the sequence of characters in
984   // kEscapeChars
985   immutable ubyte[7] kEscapeCharVals = [
986     '"', '\\', '\b', '\f', '\n', '\r', '\t',
987   ];
988 
989   // Return the integer value of a hex character ch.
990   // Throw a protocol exception if the character is not [0-9a-f].
991   ubyte hexVal(ubyte ch) {
992     if ((ch >= '0') && (ch <= '9')) {
993       return cast(ubyte)(ch - '0');
994     } else if ((ch >= 'a') && (ch <= 'f')) {
995       return cast(ubyte)(ch - 'a' + 10);
996     }
997     else {
998       throw new TProtocolException("Expected hex val ([0-9a-f]), got '" ~
999         ch ~ "'.", TProtocolException.Type.INVALID_DATA);
1000     }
1001   }
1002 
1003   // Return the hex character representing the integer val. The value is masked
1004   // to make sure it is in the correct range.
1005   ubyte hexChar(ubyte val) {
1006     val &= 0x0F;
1007     if (val < 10) {
1008       return cast(ubyte)(val + '0');
1009     } else {
1010       return cast(ubyte)(val - 10 + 'a');
1011     }
1012   }
1013 
1014   // Return true if the character ch is in [-+0-9.Ee]; false otherwise
1015   bool isJsonNumeric(ubyte ch) {
1016     switch (ch) {
1017       case '+':
1018       case '-':
1019       case '.':
1020       case '0':
1021       case '1':
1022       case '2':
1023       case '3':
1024       case '4':
1025       case '5':
1026       case '6':
1027       case '7':
1028       case '8':
1029       case '9':
1030       case 'E':
1031       case 'e':
1032         return true;
1033       default:
1034         return false;
1035     }
1036   }
1037 }