thrift 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +203 -40
  3. data/ext/binary_protocol_accelerated.c +130 -40
  4. data/ext/bytes.c +14 -0
  5. data/ext/compact_protocol.c +184 -91
  6. data/ext/constants.h +3 -0
  7. data/ext/extconf.rb +12 -4
  8. data/ext/macros.h +11 -0
  9. data/ext/memory_buffer.c +67 -12
  10. data/ext/struct.c +192 -71
  11. data/ext/thrift_native.c +7 -0
  12. data/lib/thrift/bytes.rb +6 -3
  13. data/lib/thrift/client.rb +31 -33
  14. data/lib/thrift/exceptions.rb +11 -8
  15. data/lib/thrift/multiplexed_processor.rb +17 -8
  16. data/lib/thrift/processor.rb +25 -3
  17. data/lib/thrift/protocol/base_protocol.rb +66 -31
  18. data/lib/thrift/protocol/binary_protocol.rb +80 -37
  19. data/lib/thrift/protocol/binary_protocol_accelerated.rb +7 -2
  20. data/lib/thrift/protocol/compact_protocol.rb +204 -88
  21. data/lib/thrift/protocol/header_protocol.rb +15 -3
  22. data/lib/thrift/protocol/json_protocol.rb +158 -109
  23. data/lib/thrift/protocol/multiplexed_protocol.rb +3 -3
  24. data/lib/thrift/protocol/protocol_decorator.rb +6 -2
  25. data/lib/thrift/serializer/deserializer.rb +1 -0
  26. data/lib/thrift/serializer/serializer.rb +7 -0
  27. data/lib/thrift/server/base_server.rb +11 -2
  28. data/lib/thrift/server/nonblocking_server.rb +69 -20
  29. data/lib/thrift/server/rack_application.rb +69 -0
  30. data/lib/thrift/server/simple_server.rb +27 -15
  31. data/lib/thrift/server/thin_http_server.rb +21 -44
  32. data/lib/thrift/server/thread_pool_server.rb +21 -17
  33. data/lib/thrift/server/threaded_server.rb +22 -18
  34. data/lib/thrift/struct.rb +34 -18
  35. data/lib/thrift/struct_union.rb +37 -36
  36. data/lib/thrift/thrift_native.rb +2 -1
  37. data/lib/thrift/transport/base_server_transport.rb +3 -0
  38. data/lib/thrift/transport/base_transport.rb +26 -4
  39. data/lib/thrift/transport/buffered_transport.rb +5 -4
  40. data/lib/thrift/transport/framed_transport.rb +33 -7
  41. data/lib/thrift/transport/header_transport.rb +159 -33
  42. data/lib/thrift/transport/http_client_transport.rb +28 -14
  43. data/lib/thrift/transport/io_stream_transport.rb +3 -1
  44. data/lib/thrift/transport/memory_buffer_transport.rb +14 -9
  45. data/lib/thrift/transport/server_socket.rb +10 -8
  46. data/lib/thrift/transport/socket.rb +85 -48
  47. data/lib/thrift/transport/ssl_server_socket.rb +67 -6
  48. data/lib/thrift/transport/ssl_socket.rb +73 -14
  49. data/lib/thrift/transport/unix_server_socket.rb +6 -2
  50. data/lib/thrift/transport/unix_socket.rb +5 -6
  51. data/lib/thrift/types.rb +23 -22
  52. data/lib/thrift/union.rb +21 -11
  53. data/lib/thrift/uuid.rb +9 -8
  54. data/lib/thrift.rb +40 -39
  55. metadata +7 -266
  56. data/benchmark/Benchmark.thrift +0 -24
  57. data/benchmark/benchmark.rb +0 -285
  58. data/benchmark/client.rb +0 -117
  59. data/benchmark/server.rb +0 -120
  60. data/benchmark/thin_server.rb +0 -45
  61. data/lib/thrift/server/mongrel_http_server.rb +0 -60
  62. data/spec/BaseService.thrift +0 -27
  63. data/spec/ExtendedService.thrift +0 -25
  64. data/spec/Referenced.thrift +0 -44
  65. data/spec/ThriftNamespacedSpec.thrift +0 -53
  66. data/spec/ThriftSpec.thrift +0 -187
  67. data/spec/base_protocol_spec.rb +0 -224
  68. data/spec/base_transport_spec.rb +0 -387
  69. data/spec/binary_protocol_accelerated_spec.rb +0 -46
  70. data/spec/binary_protocol_spec.rb +0 -72
  71. data/spec/binary_protocol_spec_shared.rb +0 -445
  72. data/spec/bytes_spec.rb +0 -99
  73. data/spec/client_spec.rb +0 -166
  74. data/spec/compact_protocol_spec.rb +0 -196
  75. data/spec/constants_demo_spec.rb +0 -101
  76. data/spec/exception_spec.rb +0 -140
  77. data/spec/flat_spec.rb +0 -62
  78. data/spec/header_protocol_spec.rb +0 -475
  79. data/spec/header_transport_spec.rb +0 -386
  80. data/spec/http_client_spec.rb +0 -159
  81. data/spec/json_protocol_spec.rb +0 -552
  82. data/spec/namespaced_spec.rb +0 -66
  83. data/spec/nonblocking_server_spec.rb +0 -361
  84. data/spec/processor_spec.rb +0 -79
  85. data/spec/serializer_spec.rb +0 -66
  86. data/spec/server_socket_spec.rb +0 -84
  87. data/spec/server_spec.rb +0 -186
  88. data/spec/socket_spec.rb +0 -67
  89. data/spec/socket_spec_shared.rb +0 -167
  90. data/spec/spec_helper.rb +0 -69
  91. data/spec/ssl_server_socket_spec.rb +0 -45
  92. data/spec/ssl_socket_spec.rb +0 -87
  93. data/spec/struct_nested_containers_spec.rb +0 -190
  94. data/spec/struct_spec.rb +0 -397
  95. data/spec/support/header_protocol_helper.rb +0 -54
  96. data/spec/thin_http_server_spec.rb +0 -126
  97. data/spec/types_spec.rb +0 -117
  98. data/spec/union_spec.rb +0 -272
  99. data/spec/unix_socket_spec.rb +0 -115
  100. data/spec/uuid_validation_spec.rb +0 -238
  101. data/test/fuzz/Makefile.am +0 -173
  102. data/test/fuzz/README.md +0 -149
  103. data/test/fuzz/fuzz_common.rb +0 -95
  104. data/test/fuzz/fuzz_parse_binary_protocol.rb +0 -22
  105. data/test/fuzz/fuzz_parse_binary_protocol_accelerated.rb +0 -22
  106. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +0 -22
  107. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +0 -22
  108. data/test/fuzz/fuzz_parse_compact_protocol.rb +0 -22
  109. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +0 -22
  110. data/test/fuzz/fuzz_parse_json_protocol.rb +0 -22
  111. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +0 -22
  112. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +0 -22
  113. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +0 -22
  114. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +0 -22
  115. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +0 -22
  116. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +0 -22
  117. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +0 -22
  118. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +0 -22
  119. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +0 -22
  120. data/test/fuzz/fuzz_tracer.rb +0 -28
