]> git.proxmox.com Git - ceph.git/blob - ceph/src/jaegertracing/thrift/lib/rb/lib/thrift/protocol/json_protocol.rb
buildsys: switch source download to quincy
[ceph.git] / ceph / src / jaegertracing / thrift / lib / rb / lib / thrift / protocol / json_protocol.rb
1 # encoding: UTF-8
2 #
3 # Licensed to the Apache Software Foundation (ASF) under one
4 # or more contributor license agreements. See the NOTICE file
5 # distributed with this work for additional information
6 # regarding copyright ownership. The ASF licenses this file
7 # to you under the Apache License, Version 2.0 (the
8 # "License"); you may not use this file except in compliance
9 # with the License. You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing,
14 # software distributed under the License is distributed on an
15 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 # KIND, either express or implied. See the License for the
17 # specific language governing permissions and limitations
18 # under the License.
19 #
20
21 require 'base64'
22
23 module Thrift
24 class LookaheadReader
25 def initialize(trans)
26 @trans = trans
27 @hasData = false
28 @data = nil
29 end
30
31 def read
32 if @hasData
33 @hasData = false
34 else
35 @data = @trans.read(1)
36 end
37
38 return @data
39 end
40
41 def peek
42 if !@hasData
43 @data = @trans.read(1)
44 end
45 @hasData = true
46 return @data
47 end
48 end
49
50 #
51 # Class to serve as base JSON context and as base class for other context
52 # implementations
53 #
54 class JSONContext
55 @@kJSONElemSeparator = ','
56 #
57 # Write context data to the trans. Default is to do nothing.
58 #
59 def write(trans)
60 end
61
62 #
63 # Read context data from the trans. Default is to do nothing.
64 #
65 def read(reader)
66 end
67
68 #
69 # Return true if numbers need to be escaped as strings in this context.
70 # Default behavior is to return false.
71 #
72 def escapeNum
73 return false
74 end
75 end
76
77 # Context class for object member key-value pairs
78 class JSONPairContext < JSONContext
79 @@kJSONPairSeparator = ':'
80
81 def initialize
82 @first = true
83 @colon = true
84 end
85
86 def write(trans)
87 if (@first)
88 @first = false
89 @colon = true
90 else
91 trans.write(@colon ? @@kJSONPairSeparator : @@kJSONElemSeparator)
92 @colon = !@colon
93 end
94 end
95
96 def read(reader)
97 if (@first)
98 @first = false
99 @colon = true
100 else
101 ch = (@colon ? @@kJSONPairSeparator : @@kJSONElemSeparator)
102 @colon = !@colon
103 JsonProtocol::read_syntax_char(reader, ch)
104 end
105 end
106
107 # Numbers must be turned into strings if they are the key part of a pair
108 def escapeNum
109 return @colon
110 end
111 end
112
113 # Context class for lists
114 class JSONListContext < JSONContext
115
116 def initialize
117 @first = true
118 end
119
120 def write(trans)
121 if (@first)
122 @first = false
123 else
124 trans.write(@@kJSONElemSeparator)
125 end
126 end
127
128 def read(reader)
129 if (@first)
130 @first = false
131 else
132 JsonProtocol::read_syntax_char(reader, @@kJSONElemSeparator)
133 end
134 end
135 end
136
137 class JsonProtocol < BaseProtocol
138
139 @@kJSONObjectStart = '{'
140 @@kJSONObjectEnd = '}'
141 @@kJSONArrayStart = '['
142 @@kJSONArrayEnd = ']'
143 @@kJSONNewline = '\n'
144 @@kJSONBackslash = '\\'
145 @@kJSONStringDelimiter = '"'
146
147 @@kThriftVersion1 = 1
148
149 @@kThriftNan = "NaN"
150 @@kThriftInfinity = "Infinity"
151 @@kThriftNegativeInfinity = "-Infinity"
152
153 def initialize(trans)
154 super(trans)
155 @context = JSONContext.new
156 @contexts = Array.new
157 @reader = LookaheadReader.new(trans)
158 end
159
160 def get_type_name_for_type_id(id)
161 case id
162 when Types::BOOL
163 "tf"
164 when Types::BYTE
165 "i8"
166 when Types::I16
167 "i16"
168 when Types::I32
169 "i32"
170 when Types::I64
171 "i64"
172 when Types::DOUBLE
173 "dbl"
174 when Types::STRING
175 "str"
176 when Types::STRUCT
177 "rec"
178 when Types::MAP
179 "map"
180 when Types::SET
181 "set"
182 when Types::LIST
183 "lst"
184 else
185 raise NotImplementedError
186 end
187 end
188
189 def get_type_id_for_type_name(name)
190 if (name == "tf")
191 result = Types::BOOL
192 elsif (name == "i8")
193 result = Types::BYTE
194 elsif (name == "i16")
195 result = Types::I16
196 elsif (name == "i32")
197 result = Types::I32
198 elsif (name == "i64")
199 result = Types::I64
200 elsif (name == "dbl")
201 result = Types::DOUBLE
202 elsif (name == "str")
203 result = Types::STRING
204 elsif (name == "rec")
205 result = Types::STRUCT
206 elsif (name == "map")
207 result = Types::MAP
208 elsif (name == "set")
209 result = Types::SET
210 elsif (name == "lst")
211 result = Types::LIST
212 else
213 result = Types::STOP
214 end
215 if (result == Types::STOP)
216 raise NotImplementedError
217 end
218 return result
219 end
220
221 # Static helper functions
222
223 # Read 1 character from the trans and verify that it is the expected character ch.
224 # Throw a protocol exception if it is not.
225 def self.read_syntax_char(reader, ch)
226 ch2 = reader.read
227 if (ch2 != ch)
228 raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected \'#{ch}\' got \'#{ch2}\'.")
229 end
230 end
231
232 # Return true if the character ch is in [-+0-9.Ee]; false otherwise
233 def is_json_numeric(ch)
234 case ch
235 when '+', '-', '.', '0' .. '9', 'E', "e"
236 return true
237 else
238 return false
239 end
240 end
241
242 def push_context(context)
243 @contexts.push(@context)
244 @context = context
245 end
246
247 def pop_context
248 @context = @contexts.pop
249 end
250
251 # Write the character ch as a JSON escape sequence ("\u00xx")
252 def write_json_escape_char(ch)
253 trans.write('\\u')
254 ch_value = ch[0]
255 if (ch_value.kind_of? String)
256 ch_value = ch.bytes.first
257 end
258 trans.write(ch_value.to_s(16).rjust(4,'0'))
259 end
260
261 # Write the character ch as part of a JSON string, escaping as appropriate.
262 def write_json_char(ch)
263 # This table describes the handling for the first 0x30 characters
264 # 0 : escape using "\u00xx" notation
265 # 1 : just output index
266 # <other> : escape using "\<other>" notation
267 kJSONCharTable = [
268 # 0 1 2 3 4 5 6 7 8 9 A B C D E F
269 0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, # 0
270 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 1
271 1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 2
272 ]
273
274 ch_value = ch[0]
275 if (ch_value.kind_of? String)
276 ch_value = ch.bytes.first
277 end
278 if (ch_value >= 0x30)
279 if (ch == @@kJSONBackslash) # Only special character >= 0x30 is '\'
280 trans.write(@@kJSONBackslash)
281 trans.write(@@kJSONBackslash)
282 else
283 trans.write(ch)
284 end
285 else
286 outCh = kJSONCharTable[ch_value];
287 # Check if regular character, backslash escaped, or JSON escaped
288 if outCh.kind_of? String
289 trans.write(@@kJSONBackslash)
290 trans.write(outCh)
291 elsif outCh == 1
292 trans.write(ch)
293 else
294 write_json_escape_char(ch)
295 end
296 end
297 end
298
299 # Write out the contents of the string str as a JSON string, escaping characters as appropriate.
300 def write_json_string(str)
301 @context.write(trans)
302 trans.write(@@kJSONStringDelimiter)
303 str.split('').each do |ch|
304 write_json_char(ch)
305 end
306 trans.write(@@kJSONStringDelimiter)
307 end
308
309 # Write out the contents of the string as JSON string, base64-encoding
310 # the string's contents, and escaping as appropriate
311 def write_json_base64(str)
312 @context.write(trans)
313 trans.write(@@kJSONStringDelimiter)
314 trans.write(Base64.strict_encode64(str))
315 trans.write(@@kJSONStringDelimiter)
316 end
317
318 # Convert the given integer type to a JSON number, or a string
319 # if the context requires it (eg: key in a map pair).
320 def write_json_integer(num)
321 @context.write(trans)
322 escapeNum = @context.escapeNum
323 if (escapeNum)
324 trans.write(@@kJSONStringDelimiter)
325 end
326 trans.write(num.to_s);
327 if (escapeNum)
328 trans.write(@@kJSONStringDelimiter)
329 end
330 end
331
332 # Convert the given double to a JSON string, which is either the number,
333 # "NaN" or "Infinity" or "-Infinity".
334 def write_json_double(num)
335 @context.write(trans)
336 # Normalize output of thrift::to_string for NaNs and Infinities
337 special = false;
338 if (num.nan?)
339 special = true;
340 val = @@kThriftNan;
341 elsif (num.infinite?)
342 special = true;
343 val = @@kThriftInfinity;
344 if (num < 0.0)
345 val = @@kThriftNegativeInfinity;
346 end
347 else
348 val = num.to_s
349 end
350
351 escapeNum = special || @context.escapeNum
352 if (escapeNum)
353 trans.write(@@kJSONStringDelimiter)
354 end
355 trans.write(val)
356 if (escapeNum)
357 trans.write(@@kJSONStringDelimiter)
358 end
359 end
360
361 def write_json_object_start
362 @context.write(trans)
363 trans.write(@@kJSONObjectStart)
364 push_context(JSONPairContext.new);
365 end
366
367 def write_json_object_end
368 pop_context
369 trans.write(@@kJSONObjectEnd)
370 end
371
372 def write_json_array_start
373 @context.write(trans)
374 trans.write(@@kJSONArrayStart)
375 push_context(JSONListContext.new);
376 end
377
378 def write_json_array_end
379 pop_context
380 trans.write(@@kJSONArrayEnd)
381 end
382
383 def write_message_begin(name, type, seqid)
384 write_json_array_start
385 write_json_integer(@@kThriftVersion1)
386 write_json_string(name)
387 write_json_integer(type)
388 write_json_integer(seqid)
389 end
390
391 def write_message_end
392 write_json_array_end
393 end
394
395 def write_struct_begin(name)
396 write_json_object_start
397 end
398
399 def write_struct_end
400 write_json_object_end
401 end
402
403 def write_field_begin(name, type, id)
404 write_json_integer(id)
405 write_json_object_start
406 write_json_string(get_type_name_for_type_id(type))
407 end
408
409 def write_field_end
410 write_json_object_end
411 end
412
413 def write_field_stop; nil; end
414
415 def write_map_begin(ktype, vtype, size)
416 write_json_array_start
417 write_json_string(get_type_name_for_type_id(ktype))
418 write_json_string(get_type_name_for_type_id(vtype))
419 write_json_integer(size)
420 write_json_object_start
421 end
422
423 def write_map_end
424 write_json_object_end
425 write_json_array_end
426 end
427
428 def write_list_begin(etype, size)
429 write_json_array_start
430 write_json_string(get_type_name_for_type_id(etype))
431 write_json_integer(size)
432 end
433
434 def write_list_end
435 write_json_array_end
436 end
437
438 def write_set_begin(etype, size)
439 write_json_array_start
440 write_json_string(get_type_name_for_type_id(etype))
441 write_json_integer(size)
442 end
443
444 def write_set_end
445 write_json_array_end
446 end
447
448 def write_bool(bool)
449 write_json_integer(bool ? 1 : 0)
450 end
451
452 def write_byte(byte)
453 write_json_integer(byte)
454 end
455
456 def write_i16(i16)
457 write_json_integer(i16)
458 end
459
460 def write_i32(i32)
461 write_json_integer(i32)
462 end
463
464 def write_i64(i64)
465 write_json_integer(i64)
466 end
467
468 def write_double(dub)
469 write_json_double(dub)
470 end
471
472 def write_string(str)
473 write_json_string(str)
474 end
475
476 def write_binary(str)
477 write_json_base64(str)
478 end
479
480 ##
481 # Reading functions
482 ##
483
484 # Reads 1 byte and verifies that it matches ch.
485 def read_json_syntax_char(ch)
486 JsonProtocol::read_syntax_char(@reader, ch)
487 end
488
489 # Decodes the four hex parts of a JSON escaped string character and returns
490 # the character via out.
491 #
492 # Note - this only supports Unicode characters in the BMP (U+0000 to U+FFFF);
493 # characters above the BMP are encoded as two escape sequences (surrogate pairs),
494 # which is not yet implemented
495 def read_json_escape_char
496 str = @reader.read
497 str += @reader.read
498 str += @reader.read
499 str += @reader.read
500 if RUBY_VERSION >= '1.9'
501 str.hex.chr(Encoding::UTF_8)
502 else
503 str.hex.chr
504 end
505 end
506
507 # Decodes a JSON string, including unescaping, and returns the string via str
508 def read_json_string(skipContext = false)
509 # This string's characters must match up with the elements in escape_char_vals.
510 # I don't have '/' on this list even though it appears on www.json.org --
511 # it is not in the RFC -> it is. See RFC 4627
512 escape_chars = "\"\\/bfnrt"
513
514 # The elements of this array must match up with the sequence of characters in
515 # escape_chars
516 escape_char_vals = [
517 "\"", "\\", "\/", "\b", "\f", "\n", "\r", "\t",
518 ]
519
520 if !skipContext
521 @context.read(@reader)
522 end
523 read_json_syntax_char(@@kJSONStringDelimiter)
524 ch = ""
525 str = ""
526 while (true)
527 ch = @reader.read
528 if (ch == @@kJSONStringDelimiter)
529 break
530 end
531 if (ch == @@kJSONBackslash)
532 ch = @reader.read
533 if (ch == 'u')
534 ch = read_json_escape_char
535 else
536 pos = escape_chars.index(ch);
537 if (pos.nil?) # not found
538 raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected control char, got \'#{ch}\'.")
539 end
540 ch = escape_char_vals[pos]
541 end
542 end
543 str += ch
544 end
545 return str
546 end
547
548 # Reads a block of base64 characters, decoding it, and returns via str
549 def read_json_base64
550 str = read_json_string
551 m = str.length % 4
552 if m != 0
553 # Add missing padding
554 (4 - m).times do
555 str += '='
556 end
557 end
558 Base64.strict_decode64(str)
559 end
560
561 # Reads a sequence of characters, stopping at the first one that is not
562 # a valid JSON numeric character.
563 def read_json_numeric_chars
564 str = ""
565 while (true)
566 ch = @reader.peek
567 if (!is_json_numeric(ch))
568 break;
569 end
570 ch = @reader.read
571 str += ch
572 end
573 return str
574 end
575
576 # Reads a sequence of characters and assembles them into a number,
577 # returning them via num
578 def read_json_integer
579 @context.read(@reader)
580 if (@context.escapeNum)
581 read_json_syntax_char(@@kJSONStringDelimiter)
582 end
583 str = read_json_numeric_chars
584
585 begin
586 num = Integer(str);
587 rescue
588 raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"")
589 end
590
591 if (@context.escapeNum)
592 read_json_syntax_char(@@kJSONStringDelimiter)
593 end
594
595 return num
596 end
597
598 # Reads a JSON number or string and interprets it as a double.
599 def read_json_double
600 @context.read(@reader)
601 num = 0
602 if (@reader.peek == @@kJSONStringDelimiter)
603 str = read_json_string(true)
604 # Check for NaN, Infinity and -Infinity
605 if (str == @@kThriftNan)
606 num = (+1.0/0.0)/(+1.0/0.0)
607 elsif (str == @@kThriftInfinity)
608 num = +1.0/0.0
609 elsif (str == @@kThriftNegativeInfinity)
610 num = -1.0/0.0
611 else
612 if (!@context.escapeNum)
613 # Raise exception -- we should not be in a string in this case
614 raise ProtocolException.new(ProtocolException::INVALID_DATA, "Numeric data unexpectedly quoted")
615 end
616 begin
617 num = Float(str)
618 rescue
619 raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"")
620 end
621 end
622 else
623 if (@context.escapeNum)
624 # This will throw - we should have had a quote if escapeNum == true
625 read_json_syntax_char(@@kJSONStringDelimiter)
626 end
627 str = read_json_numeric_chars
628 begin
629 num = Float(str)
630 rescue
631 raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"")
632 end
633 end
634 return num
635 end
636
637 def read_json_object_start
638 @context.read(@reader)
639 read_json_syntax_char(@@kJSONObjectStart)
640 push_context(JSONPairContext.new)
641 nil
642 end
643
644 def read_json_object_end
645 read_json_syntax_char(@@kJSONObjectEnd)
646 pop_context
647 nil
648 end
649
650 def read_json_array_start
651 @context.read(@reader)
652 read_json_syntax_char(@@kJSONArrayStart)
653 push_context(JSONListContext.new)
654 nil
655 end
656
657 def read_json_array_end
658 read_json_syntax_char(@@kJSONArrayEnd)
659 pop_context
660 nil
661 end
662
663 def read_message_begin
664 read_json_array_start
665 version = read_json_integer
666 if (version != @@kThriftVersion1)
667 raise ProtocolException.new(ProtocolException::BAD_VERSION, 'Message contained bad version.')
668 end
669 name = read_json_string
670 message_type = read_json_integer
671 seqid = read_json_integer
672 [name, message_type, seqid]
673 end
674
675 def read_message_end
676 read_json_array_end
677 nil
678 end
679
680 def read_struct_begin
681 read_json_object_start
682 nil
683 end
684
685 def read_struct_end
686 read_json_object_end
687 nil
688 end
689
690 def read_field_begin
691 # Check if we hit the end of the list
692 ch = @reader.peek
693 if (ch == @@kJSONObjectEnd)
694 field_type = Types::STOP
695 else
696 field_id = read_json_integer
697 read_json_object_start
698 field_type = get_type_id_for_type_name(read_json_string)
699 end
700 [nil, field_type, field_id]
701 end
702
703 def read_field_end
704 read_json_object_end
705 end
706
707 def read_map_begin
708 read_json_array_start
709 key_type = get_type_id_for_type_name(read_json_string)
710 val_type = get_type_id_for_type_name(read_json_string)
711 size = read_json_integer
712 read_json_object_start
713 [key_type, val_type, size]
714 end
715
716 def read_map_end
717 read_json_object_end
718 read_json_array_end
719 end
720
721 def read_list_begin
722 read_json_array_start
723 [get_type_id_for_type_name(read_json_string), read_json_integer]
724 end
725
726 def read_list_end
727 read_json_array_end
728 end
729
730 def read_set_begin
731 read_json_array_start
732 [get_type_id_for_type_name(read_json_string), read_json_integer]
733 end
734
735 def read_set_end
736 read_json_array_end
737 end
738
739 def read_bool
740 byte = read_byte
741 byte != 0
742 end
743
744 def read_byte
745 read_json_integer
746 end
747
748 def read_i16
749 read_json_integer
750 end
751
752 def read_i32
753 read_json_integer
754 end
755
756 def read_i64
757 read_json_integer
758 end
759
760 def read_double
761 read_json_double
762 end
763
764 def read_string
765 read_json_string
766 end
767
768 def read_binary
769 read_json_base64
770 end
771
772 def to_s
773 "json(#{super.to_s})"
774 end
775 end
776
777 class JsonProtocolFactory < BaseProtocolFactory
778 def get_protocol(trans)
779 return Thrift::JsonProtocol.new(trans)
780 end
781
782 def to_s
783 "json"
784 end
785 end
786 end