bencode_ext 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +0 -1
- data/VERSION +1 -1
- data/bencode_ext.gemspec +3 -3
- data/ext/bencode_ext/bencode.c +145 -80
- data/ext/bencode_ext/bencode.h +3 -1
- data/ext/bencode_ext/extconf.rb +2 -0
- data/test/test_bencode_ext.rb +13 -0
- metadata +3 -4
data/Rakefile
CHANGED
@@ -14,7 +14,6 @@ Jeweler::Tasks.new do |gem|
|
|
14
14
|
gem.required_ruby_version = '~>1.9.2'
|
15
15
|
gem.add_dependency 'rake-compiler', '~>0.7.5'
|
16
16
|
gem.add_development_dependency "jeweler", "~> 1.5.2"
|
17
|
-
gem.extensions << "ext/bencode_ext/extconf.rb"
|
18
17
|
end
|
19
18
|
Jeweler::RubygemsDotOrgTasks.new
|
20
19
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/bencode_ext.gemspec
CHANGED
@@ -5,14 +5,14 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{bencode_ext}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["naquad"]
|
12
|
-
s.date = %q{2011-01-
|
12
|
+
s.date = %q{2011-01-14}
|
13
13
|
s.description = %q{BEncodeExt is implementation of Bencode reader/writer (BitTorent encoding) in C.}
|
14
14
|
s.email = %q{naquad@gmail.com}
|
15
|
-
s.extensions = ["ext/bencode_ext/extconf.rb"
|
15
|
+
s.extensions = ["ext/bencode_ext/extconf.rb"]
|
16
16
|
s.extra_rdoc_files = [
|
17
17
|
"LICENSE.txt",
|
18
18
|
"README.rdoc"
|
data/ext/bencode_ext/bencode.c
CHANGED
@@ -23,88 +23,28 @@
|
|
23
23
|
|
24
24
|
#include "bencode.h"
|
25
25
|
|
26
|
-
#define NEXT_CHAR ++*str; --*len;
|
27
|
-
#define END_CHECK_SKIP(t) {if(!*len)\
|
28
|
-
rb_raise(DecodeError, "Unpexpected " #t " end!");\
|
29
|
-
if(**str != 'e')\
|
30
|
-
rb_raise(DecodeError, "Mailformed " #t " at %d byte: %c", rlen - *len, **str);\
|
31
|
-
NEXT_CHAR;}
|
32
|
-
|
33
26
|
static long parse_num(char** str, long* len){
|
34
27
|
long t = 1, ret = 0;
|
35
28
|
|
36
29
|
if(**str == '-'){
|
37
30
|
t = -1;
|
38
|
-
|
31
|
+
++*str;
|
32
|
+
--*len;
|
39
33
|
}
|
40
34
|
|
41
35
|
while(*len && **str >= '0' && **str <= '9'){
|
42
36
|
ret = ret * 10 + (**str - '0');
|
43
|
-
|
37
|
+
++*str;
|
38
|
+
--*len;
|
44
39
|
}
|
45
40
|
|
46
41
|
return ret * t;
|
47
42
|
}
|
48
43
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
switch(**str){
|
54
|
-
case 'l':{
|
55
|
-
VALUE ret = rb_ary_new();
|
56
|
-
NEXT_CHAR;
|
57
|
-
|
58
|
-
while(**str != 'e' && *len)
|
59
|
-
rb_ary_push(ret, _decode(str, len, rlen));
|
60
|
-
|
61
|
-
END_CHECK_SKIP(list);
|
62
|
-
return ret;
|
63
|
-
}
|
64
|
-
case 'd':{
|
65
|
-
VALUE ret = rb_hash_new();
|
66
|
-
NEXT_CHAR;
|
67
|
-
|
68
|
-
while(**str != 'e' && *len){
|
69
|
-
long t = *len;
|
70
|
-
VALUE k = _decode(str, len, rlen);
|
71
|
-
|
72
|
-
if(TYPE(k) != T_STRING)
|
73
|
-
rb_raise(DecodeError, "Dictionary key is not a string at %d!", rlen - t);
|
74
|
-
|
75
|
-
rb_hash_aset(ret, k, _decode(str, len, rlen));
|
76
|
-
}
|
77
|
-
|
78
|
-
END_CHECK_SKIP(dictionary);
|
79
|
-
return ret;
|
80
|
-
}
|
81
|
-
case 'i':{
|
82
|
-
long t;
|
83
|
-
NEXT_CHAR;
|
84
|
-
t = parse_num(str, len);
|
85
|
-
END_CHECK_SKIP(integer);
|
86
|
-
return LONG2FIX(t);
|
87
|
-
}
|
88
|
-
case '0'...'9':{
|
89
|
-
VALUE ret;
|
90
|
-
long slen = parse_num(str, len);
|
91
|
-
|
92
|
-
if(slen < 0 || (*len && **str != ':'))
|
93
|
-
rb_raise(DecodeError, "Invalid string length specification at %d: %c", rlen - *len, **str);
|
94
|
-
|
95
|
-
if(!*len || *len < slen + 1)
|
96
|
-
rb_raise(DecodeError, "Unexpected string end!");
|
97
|
-
|
98
|
-
ret = rb_str_new(++*str, slen);
|
99
|
-
*str += slen;
|
100
|
-
*len -= slen + 1;
|
101
|
-
return ret;
|
102
|
-
}
|
103
|
-
default:
|
104
|
-
rb_raise(DecodeError, "Unknown element type at %d byte: %c", rlen - *len, **str);
|
105
|
-
return Qnil;
|
106
|
-
}
|
107
|
-
}
|
44
|
+
#define NEXT_CHAR ++str; --len;
|
45
|
+
#define ELEMENT_SCALAR 0
|
46
|
+
#define ELEMENT_STRUCT 1
|
47
|
+
#define ELEMENT_END 2
|
108
48
|
|
109
49
|
/*
|
110
50
|
* Document-method: BEncode.decode
|
@@ -118,25 +58,104 @@ static VALUE _decode(char** str, long* len, long rlen){
|
|
118
58
|
*
|
119
59
|
* Examples:
|
120
60
|
*
|
121
|
-
* 'i1e' => 1
|
122
|
-
* 'i-1e' => -1
|
123
|
-
* '6:string' => 'string'
|
61
|
+
* BEncode.decode('i1e') => 1
|
62
|
+
* BEncode.decode('i-1e') => -1
|
63
|
+
* BEncode.decode('6:string') => 'string'
|
124
64
|
*/
|
125
65
|
|
126
|
-
static VALUE decode(VALUE self, VALUE
|
127
|
-
long rlen
|
128
|
-
char*
|
129
|
-
VALUE ret;
|
66
|
+
static VALUE decode(VALUE self, VALUE encoded){
|
67
|
+
long len, rlen;
|
68
|
+
char* str;
|
69
|
+
VALUE ret, container_stack, current_container, key, crt;
|
130
70
|
|
131
|
-
if(!rb_obj_is_kind_of(
|
71
|
+
if(!rb_obj_is_kind_of(encoded, rb_cString))
|
132
72
|
rb_raise(rb_eTypeError, "String expected");
|
133
73
|
|
134
|
-
|
135
|
-
|
136
|
-
|
74
|
+
len = rlen = RSTRING_LEN(encoded);
|
75
|
+
if(!len)
|
76
|
+
return Qnil;
|
77
|
+
|
78
|
+
str = RSTRING_PTR(encoded);
|
79
|
+
container_stack = rb_ary_new();
|
80
|
+
current_container = ret = key = Qnil;
|
81
|
+
|
82
|
+
while(len){
|
83
|
+
int state = ELEMENT_SCALAR;
|
84
|
+
switch(*str){
|
85
|
+
case 'l':
|
86
|
+
case 'd':
|
87
|
+
crt = *str == 'l' ? rb_ary_new() : rb_hash_new();
|
88
|
+
NEXT_CHAR;
|
89
|
+
if(NIL_P(current_container)){
|
90
|
+
ret = current_container = crt;
|
91
|
+
continue;
|
92
|
+
}
|
93
|
+
state = ELEMENT_STRUCT;
|
94
|
+
break;
|
95
|
+
case 'i':
|
96
|
+
NEXT_CHAR;
|
97
|
+
crt = LONG2FIX(parse_num(&str, &len));
|
98
|
+
|
99
|
+
if(!len)
|
100
|
+
rb_raise(DecodeError, "Unpexpected integer end!");
|
101
|
+
if(*str != 'e')
|
102
|
+
rb_raise(DecodeError, "Mailformed integer at %d byte: %c", rlen - len, *str);
|
103
|
+
|
104
|
+
NEXT_CHAR;
|
105
|
+
break;
|
106
|
+
case '0'...'9':{
|
107
|
+
long slen = parse_num(&str, &len);
|
108
|
+
|
109
|
+
if(slen < 0 || (len && *str != ':'))
|
110
|
+
rb_raise(DecodeError, "Invalid string length specification at %d: %c", rlen - len, *str);
|
111
|
+
|
112
|
+
if(!len || len < slen + 1)
|
113
|
+
rb_raise(DecodeError, "Unexpected string end!");
|
114
|
+
|
115
|
+
crt = rb_str_new(++str, slen);
|
116
|
+
str += slen;
|
117
|
+
len -= slen + 1;
|
118
|
+
break;
|
119
|
+
}
|
120
|
+
case 'e':
|
121
|
+
state = ELEMENT_END;
|
122
|
+
NEXT_CHAR;
|
123
|
+
break;
|
124
|
+
default:
|
125
|
+
rb_raise(DecodeError, "Unknown element type at %d: %c!", rlen - len, *str);
|
126
|
+
}
|
127
|
+
|
128
|
+
if(state == ELEMENT_END){
|
129
|
+
if(NIL_P(current_container))
|
130
|
+
rb_raise(DecodeError, "Unexpected container end at %d!", rlen - len);
|
131
|
+
current_container = rb_ary_pop(container_stack);
|
132
|
+
}else if(NIL_P(current_container)){
|
133
|
+
if(NIL_P(ret))
|
134
|
+
ret = crt;
|
135
|
+
break;
|
136
|
+
}else{
|
137
|
+
if(TYPE(current_container) == T_ARRAY){
|
138
|
+
rb_ary_push(current_container, crt);
|
139
|
+
}else if(NIL_P(key)){
|
140
|
+
if(TYPE(crt) != T_STRING)
|
141
|
+
rb_raise(DecodeError, "Dictionary key must be a string (at %d)!", rlen - len);
|
142
|
+
key = crt;
|
143
|
+
}else{
|
144
|
+
rb_hash_aset(current_container, key, crt);
|
145
|
+
key = Qnil;
|
146
|
+
}
|
147
|
+
|
148
|
+
if(state == ELEMENT_STRUCT){
|
149
|
+
rb_ary_push(container_stack, current_container);
|
150
|
+
if(max_depth > -1 && max_depth < RARRAY_LEN(container_stack) + 1)
|
151
|
+
rb_raise(DecodeError, "Structure is too deep!");
|
152
|
+
current_container = crt;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
137
156
|
|
138
157
|
if(len)
|
139
|
-
rb_raise(DecodeError, "String has garbage on the end (starts at %d).", rlen - len);
|
158
|
+
rb_raise(DecodeError, "String has garbage on the end (starts at %d: %s).", rlen - len);
|
140
159
|
|
141
160
|
return ret;
|
142
161
|
}
|
@@ -257,9 +276,53 @@ static VALUE mod_encode(VALUE self, VALUE x){
|
|
257
276
|
return encode(x);
|
258
277
|
}
|
259
278
|
|
279
|
+
/*
|
280
|
+
* Document-method: max_depth
|
281
|
+
* call-seq:
|
282
|
+
* BEncode.max_depth
|
283
|
+
*
|
284
|
+
* Get maximum depth of parsed structure.
|
285
|
+
*/
|
286
|
+
|
287
|
+
static VALUE get_max_depth(VALUE self){
|
288
|
+
return LONG2FIX(max_depth);
|
289
|
+
}
|
290
|
+
|
291
|
+
/*
|
292
|
+
* Document-method: max_depth=
|
293
|
+
* call-seq:
|
294
|
+
* BEncode.max_depth = _integer_
|
295
|
+
*
|
296
|
+
* Sets maximum depth of parsed structure.
|
297
|
+
* Expects integer greater or equal to 0.
|
298
|
+
* By default this value is 5000.
|
299
|
+
* Assigning nil will disable depth check.
|
300
|
+
*/
|
301
|
+
|
302
|
+
static VALUE set_max_depth(VALUE self, VALUE depth){
|
303
|
+
long t;
|
304
|
+
|
305
|
+
if(NIL_P(depth)){
|
306
|
+
max_depth = -1;
|
307
|
+
return depth;
|
308
|
+
}
|
309
|
+
|
310
|
+
if(!rb_obj_is_kind_of(depth, rb_cInteger))
|
311
|
+
rb_raise(rb_eArgError, "Integer expected!");
|
312
|
+
|
313
|
+
t = NUM2LONG(depth);
|
314
|
+
if(t < 0)
|
315
|
+
rb_raise(rb_eArgError, "Depth must be between 0 and 5000000");
|
316
|
+
|
317
|
+
max_depth = t;
|
318
|
+
return depth;
|
319
|
+
}
|
320
|
+
|
260
321
|
void Init_bencode_ext(){
|
261
322
|
readId = rb_intern("read");
|
323
|
+
max_depth = 5000;
|
262
324
|
BEncode = rb_define_module("BEncode");
|
325
|
+
|
263
326
|
/*
|
264
327
|
* Document-class: BEncode::DecodeError
|
265
328
|
* Exception for indicating decoding errors.
|
@@ -275,6 +338,8 @@ void Init_bencode_ext(){
|
|
275
338
|
rb_define_singleton_method(BEncode, "decode", decode, 1);
|
276
339
|
rb_define_singleton_method(BEncode, "encode", mod_encode, 1);
|
277
340
|
rb_define_singleton_method(BEncode, "decode_file", decode_file, 1);
|
341
|
+
rb_define_singleton_method(BEncode, "max_depth", get_max_depth, 0);
|
342
|
+
rb_define_singleton_method(BEncode, "max_depth=", set_max_depth, 1);
|
278
343
|
|
279
344
|
rb_define_method(BEncode, "bencode", encode, 0);
|
280
345
|
rb_define_method(rb_cString, "bdecode", str_bdecode, 0);
|
data/ext/bencode_ext/bencode.h
CHANGED
@@ -9,7 +9,6 @@ static VALUE EncodeError;
|
|
9
9
|
static VALUE readId;
|
10
10
|
|
11
11
|
static long parse_num(char**, long*);
|
12
|
-
static VALUE _decode(char**, long*, long);
|
13
12
|
static VALUE decode(VALUE, VALUE);
|
14
13
|
static VALUE encode(VALUE);
|
15
14
|
static int hash_traverse(VALUE, VALUE, VALUE);
|
@@ -17,6 +16,9 @@ static VALUE str_bdecode(VALUE);
|
|
17
16
|
static VALUE mod_encode(VALUE, VALUE);
|
18
17
|
static VALUE _decode_file(VALUE);
|
19
18
|
static VALUE decode_file(VALUE, VALUE);
|
19
|
+
static VALUE get_max_depth(VALUE);
|
20
|
+
static VALUE set_max_depth(VALUE, VALUE);
|
21
|
+
static long max_depth;
|
20
22
|
void Init_bencode_ext();
|
21
23
|
|
22
24
|
#endif
|
data/ext/bencode_ext/extconf.rb
CHANGED
data/test/test_bencode_ext.rb
CHANGED
@@ -9,6 +9,7 @@ class TestBencodeExt < Test::Unit::TestCase
|
|
9
9
|
assert_equal('li1ei2ee', [1, 2].bencode)
|
10
10
|
assert_equal('d3:keyi10ee', {:key => 10}.bencode)
|
11
11
|
assert_equal('ld1:ki1eed1:ki2eed1:kd1:v3:123eee', [{:k => 1}, {:k => 2}, {:k => {:v => '123'}}].bencode)
|
12
|
+
assert_equal('llli1eei1eei1ee', [[[1],1],1].bencode)
|
12
13
|
|
13
14
|
assert_raises(BEncode::EncodeError) { STDERR.bencode }
|
14
15
|
end
|
@@ -20,9 +21,21 @@ class TestBencodeExt < Test::Unit::TestCase
|
|
20
21
|
assert_equal([1, 2], 'li1ei2ee'.bdecode)
|
21
22
|
assert_equal({'key' => 10}, 'd3:keyi10ee'.bdecode)
|
22
23
|
assert_equal([{'k' => 1}, {'k' => 2}, {'k' => {'v' => '123'}}], 'ld1:ki1eed1:ki2eed1:kd1:v3:123eee'.bdecode)
|
24
|
+
assert_equal([[[1],1],1], 'llli1eei1eei1ee'.bdecode)
|
23
25
|
|
24
26
|
assert_raises(BEncode::DecodeError) {'33:unpexpected_end'.bdecode }
|
25
27
|
assert_raises(BEncode::DecodeError) { 'i1x'.bdecode }
|
28
|
+
assert_raises(ArgumentError) { BEncode.max_depth = 1.1 }
|
29
|
+
assert_raises(ArgumentError) { BEncode.max_depth = -5 }
|
30
|
+
assert_raises(BEncode::DecodeError) do
|
31
|
+
BEncode.max_depth = 1
|
32
|
+
'lli1eee'.bdecode
|
33
|
+
end
|
34
|
+
assert_nothing_raised(BEncode::DecodeError) do
|
35
|
+
BEncode.max_depth = 0
|
36
|
+
'i1e'.bdecode
|
37
|
+
end
|
38
|
+
|
26
39
|
assert_nil(''.bdecode)
|
27
40
|
end
|
28
41
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
- 1
|
8
7
|
- 2
|
9
|
-
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- naquad
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-01-
|
17
|
+
date: 2011-01-14 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -53,7 +53,6 @@ executables: []
|
|
53
53
|
|
54
54
|
extensions:
|
55
55
|
- ext/bencode_ext/extconf.rb
|
56
|
-
- ext/bencode_ext/extconf.rb
|
57
56
|
extra_rdoc_files:
|
58
57
|
- LICENSE.txt
|
59
58
|
- README.rdoc
|