data/ext/thrift_native.c CHANGED
@@ -92,6 +92,7 @@ ID skip_method_id;
92
92
  ID write_method_id;
93
93
  ID read_all_method_id;
94
94
  ID read_into_buffer_method_id;
95
+ ID reset_message_size_method_id;
95
96
  ID force_binary_encoding_id;
96
97
  ID convert_to_utf8_byte_buffer_id;
97
98
  ID convert_to_string_id;
@@ -99,8 +100,10 @@ ID convert_to_string_id;
99
100
  // constant ids
100
101
  ID fields_const_id;
101
102
  ID transport_ivar_id;
103
+ ID reset_message_size_ivar_id;
102
104
  ID strict_read_ivar_id;
103
105
  ID strict_write_ivar_id;
106
+ ID max_string_size_ivar_id;
104
107
 
105
108
  // cached symbols
106
109
  VALUE type_sym;
@@ -121,6 +124,7 @@ int PROTOERR_BAD_VERSION;
121
124
  int PROTOERR_NOT_IMPLEMENTED;
122
125
  int PROTOERR_DEPTH_LIMIT;
123
126
 
127
+ // cppcheck-suppress unusedFunction
124
128
  RUBY_FUNC_EXPORTED void Init_thrift_native(void) {
125
129
  // cached classes
126
130
  thrift_module = rb_const_get(rb_cObject, rb_intern("Thrift"));
@@ -199,6 +203,7 @@ RUBY_FUNC_EXPORTED void Init_thrift_native(void) {
199
203
  write_method_id = rb_intern("write");
200
204
  read_all_method_id = rb_intern("read_all");
201
205
  read_into_buffer_method_id = rb_intern("read_into_buffer");
206
+ reset_message_size_method_id = rb_intern("reset_message_size");
202
207
  force_binary_encoding_id = rb_intern("force_binary_encoding");
203
208
  convert_to_utf8_byte_buffer_id = rb_intern("convert_to_utf8_byte_buffer");
204
209
  convert_to_string_id = rb_intern("convert_to_string");
@@ -206,8 +211,10 @@ RUBY_FUNC_EXPORTED void Init_thrift_native(void) {
206
211
  // constant ids
207
212
  fields_const_id = rb_intern("FIELDS");
208
213
  transport_ivar_id = rb_intern("@trans");
214
+ reset_message_size_ivar_id = rb_intern("@reset_message_size");
209
215
  strict_read_ivar_id = rb_intern("@strict_read");
210
216
  strict_write_ivar_id = rb_intern("@strict_write");
217
+ max_string_size_ivar_id = rb_intern("@max_string_size");
211
218
 
212
219
  // cached symbols
213
220
  type_sym = ID2SYM(rb_intern("type"));
data/lib/thrift/bytes.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # Licensed to the Apache Software Foundation (ASF) under one
4
5
  # or more contributor license agreements. See the NOTICE file
@@ -28,21 +29,23 @@ module Thrift
28
29
  # Returns a String with BINARY encoding, filled with null characters
29
30
  # if size is greater than zero
30
31
  def self.empty_byte_buffer(size = nil)
31
- if (size && size > 0)
32
+ if size && size > 0
32
33
  "\0".b * size
33
34
  else
34
- ''.b
35
+ "".b
35
36
  end
36
37
  end
37
38
 
38
39
  # Forces the encoding of the buffer to BINARY. If the buffer
39
- # passed is frozen, then it will be duplicated.
40
+ # passed is frozen and not already BINARY, then it will be duplicated.
40
41
  #
41
42
  # buffer - The String to force the encoding of.
42
43
  #
43
44
  # Returns the String passed with an encoding of BINARY; returned
44
45
  # String may be a duplicate.
45
46
  def self.force_binary_encoding(buffer)
47
+ return buffer if buffer.encoding == Encoding::BINARY
48
+
46
49
  buffer = buffer.dup if buffer.frozen?
47
50
  buffer.force_encoding(Encoding::BINARY)
48
51
  end
data/lib/thrift/client.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -30,42 +31,22 @@ module Thrift
30
31
  end
31
32
 
32
33
  def send_message(name, args_class, args = {})
33
- seqid = next_seqid!
34
- @oprot.write_message_begin(name, MessageTypes::CALL, seqid)
35
- send_message_args(args_class, args)
36
- @pending_seqids << seqid
34
+ write_message(name, MessageTypes::CALL, args_class, args)
37
35
  end
38
36
 
39
37
  def send_oneway_message(name, args_class, args = {})
40
- @oprot.write_message_begin(name, MessageTypes::ONEWAY, next_seqid!)
41
- send_message_args(args_class, args)
38
+ write_message(name, MessageTypes::ONEWAY, args_class, args)
42
39
  end
43
40
 
44
41
  def send_message_args(args_class, args)
45
- data = args_class.new
46
- args.each do |k, v|
47
- data.send("#{k.to_s}=", v)
48
- end
49
- begin
50
- data.write(@oprot)
51
- rescue StandardError => e
52
- @oprot.trans.close
53
- raise e
54
- end
55
- @oprot.write_message_end
56
- @oprot.trans.flush
42
+ write_message(nil, nil, args_class, args)
57
43
  end
58
44
 
59
- def receive_message_begin()
45
+ def receive_message_begin
60
46
  fname, mtype, rseqid = @iprot.read_message_begin
61
47
  [fname, mtype, rseqid]
62
48
  end
63
49
 
64
- def reply_seqid(rseqid)
65
- expected_seqid = dequeue_pending_seqid
66
- !expected_seqid.nil? && rseqid == expected_seqid
67
- end
68
-
69
50
  def validate_message_begin(fname, mtype, rseqid, expected_name)
70
51
  expected_seqid = dequeue_pending_seqid
71
52
 
@@ -76,14 +57,14 @@ module Thrift
76
57
  if mtype != MessageTypes::REPLY
77
58
  raise ApplicationException.new(
78
59
  ApplicationException::INVALID_MESSAGE_TYPE,
79
- "#{expected_name} failed: invalid message type"
60
+ "#{expected_name} failed: invalid message type",
80
61
  )
81
62
  end
82
63
 
83
64
  if fname != expected_name
84
65
  raise ApplicationException.new(
85
66
  ApplicationException::WRONG_METHOD_NAME,
86
- "#{expected_name} failed: wrong method name"
67
+ "#{expected_name} failed: wrong method name",
87
68
  )
88
69
  end
89
70
 
@@ -91,7 +72,7 @@ module Thrift
91
72
 
92
73
  raise ApplicationException.new(
93
74
  ApplicationException::BAD_SEQUENCE_ID,
94
- "#{expected_name} failed: out of sequence response"
75
+ "#{expected_name} failed: out of sequence response",
95
76
  )
96
77
  end
97
78
 
@@ -102,14 +83,31 @@ module Thrift
102
83
  result
103
84
  end
104
85
 
105
- def handle_exception(mtype)
106
- if mtype == MessageTypes::EXCEPTION
107
- dequeue_pending_seqid
108
- raise_application_exception
86
+ private
87
+
88
+ def write_message(name, type, args_class, args)
89
+ data = args_class.new
90
+ args.each do |k, v|
91
+ data.send("#{k}=", v)
109
92
  end
110
- end
93
+ seqid = next_seqid! unless name.nil?
111
94
 
112
- private
95
+ flush_result = begin
96
+ @oprot.write_message_begin(name, type, seqid) unless name.nil?
97
+ data.write(@oprot)
98
+ @oprot.write_message_end
99
+ @oprot.trans.flush
100
+ rescue StandardError
101
+ begin
102
+ @oprot.trans.close
103
+ rescue StandardError
104
+ # Preserve the original send error.
105
+ end
106
+ raise
107
+ end
108
+
109
+ type == MessageTypes::CALL ? @pending_seqids << seqid : flush_result
110
+ end
113
111
 
114
112
  def next_seqid!
115
113
  seqid = @seqid
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -18,6 +19,8 @@
18
19
  #
19
20
 
20
21
  module Thrift
22
+ DEFAULT_RECURSION_DEPTH = 64
23
+
21
24
  class Exception < StandardError
22
25
  def initialize(message)
23
26
  super
@@ -28,7 +31,6 @@ module Thrift
28
31
  end
29
32
 
30
33
  class ApplicationException < Exception
31
-
32
34
  UNKNOWN = 0
33
35
  UNKNOWN_METHOD = 1
34
36
  INVALID_MESSAGE_TYPE = 2
@@ -48,10 +50,11 @@ module Thrift
48
50
  @type = type
49
51
  end
50
52
 
51
- def read(iprot)
53
+ def read(iprot, remaining_depth = DEFAULT_RECURSION_DEPTH)
54
+ raise ProtocolException.new(ProtocolException::DEPTH_LIMIT, "Maximum recursion depth exceeded") if remaining_depth <= 0
52
55
  iprot.read_struct_begin
53
56
  while true
54
- fname, ftype, fid = iprot.read_field_begin
57
+ _, ftype, fid = iprot.read_field_begin
55
58
  if ftype == Types::STOP
56
59
  break
57
60
  end
@@ -67,21 +70,21 @@ module Thrift
67
70
  iprot.read_struct_end
68
71
  end
69
72
 
70
- def write(oprot)
71
- oprot.write_struct_begin('Thrift::ApplicationException')
73
+ def write(oprot, remaining_depth = DEFAULT_RECURSION_DEPTH)
74
+ raise ProtocolException.new(ProtocolException::DEPTH_LIMIT, "Maximum recursion depth exceeded") if remaining_depth <= 0
75
+ oprot.write_struct_begin("Thrift::ApplicationException")
72
76
  unless @message.nil?
73
- oprot.write_field_begin('message', Types::STRING, 1)
77
+ oprot.write_field_begin("message", Types::STRING, 1)
74
78
  oprot.write_string(@message)
75
79
  oprot.write_field_end
76
80
  end
77
81
  unless @type.nil?
78
- oprot.write_field_begin('type', Types::I32, 2)
82
+ oprot.write_field_begin("type", Types::I32, 2)
79
83
  oprot.write_i32(@type)
80
84
  oprot.write_field_end
81
85
  end
82
86
  oprot.write_field_stop
83
87
  oprot.write_struct_end
84
88
  end
85
-
86
89
  end
87
90
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -16,24 +17,33 @@
16
17
  # specific language governing permissions and limitations
17
18
  # under the License.
18
19
 
19
- require 'thrift/protocol/protocol_decorator'
20
- require 'thrift/protocol/base_protocol'
20
+ require "thrift/protocol/protocol_decorator"
21
+ require "thrift/protocol/base_protocol"
21
22
 
22
23
  module Thrift
23
24
  class MultiplexedProcessor
24
25
  def initialize
25
26
  @actual_processors = {}
27
+ @default_processor = nil
26
28
  end
27
29
 
28
30
  def register_processor(service_name, processor)
29
31
  @actual_processors[service_name] = processor
30
32
  end
31
33
 
34
+ def register_default(processor)
35
+ @default_processor = processor
36
+ end
37
+
32
38
  def process(iprot, oprot)
33
39
  name, type, seqid = iprot.read_message_begin
34
40
  check_type(type)
35
- check_separator(name)
36
- service_name, method = name.split(':')
41
+ if name.count(":") < 1
42
+ check_default_processor(name)
43
+ return @default_processor.process(StoredMessageProtocol.new(iprot, [name, type, seqid]), oprot)
44
+ end
45
+
46
+ service_name, method = name.split(":", 2)
37
47
  processor(service_name).process(StoredMessageProtocol.new(iprot, [method, type, seqid]), oprot)
38
48
  end
39
49
 
@@ -49,19 +59,18 @@ module Thrift
49
59
 
50
60
  def check_type(type)
51
61
  unless [MessageTypes::CALL, MessageTypes::ONEWAY].include?(type)
52
- raise Thrift::Exception.new('This should not have happened!?')
62
+ raise Thrift::Exception.new("This should not have happened!?")
53
63
  end
54
64
  end
55
65
 
56
- def check_separator(name)
57
- if name.count(':') < 1
66
+ def check_default_processor(name)
67
+ unless @default_processor
58
68
  raise Thrift::Exception.new("Service name not found in message name: #{name}. Did you forget to use a Thrift::Protocol::MultiplexedProtocol in your client?")
59
69
  end
60
70
  end
61
71
  end
62
72
 
63
73
  class StoredMessageProtocol < BaseProtocol
64
-
65
74
  include ProtocolDecorator
66
75
 
67
76
  def initialize(protocol, message_begin)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -17,10 +18,15 @@
17
18
  # under the License.
18
19
  #
19
20
 
20
- require 'logger'
21
+ require "logger"
22
+ require "thrift/protocol/base_protocol"
21
23
 
22
24
  module Thrift
23
25
  module Processor
26
+ class ArgumentProtocolException < ProtocolException
27
+ end
28
+ private_constant :ArgumentProtocolException
29
+
24
30
  def initialize(handler, logger = nil)
25
31
  @handler = handler
26
32
  if logger.nil?
@@ -33,11 +39,25 @@ module Thrift
33
39
 
34
40
  def process(iprot, oprot)
35
41
  name, type, seqid = iprot.read_message_begin
42
+ unless type == MessageTypes::CALL || type == MessageTypes::ONEWAY
43
+ iprot.skip(Types::STRUCT)
44
+ iprot.read_message_end
45
+ x = ApplicationException.new(
46
+ ApplicationException::INVALID_MESSAGE_TYPE,
47
+ "Invalid message type #{type} for function #{name}",
48
+ )
49
+ write_error(x, oprot, name, seqid)
50
+ return false
51
+ end
52
+
36
53
  if respond_to?("process_#{name}")
37
54
  begin
38
55
  send("process_#{name}", seqid, iprot, oprot)
56
+ rescue ArgumentProtocolException => e
57
+ x = ApplicationException.new(ApplicationException::PROTOCOL_ERROR, e.message)
58
+ write_error(x, oprot, name, seqid)
39
59
  rescue => e
40
- x = ApplicationException.new(ApplicationException::INTERNAL_ERROR, 'Internal error')
60
+ x = ApplicationException.new(ApplicationException::INTERNAL_ERROR, "Internal error")
41
61
  @logger.debug "Internal error : #{e.message}\n#{e.backtrace.join("\n")}"
42
62
  write_error(x, oprot, name, seqid)
43
63
  end
@@ -45,7 +65,7 @@ module Thrift
45
65
  else
46
66
  iprot.skip(Types::STRUCT)
47
67
  iprot.read_message_end
48
- x = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, 'Unknown function '+name)
68
+ x = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, "Unknown function " + name)
49
69
  write_error(x, oprot, name, seqid)
50
70
  false
51
71
  end
@@ -56,6 +76,8 @@ module Thrift
56
76
  args.read(iprot)
57
77
  iprot.read_message_end
58
78
  args
79
+ rescue ProtocolException => e
80
+ raise ArgumentProtocolException.new(e.type, e.message)
59
81
  end
60
82
 
61
83
  def write_result(result, oprot, name, seqid)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -18,11 +19,11 @@
18
19
  #
19
20
 
20
21
  # this require is to make generated struct definitions happy
21
- require 'set'
22
+ require "set"
23
+ require "thrift/exceptions"
22
24
 
23
25
  module Thrift
24
26
  class ProtocolException < Exception
25
-
26
27
  UNKNOWN = 0
27
28
  INVALID_DATA = 1
28
29
  NEGATIVE_SIZE = 2
@@ -40,6 +41,14 @@ module Thrift
40
41
  end
41
42
 
42
43
  class BaseProtocol
44
+ MAX_CONTAINER_SIZE = (1 << 31) - 1
45
+
46
+ # The longest string or binary field BinaryProtocol and CompactProtocol read
47
+ # by default: the frame size limit the framed transports apply. A string's
48
+ # length is the read size handed to the transport, and an unframed transport
49
+ # has no frame to bound it. Pass max_string_size: nil to read strings of any
50
+ # length.
51
+ DEFAULT_MAX_STRING_SIZE = 16_384_000
43
52
 
44
53
  attr_reader :trans
45
54
 
@@ -48,10 +57,24 @@ module Thrift
48
57
  end
49
58
 
50
59
  def native?
51
- puts "wrong method is being called!"
52
60
  false
53
61
  end
54
62
 
63
+ # Raises a SIZE_LIMIT ProtocolException when a declared string length is over
64
+ # the protocol's max_string_size.
65
+ def check_string_size(size)
66
+ max = @max_string_size
67
+ return if max.nil? || size <= max
68
+
69
+ raise ProtocolException.new(ProtocolException::SIZE_LIMIT, "String size #{size} larger than the maximum #{max}")
70
+ end
71
+
72
+ def self.validate_max_string_size(max_string_size)
73
+ return if max_string_size.nil? || (max_string_size.is_a?(Integer) && max_string_size > 0)
74
+
75
+ raise ArgumentError, "max_string_size must be nil or a positive Integer"
76
+ end
77
+
55
78
  def write_message_begin(name, type, seqid)
56
79
  raise NotImplementedError
57
80
  end
@@ -228,6 +251,10 @@ module Thrift
228
251
  raise NotImplementedError
229
252
  end
230
253
 
254
+ def skip_string
255
+ read_string
256
+ end
257
+
231
258
  # Writes a field based on the field information, field ID and value.
232
259
  #
233
260
  # field_info - A Hash containing the definition of the field:
@@ -236,25 +263,19 @@ module Thrift
236
263
  # :binary - A Boolean flag that indicates if Thrift::Types::STRING is a binary string (string without encoding).
237
264
  # fid - The ID of the field.
238
265
  # value - The field's value to write; object type varies based on :type.
266
+ # remaining_depth - The optional recursion budget of the enclosing struct.
239
267
  #
240
268
  # Returns nothing.
241
- def write_field(*args)
242
- if args.size == 3
243
- # handles the documented method signature - write_field(field_info, fid, value)
244
- field_info = args[0]
245
- fid = args[1]
246
- value = args[2]
247
- elsif args.size == 4
248
- # handles the deprecated method signature - write_field(name, type, fid, value)
249
- field_info = {:name => args[0], :type => args[1]}
250
- fid = args[2]
251
- value = args[3]
252
- else
253
- raise ArgumentError, "wrong number of arguments (#{args.size} for 3)"
269
+ def write_field(field_info, fid, value, remaining_depth = nil)
270
+ unless field_info.is_a?(Hash)
271
+ field_info = {name: field_info, type: fid}
272
+ fid = value
273
+ value = remaining_depth
274
+ remaining_depth = nil
254
275
  end
255
276
 
256
277
  write_field_begin(field_info[:name], field_info[:type], fid)
257
- write_type(field_info, value)
278
+ write_type(field_info, value, remaining_depth)
258
279
  write_field_end
259
280
  end
260
281
 
@@ -264,13 +285,14 @@ module Thrift
264
285
  # :type - The Thrift::Types constant that determines how the value is written.
265
286
  # :binary - A Boolean flag that indicates if Thrift::Types::STRING is a binary string (string without encoding).
266
287
  # value - The field's value to write; object type varies based on field_info[:type].
288
+ # remaining_depth - The optional recursion budget of the enclosing struct.
267
289
  #
268
290
  # Returns nothing.
269
- def write_type(field_info, value)
291
+ def write_type(field_info, value, remaining_depth = nil)
270
292
  # if field_info is a Integer, assume it is a Thrift::Types constant
271
293
  # convert it into a field_info Hash for backwards compatibility
272
294
  if field_info.is_a? Integer
273
- field_info = {:type => field_info}
295
+ field_info = {type: field_info}
274
296
  end
275
297
 
276
298
  case field_info[:type]
@@ -295,7 +317,11 @@ module Thrift
295
317
  when Types::UUID
296
318
  write_uuid(value)
297
319
  when Types::STRUCT
298
- value.write(self)
320
+ if remaining_depth
321
+ value.write(self, remaining_depth - 1)
322
+ else
323
+ value.write(self)
324
+ end
299
325
  else
300
326
  raise NotImplementedError
301
327
  end
@@ -312,7 +338,7 @@ module Thrift
312
338
  # if field_info is a Integer, assume it is a Thrift::Types constant
313
339
  # convert it into a field_info Hash for backwards compatibility
314
340
  if field_info.is_a? Integer
315
- field_info = {:type => field_info}
341
+ field_info = {type: field_info}
316
342
  end
317
343
 
318
344
  case field_info[:type]
@@ -341,7 +367,8 @@ module Thrift
341
367
  end
342
368
  end
343
369
 
344
- def skip(type)
370
+ def skip(type, max_depth = 64)
371
+ raise ProtocolException.new(ProtocolException::DEPTH_LIMIT, "Maximum skip depth exceeded") if max_depth <= 0
345
372
  case type
346
373
  when Types::BOOL
347
374
  read_bool
@@ -356,44 +383,52 @@ module Thrift
356
383
  when Types::DOUBLE
357
384
  read_double
358
385
  when Types::STRING
359
- read_string
386
+ skip_string
360
387
  when Types::UUID
361
388
  read_uuid
362
389
  when Types::STRUCT
363
390
  read_struct_begin
364
391
  while true
365
- name, type, id = read_field_begin
392
+ _, type, _ = read_field_begin
366
393
  break if type == Types::STOP
367
- skip(type)
394
+ skip(type, max_depth - 1)
368
395
  read_field_end
369
396
  end
370
397
  read_struct_end
371
398
  when Types::MAP
372
399
  ktype, vtype, size = read_map_begin
400
+ validate_container_size(size)
373
401
  size.times do
374
- skip(ktype)
375
- skip(vtype)
402
+ skip(ktype, max_depth - 1)
403
+ skip(vtype, max_depth - 1)
376
404
  end
377
405
  read_map_end
378
406
  when Types::SET
379
407
  etype, size = read_set_begin
408
+ validate_container_size(size)
380
409
  size.times do
381
- skip(etype)
410
+ skip(etype, max_depth - 1)
382
411
  end
383
412
  read_set_end
384
413
  when Types::LIST
385
414
  etype, size = read_list_begin
415
+ validate_container_size(size)
386
416
  size.times do
387
- skip(etype)
417
+ skip(etype, max_depth - 1)
388
418
  end
389
419
  read_list_end
390
420
  else
391
- raise ProtocolException.new(ProtocolException::INVALID_DATA, 'Invalid data')
421
+ raise ProtocolException.new(ProtocolException::INVALID_DATA, "Invalid data")
392
422
  end
393
423
  end
394
424
 
425
+ def validate_container_size(size)
426
+ raise ProtocolException.new(ProtocolException::NEGATIVE_SIZE, "Negative size") unless size >= 0
427
+ raise ProtocolException.new(ProtocolException::SIZE_LIMIT, "Container size limit exceeded") if size > MAX_CONTAINER_SIZE
428
+ end
429
+
395
430
  def to_s
396
- "#{trans.to_s}"
431
+ trans.to_s
397
432
  end
398
433
  end
399
434