stomp_parser 1.0.0-universal-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ package stomp_parser;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.RubyClass;
5
+ import org.jruby.RubyModule;
6
+ import org.jruby.runtime.load.BasicLibraryService;
7
+ import org.jruby.runtime.builtin.IRubyObject;
8
+ import org.jruby.runtime.ObjectAllocator;
9
+
10
+ public class JavaParserService implements BasicLibraryService {
11
+ public boolean basicLoad(Ruby ruby) {
12
+ RubyModule mStomp = ruby.getClassFromPath("StompParser");
13
+ RubyClass cJavaParser = ruby.defineClassUnder("JavaParser", ruby.getObject(), JAVA_PARSER_ALLOCATOR, mStomp);
14
+ cJavaParser.defineAnnotatedMethods(JavaParser.class);
15
+ return true;
16
+ }
17
+
18
+ private static final ObjectAllocator JAVA_PARSER_ALLOCATOR = new ObjectAllocator() {
19
+ public IRubyObject allocate(Ruby ruby, RubyClass klass) {
20
+ return new JavaParser(ruby, klass);
21
+ }
22
+ };
23
+ }
@@ -0,0 +1,225 @@
1
+ #include <ruby.h>
2
+
3
+ #if DEBUG_H
4
+ # define DEBUG(fmt, ...) do { fprintf(stderr, fmt "\n", ##__VA_ARGS__); } while(0)
5
+ #else
6
+ # define DEBUG(...)
7
+ #endif
8
+
9
+ #define UNUSED(x) (void)(x)
10
+ #define MARK_LEN (p - mark)
11
+ #define MARK_STR_NEW() rb_str_new(mark, MARK_LEN)
12
+
13
+ #define true 1
14
+ #define false 0
15
+
16
+ typedef struct {
17
+ VALUE error;
18
+ long max_frame_size;
19
+
20
+ VALUE chunk;
21
+ const char *p;
22
+ int cs;
23
+ const char *mark;
24
+ VALUE mark_key;
25
+ VALUE mark_frame;
26
+ long mark_frame_size;
27
+ long mark_content_length;
28
+ } parser_state_t;
29
+
30
+ VALUE mStompParser = Qnil;
31
+ VALUE cFrame = Qnil;
32
+ VALUE eFrameSizeExceeded = Qnil;
33
+ ID g_new;
34
+ ID g_write_command;
35
+ ID g_write_header;
36
+ ID g_write_body;
37
+ ID g_content_length;
38
+ ID g_build_parse_error;
39
+ ID g_max_frame_size;
40
+
41
+ %%{
42
+ machine frame;
43
+
44
+ action mark {
45
+ mark = p;
46
+ }
47
+
48
+ action mark_frame {
49
+ mark_frame = rb_funcall(cFrame, g_new, 2, Qnil, Qnil);
50
+ mark_frame_size = 0;
51
+ }
52
+
53
+ action write_command {
54
+ rb_funcall(mark_frame, g_write_command, 1, MARK_STR_NEW());
55
+ mark = NULL;
56
+ }
57
+
58
+ action mark_key {
59
+ mark_key = MARK_STR_NEW();
60
+ mark = NULL;
61
+ }
62
+
63
+ action write_header {
64
+ rb_funcall(mark_frame, g_write_header, 2, mark_key, MARK_STR_NEW());
65
+ mark_key = Qnil;
66
+ mark = NULL;
67
+ }
68
+
69
+ action finish_headers {
70
+ VALUE length = rb_funcall(mark_frame, g_content_length, 0);
71
+ if ( ! NIL_P(length)) {
72
+ mark_content_length = NUM2LONG(length);
73
+ } else {
74
+ mark_content_length = -1;
75
+ }
76
+ }
77
+
78
+ action write_body {
79
+ rb_funcall(mark_frame, g_write_body, 1, MARK_STR_NEW());
80
+ mark = NULL;
81
+ }
82
+
83
+ action consume_null {
84
+ ((mark_content_length != -1) && (MARK_LEN < mark_content_length))
85
+ }
86
+
87
+ action consume_octet {
88
+ ((mark_content_length == -1) || (MARK_LEN < mark_content_length))
89
+ }
90
+
91
+ action check_frame_size {
92
+ mark_frame_size += 1;
93
+ if (mark_frame_size > max_frame_size) {
94
+ rb_raise(eFrameSizeExceeded, "");
95
+ }
96
+ }
97
+
98
+ action finish_frame {
99
+ rb_yield(mark_frame);
100
+ mark_frame = Qnil;
101
+ }
102
+
103
+ include frame_common "parser_common.rl";
104
+
105
+ write data noprefix;
106
+ }%%
107
+
108
+ static void parser_free(parser_state_t *state) {
109
+ // TODO: free memory inside struct!
110
+ xfree(state);
111
+ }
112
+
113
+ static void parser_mark(parser_state_t *state) {
114
+ rb_gc_mark(state->error);
115
+ rb_gc_mark(state->mark_key);
116
+ rb_gc_mark(state->mark_frame);
117
+ rb_gc_mark(state->chunk);
118
+ }
119
+
120
+ static VALUE parser_alloc(VALUE klass) {
121
+ parser_state_t *state = ALLOC(parser_state_t);
122
+ return Data_Wrap_Struct(klass, parser_mark, parser_free, state);
123
+ }
124
+
125
+ static VALUE parser_initialize(int argc, VALUE *argv, VALUE self) {
126
+ parser_state_t *state;
127
+ Data_Get_Struct(self, parser_state_t, state);
128
+
129
+ VALUE max_frame_size;
130
+ rb_scan_args(argc, argv, "01", &max_frame_size);
131
+
132
+ if (max_frame_size == Qnil) {
133
+ max_frame_size = rb_funcall(mStompParser, g_max_frame_size, 0);
134
+ }
135
+
136
+ state->error = Qnil;
137
+ state->max_frame_size = FIX2LONG(max_frame_size);
138
+ state->chunk = Qnil;
139
+ state->cs = start;
140
+ state->mark = NULL;
141
+ state->mark_key = Qnil;
142
+ state->mark_frame = Qnil;
143
+ state->mark_frame_size = 0;
144
+ state->mark_content_length = 0;
145
+
146
+ return self;
147
+ }
148
+
149
+ static VALUE parser_parse(VALUE self, VALUE new_chunk) {
150
+ parser_state_t *state;
151
+ Data_Get_Struct(self, parser_state_t, state);
152
+
153
+ if (NIL_P(state->error)) {
154
+ VALUE chunk = Qnil;
155
+ const char *p = NULL;
156
+ const char *mark = NULL;
157
+
158
+ if ( ! NIL_P(state->chunk)) {
159
+ long offset = RSTRING_LEN(state->chunk);
160
+ long mark_offset = state->mark - RSTRING_PTR(state->chunk);
161
+
162
+ chunk = rb_str_append(state->chunk, new_chunk);
163
+ p = RSTRING_PTR(chunk) + offset;
164
+ mark = RSTRING_PTR(chunk) + mark_offset;
165
+ } else {
166
+ chunk = new_chunk;
167
+ p = RSTRING_PTR(chunk);
168
+ }
169
+
170
+ const char *pe = RSTRING_END(chunk);
171
+ long max_frame_size = state->max_frame_size;
172
+
173
+ int cs = state->cs;
174
+ VALUE mark_key = state->mark_key;
175
+ VALUE mark_frame = state->mark_frame;
176
+ long mark_frame_size = state->mark_frame_size;
177
+ long mark_content_length = state->mark_content_length;
178
+
179
+ %% write exec;
180
+
181
+ if (mark != NULL) {
182
+ state->chunk = chunk;
183
+ } else {
184
+ state->chunk = Qnil;
185
+ }
186
+
187
+ state->cs = cs;
188
+ state->mark = mark;
189
+ state->mark_key = mark_key;
190
+ state->mark_frame = mark_frame;
191
+ state->mark_frame_size = mark_frame_size;
192
+ state->mark_content_length = mark_content_length;
193
+
194
+ if (cs == error) {
195
+ long index = p - RSTRING_PTR(chunk);
196
+ state->error = rb_funcall(mStompParser, g_build_parse_error, 2, chunk, LONG2NUM(index));
197
+ }
198
+ }
199
+
200
+ if ( ! NIL_P(state->error)) {
201
+ rb_exc_raise(state->error);
202
+ }
203
+
204
+ return Qnil;
205
+ }
206
+
207
+ void Init_c_parser(void) {
208
+ mStompParser = rb_const_get(rb_cObject, rb_intern("StompParser"));
209
+ cFrame = rb_const_get(mStompParser, rb_intern("Frame"));
210
+ eFrameSizeExceeded = rb_const_get(mStompParser, rb_intern("FrameSizeExceeded"));
211
+
212
+ g_new = rb_intern("new");
213
+ g_write_command = rb_intern("write_command");
214
+ g_write_header = rb_intern("write_header");
215
+ g_write_body = rb_intern("write_body");
216
+ g_content_length = rb_intern("content_length");
217
+ g_build_parse_error = rb_intern("build_parse_error");
218
+ g_max_frame_size = rb_intern("max_frame_size");
219
+
220
+ VALUE cParser = rb_define_class_under(mStompParser, "CParser", rb_cObject);
221
+ rb_define_alloc_func(cParser, parser_alloc);
222
+
223
+ rb_define_method(cParser, "initialize", parser_initialize, -1);
224
+ rb_define_method(cParser, "parse", parser_parse, 1);
225
+ }
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "mkmf"
4
+
5
+ $CFLAGS << " -O3"
6
+
7
+ should_build = true
8
+ should_build &&= have_header "ruby.h"
9
+ should_build &&= defined?(RUBY_ENGINE) && %w[ruby rbx].include?(RUBY_ENGINE)
10
+
11
+ if should_build
12
+ create_makefile("stomp_parser/c_parser")
13
+ else
14
+ dummy_makefile(".")
15
+ end
@@ -0,0 +1,46 @@
1
+ require "stomp_parser/version"
2
+ require "stomp_parser/error"
3
+ require "stomp_parser/frame"
4
+ require "stomp_parser/ruby_parser"
5
+
6
+ case RUBY_ENGINE
7
+ when "ruby", "rbx"
8
+ require "stomp_parser/c_parser"
9
+ when "jruby"
10
+ require "stomp_parser/java_parser"
11
+ end
12
+
13
+ module StompParser
14
+ Parser = if defined?(CParser)
15
+ CParser
16
+ elsif defined?(JavaParser)
17
+ JavaParser
18
+ else
19
+ RubyParser
20
+ end
21
+
22
+ @max_frame_size = 1024 * 10 # 10KB
23
+
24
+ class << self
25
+ attr_accessor :max_frame_size
26
+
27
+ # Create a parse error from a string chunk and an index.
28
+ #
29
+ # @api private
30
+ # @param [String] chunk
31
+ # @param [Integer] index
32
+ # @return [ParseError]
33
+ def build_parse_error(chunk, index)
34
+ ctx = 7
35
+ min = [0, index - ctx].max
36
+ len = ctx + 1 + ctx
37
+ context = chunk.byteslice(min, len).force_encoding("BINARY")
38
+
39
+ idx = index - min
40
+ chr = context[idx]
41
+ context[idx] = " -->#{chr}<-- "
42
+
43
+ ParseError.new("unexpected #{chr} in chunk (#{context.inspect})")
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ module StompParser
2
+ class Error < StandardError
3
+ end
4
+
5
+ # Errors raised by the Parser.
6
+ class ParseError < Error
7
+ end
8
+
9
+ # Raised when the Parser has reached the
10
+ # limit for how large a Frame may be.
11
+ #
12
+ # Protects against malicious clients trying to
13
+ # fill the available memory by sending very large
14
+ # frames, for example by sending an unlimited
15
+ # amount of headers.
16
+ class FrameSizeExceeded < ParseError
17
+ end
18
+ end
@@ -0,0 +1,133 @@
1
+ module StompParser
2
+ class Frame
3
+ HEADER_TRANSLATIONS = {
4
+ '\\r' => "\r",
5
+ '\\n' => "\n",
6
+ '\\c' => ":",
7
+ '\\\\' => '\\',
8
+ }.freeze
9
+ HEADER_TRANSLATIONS_KEYS = Regexp.union(HEADER_TRANSLATIONS.keys).freeze
10
+ HEADER_REVERSE_TRANSLATIONS = HEADER_TRANSLATIONS.invert
11
+ HEADER_REVERSE_TRANSLATIONS_KEYS = Regexp.union(HEADER_REVERSE_TRANSLATIONS.keys).freeze
12
+ EMPTY = "".force_encoding("UTF-8").freeze
13
+
14
+ # @return [String]
15
+ attr_reader :command
16
+
17
+ # @return [Hash<String, String>]
18
+ attr_reader :headers
19
+
20
+ # @return [String]
21
+ attr_reader :body
22
+
23
+ # Construct a frame from a command, optional headers, and a body.
24
+ #
25
+ # @param [String] command
26
+ # @param [Hash<String, String>] headers
27
+ # @param [String] body
28
+ def initialize(command, headers = {}, body)
29
+ @command = command || EMPTY
30
+ @headers = headers
31
+ @body = body || EMPTY
32
+ end
33
+
34
+ # Content length of this frame, according to headers.
35
+ #
36
+ # @raise [ArgumentError] if content-length is not a valid integer
37
+ # @return [Integer, nil]
38
+ def content_length
39
+ if headers.has_key?("content-length")
40
+ begin
41
+ Integer(headers["content-length"])
42
+ rescue ArgumentError
43
+ raise Error, "invalid content length #{headers["content-length"].inspect}"
44
+ end
45
+ end
46
+ end
47
+
48
+ def content_type
49
+ headers["content-type"]
50
+ end
51
+
52
+ # @raise [ArgumentError] if encoding does not exist
53
+ # @return [Encoding] body encoding, according to headers.
54
+ def content_encoding
55
+ if content_type
56
+ mime_type, charset = content_type.to_s.split(";")
57
+ mime_type = mime_type.to_s
58
+ charset = charset.to_s[/\Acharset=(.*)/, 1].to_s
59
+
60
+ if charset.empty? and mime_type.to_s.start_with?("text/")
61
+ Encoding::UTF_8
62
+ elsif charset.empty?
63
+ Encoding::BINARY
64
+ else
65
+ Encoding.find(charset)
66
+ end
67
+ else
68
+ Encoding::BINARY
69
+ end
70
+ end
71
+
72
+ # Change the command of this frame.
73
+ #
74
+ # @param [String] command
75
+ def write_command(command)
76
+ @command = command
77
+ end
78
+
79
+ # Write a single header to this frame.
80
+ #
81
+ # @param [String] key
82
+ # @param [String] value
83
+ def write_header(key, value)
84
+ # @see http://stomp.github.io/stomp-specification-1.2.html#Repeated_Header_Entries
85
+ key = translate_header(key)
86
+ @headers[key] = translate_header(value) unless @headers.has_key?(key)
87
+ end
88
+
89
+ # Write the body to this frame.
90
+ #
91
+ # @param [String] body
92
+ def write_body(body)
93
+ @body = body.force_encoding(content_encoding)
94
+ end
95
+
96
+ # @return [String] a string-representation of this frame.
97
+ def to_str
98
+ frame = "".force_encoding("UTF-8")
99
+ frame << command << "\n"
100
+
101
+ outgoing_headers = headers.dup
102
+ outgoing_headers["content-length"] = body.bytesize
103
+ outgoing_headers.each do |key, value|
104
+ frame << serialize_header(key) << ":" << serialize_header(value) << "\n"
105
+ end
106
+ frame << "\n"
107
+
108
+ frame << body << "\x00"
109
+ frame
110
+ end
111
+ alias_method :to_s, :to_str
112
+
113
+ def [](key)
114
+ @headers[key]
115
+ end
116
+
117
+ def destination
118
+ self["destination"]
119
+ end
120
+
121
+ private
122
+
123
+ # @see http://stomp.github.io/stomp-specification-1.2.html#Value_Encoding
124
+ def translate_header(value)
125
+ value.gsub(HEADER_TRANSLATIONS_KEYS, HEADER_TRANSLATIONS).force_encoding(Encoding::UTF_8) unless value.empty?
126
+ end
127
+
128
+ # inverse of #translate_header
129
+ def serialize_header(value)
130
+ value.to_s.gsub(HEADER_REVERSE_TRANSLATIONS_KEYS, HEADER_REVERSE_TRANSLATIONS)
131
+ end
132
+ end
133
+ end