stomp_parser 1.0.0-universal-java

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.
@@ -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