evernote-thrift 1.23.0 → 1.23.1

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