bencodr 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'uri'
4
+
5
+ module BEncodr
6
+ module String
7
+ module Generic
8
+ module InstanceMethods
9
+ # Encodes object into a bencoded string. BEncoded strings are length-prefixed base ten followed by a colon and
10
+ # the string.
11
+ #
12
+ # :symbol.bencodr #=> "6:symbol"
13
+ #
14
+ # @return [::String] the bencoded string
15
+ def bencode
16
+ (respond_to?(:to_s) ? to_s : to_str).bencode
17
+ end
18
+ end
19
+ end
20
+
21
+ # Registers a class as an object that can be converted into a bencoded string. Class must have instance method to_s
22
+ # or to_str.
23
+ #
24
+ # class MyClass
25
+ # def to_s
26
+ # "string"
27
+ # end
28
+ # end
29
+ #
30
+ # BEncode::String.register MyClass
31
+ # my_class = MyClass.new
32
+ # my_class.bencodr #=> "6:string"
33
+ #
34
+ # @param [Class#to_s, Class#to_str] type the class to add the bencodr instance method to
35
+ def self.register(type)
36
+ type.send :include, Generic::InstanceMethods
37
+ end
38
+
39
+ register Symbol
40
+ register URI::Generic
41
+
42
+ module String
43
+ module InstanceMethods
44
+ # Encodes a string into a bencoded string. BEncoded strings are length-prefixed base ten followed by a colon and
45
+ # the string.
46
+ #
47
+ # "string".bencodr #=> "6:string"
48
+ #
49
+ # @return [::String] the bencoded string
50
+ def bencode
51
+ [length, ':', self].join
52
+ end
53
+ end
54
+
55
+ ::String.send :include, InstanceMethods
56
+ end
57
+ end
58
+ end
data/lib/bencodr.rb ADDED
@@ -0,0 +1,56 @@
1
+ # encoding: UTF-8
2
+
3
+ path = File.expand_path(File.dirname(__FILE__)) + "/bencodr"
4
+
5
+ require path + "/string"
6
+ require path + "/integer"
7
+ require path + "/list"
8
+ require path + "/dictionary"
9
+ require path + "/parser"
10
+
11
+ module BEncodr
12
+ class BEncodeError < StandardError; end
13
+
14
+ class << self
15
+ # This method decodes a bencoded string.
16
+ #
17
+ # BEncode.decode("6:string") #=> "string"
18
+ #
19
+ # @param [::String] string the bencoded string to decode
20
+ # @return [::String, ::Integer, ::Hash, ::Array] the decoded object
21
+ def decode(string)
22
+ scanner = StringScanner.new(string)
23
+ Parser.parse_object(scanner) or raise BEncodeError, "Invalid bencoding"
24
+ end
25
+
26
+ # This method decodes a bencoded file.
27
+ #
28
+ # BEncode.decode_file("simple.torrent") #=> "d8:announce32:http://www..."
29
+ #
30
+ # @param [::String] file the file to decode
31
+ # @return [::String, ::Integer, ::Hash, ::Array] the decoded object
32
+ def decode_file(file)
33
+ decode(File.open(file, 'rb') {|f| f.read})
34
+ end
35
+
36
+ # This method encodes a bencoded object.
37
+ #
38
+ # BEncode.encode("string") #=> "6:string"
39
+ #
40
+ # @param [#bencodr] object the object to encode
41
+ # @return [::String] the bencoded object
42
+ def encode(object)
43
+ object.bencode
44
+ end
45
+
46
+ # This method encodes a bencoded object.
47
+ #
48
+ # BEncode.encode("string") #=> "6:string"
49
+ #
50
+ # @param [::String] file the file to write the bencoded object to
51
+ # @param [#bencodr] object the object to encode
52
+ def encode_file(file, object)
53
+ File.open(file, 'wb') {|f| f.write encode(object)}
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: UTF-8
2
+
3
+ require "spec"
4
+ require "spec_helper"
5
+
6
+ describe BEncodr do
7
+ describe "#decode" do
8
+ # Most of this is covered in other tests. Only difference is this accepts string instead of scanner.
9
+ it "should parse a bencoded string" do
10
+ BEncodr.decode("6:string").should == "string"
11
+ end
12
+
13
+ it "should parse a bencoded integer" do
14
+ BEncodr.decode("i4e").should == 4
15
+ end
16
+
17
+ it "should parse a bencoded list" do
18
+ BEncodr.decode("l6:stringeeeee").should == ["string"]
19
+ end
20
+
21
+ it "should parse a bencoded dictionary containing a key value pair" do
22
+ BEncodr.decode("d6:stringi1ee").should == {"string" => 1}
23
+ end
24
+
25
+ it "should raise an error when the type is not recognized" do
26
+ lambda{BEncodr.decode("freak out!")}.should raise_error BEncodr::BEncodeError
27
+ end
28
+ end
29
+
30
+ describe "#decode_file" do
31
+ it "should parse a bencoded file" do
32
+ dirname = File.dirname(__FILE__)
33
+ BEncodr.decode_file("#{dirname}/samples/mini.bencode").should == {"ba" => 3}
34
+ end
35
+ end
36
+
37
+ describe "#encode" do
38
+ # Covered in other tests so only simple stuff here.
39
+ it "should bencodr an object" do
40
+ BEncodr.encode("string").should == "6:string"
41
+ end
42
+ end
43
+
44
+ describe "#encode_file" do
45
+ context "when an object gets bencoded and written to a file" do
46
+ before :all do
47
+ @path = File.join(File.dirname(__FILE__), '..', 'tmp')
48
+ Dir.mkdir(@path) unless File.exists? @path
49
+ end
50
+
51
+ before :each do
52
+ @file = File.join(@path, 'test.bencodr')
53
+ @object = "string"
54
+ BEncodr.encode_file(@file, @object)
55
+ end
56
+
57
+ it "should actually write a file" do
58
+ File.exists?(@file).should be_true
59
+ end
60
+
61
+ it "should properly encode the file" do
62
+ BEncodr.decode_file(@file).should == @object
63
+ end
64
+
65
+ after :each do
66
+ File.delete(@file)
67
+ end
68
+
69
+ after :all do
70
+ Dir.delete(@path) if File.exists? @path
71
+ end
72
+ end
73
+
74
+ it "should read a torrent with newlines as part of a string without raising an error" do
75
+ file = File.join(File.dirname(__FILE__), 'samples', 'python.torrent')
76
+ lambda{BEncodr.decode_file file}.should_not raise_error
77
+ end
78
+ end
79
+
80
+ context "when parsing and then encoding" do
81
+ it "should be equal to the pre-parsed and encoded bencoded string" do
82
+ file = File.dirname(__FILE__) + "/samples/bencode.rb.torrent"
83
+ BEncodr.decode_file(file).bencode.should == File.open(file, "rb") {|f| f.read}
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,79 @@
1
+ # encoding: UTF-8
2
+
3
+ require "spec"
4
+ require "spec_helper"
5
+
6
+ describe Hash do
7
+ describe "#bencodr" do
8
+ it "should encode an empty hash" do
9
+ {}.bencode.should == "de"
10
+ end
11
+
12
+ context "a key should always be encoded as a string" do
13
+ it "should encode a string key as a string" do
14
+ {"string" => "string"}.bencode.should == "d6:string6:stringe"
15
+ end
16
+
17
+ it "should encode a symbol key as a string" do
18
+ {:symbol => :symbol}.bencode.should == "d6:symbol6:symbole"
19
+ end
20
+
21
+ it "should encode a uri key as a string" do
22
+ uri = URI.parse("http://github.com/blatyo/bencode")
23
+ {uri => uri}.bencode.should == "d32:http://github.com/blatyo/bencode32:http://github.com/blatyo/bencodee"
24
+ end
25
+
26
+ it "should encode an integer key as a string" do
27
+ {1 => 1}.bencode.should == "d1:1i1ee"
28
+ end
29
+
30
+ it "should encode a float key as a string" do
31
+ {1.1 => 1.1}.bencode.should == "d3:1.1i1ee"
32
+ end
33
+
34
+ it "should encode a time key as a string" do
35
+ time = Time.utc(0)
36
+ {time => time}.bencode.should == "d23:2000-01-01 00:00:00 UTCi946684800ee"
37
+ end
38
+
39
+ it "should encode an array key as a string" do
40
+ array = (1..4).to_a
41
+ {array => array}.bencode.should == "d12:[1, 2, 3, 4]li1ei2ei3ei4eee"
42
+ end
43
+
44
+ it "should encode a hash key as a string" do
45
+ {{} => {}}.bencode.should == "d2:{}dee"
46
+ end
47
+ end
48
+
49
+ it "should encode keys in sorted (as raw strings) order" do
50
+ {:a => 1, "A" => 1, 1=> 1}.bencode.should == "d1:1i1e1:Ai1e1:ai1ee"
51
+ end
52
+ end
53
+ end
54
+
55
+ describe BEncodr::Dictionary do
56
+ describe "#register" do
57
+ context "once an object has been registered as a BEncode dictionary" do
58
+ before :all do
59
+ klass = Class.new do
60
+ def to_h
61
+ {:a => "a", :b => "b"}
62
+ end
63
+ end
64
+ BEncodr::Dictionary.register klass
65
+ @instance = klass.new
66
+ end
67
+
68
+ context "an instance of that object" do
69
+ it "should respond to bencodr" do
70
+ @instance.should respond_to :bencode
71
+ end
72
+
73
+ it "should encode to a bencoded dictionary" do
74
+ @instance.bencode.should == "d1:a1:a1:b1:be"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,72 @@
1
+ # encoding: UTF-8
2
+
3
+ require "spec"
4
+ require "spec_helper"
5
+
6
+ describe Integer do
7
+ describe "#bencodr" do
8
+ it "should encode a positive integer" do
9
+ 1.bencode.should == "i1e"
10
+ end
11
+
12
+ it "should encode a negative integer" do
13
+ -1.bencode.should == "i-1e"
14
+ end
15
+
16
+ it "should encode a positive big integer" do
17
+ 10_000_000_000.bencode.should == "i10000000000e"
18
+ end
19
+
20
+ it "should encode a negative big integer" do
21
+ -10_000_000_000.bencode.should == "i-10000000000e"
22
+ end
23
+ end
24
+ end
25
+
26
+ describe Numeric do
27
+ describe "#bencodr" do
28
+ it "should encode a positive float with precision loss" do
29
+ 1.1.bencode.should == "i1e"
30
+ end
31
+
32
+ it "should encode a negative float with precision loss" do
33
+ -1.1.bencode.should == "i-1e"
34
+ end
35
+
36
+ it "should encode an positive exponential float" do
37
+ 1e10.bencode.should == "i10000000000e"
38
+ end
39
+
40
+ it "should encode an negative exponential float" do
41
+ -1e10.bencode.should == "i-10000000000e"
42
+ end
43
+ end
44
+ end
45
+
46
+ describe Time do
47
+ describe "#bencodr" do
48
+ it "should encode to bencoding" do
49
+ Time.at(4).bencode.should == "i4e"
50
+ end
51
+ end
52
+ end
53
+
54
+ describe BEncodr::Integer do
55
+ describe "#register" do
56
+ context "once an object has been registered as a BEncode integer" do
57
+ before :all do
58
+ BEncodr::Integer.register NilClass
59
+ end
60
+
61
+ context "an instance of that object" do
62
+ it "should respond to bencodr" do
63
+ nil.should respond_to :bencode
64
+ end
65
+
66
+ it "should encode to a bencoded integer" do
67
+ nil.bencode.should == "i0e"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+
3
+ require "spec"
4
+ require "spec_helper"
5
+
6
+ describe Array do
7
+ describe "#bencodr" do
8
+ it "should encode an empty array" do
9
+ [].bencode.should == "le"
10
+ end
11
+
12
+ it "should encode an array filled with bencodable objects" do
13
+ [:e, "a", 1, Time.at(11)].bencode.should == "l1:e1:ai1ei11ee"
14
+ end
15
+ end
16
+ end
17
+
18
+ describe BEncodr::List do
19
+ describe "#register" do
20
+ context "once an object has been registered as a BEncode list" do
21
+ before :all do
22
+ BEncodr::List.register Range
23
+ end
24
+
25
+ context "an instance of that object" do
26
+ it "should respond to bencodr" do
27
+ (1..2).should respond_to :bencode
28
+ end
29
+
30
+ it "should encode to a bencoded list" do
31
+ (1..2).bencode.should == "li1ei2ee"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,176 @@
1
+ # encoding: UTF-8
2
+
3
+ require "spec"
4
+
5
+ describe BEncodr::Parser do
6
+ describe "#parse_object" do
7
+ # Most of this functionality is covered with other tests. So minimal stuff here.
8
+ it "should parse a bencoded string" do
9
+ scanner = StringScanner.new("6:string")
10
+ BEncodr::Parser.parse_object(scanner).should == "string"
11
+ end
12
+
13
+ it "should parse a bencoded integer" do
14
+ scanner = StringScanner.new("i4e")
15
+ BEncodr::Parser.parse_object(scanner).should == 4
16
+ end
17
+
18
+ it "should parse a bencoded list" do
19
+ scanner = StringScanner.new("l6:stringeeeee")
20
+ BEncodr::Parser.parse_object(scanner).should == ["string"]
21
+ end
22
+
23
+ it "should parse a bencoded dictionary containing a key value pair" do
24
+ scanner = StringScanner.new("d6:stringi1ee")
25
+ BEncodr::Parser.parse_object(scanner).should == {"string" => 1}
26
+ end
27
+
28
+ it "should return nil when the type is not recognized" do
29
+ scanner = StringScanner.new("freak out!")
30
+ BEncodr::Parser.parse_object(scanner).should == nil
31
+ end
32
+ end
33
+
34
+ describe "#parse_stirng" do
35
+ it "should parse a bencoded string" do
36
+ scanner = StringScanner.new("6:string")
37
+ BEncodr::Parser.parse_string(scanner).should == "string"
38
+ end
39
+
40
+ it "should parse a zero length bencoded string" do
41
+ scanner = StringScanner.new("0:")
42
+ BEncodr::Parser.parse_string(scanner).should == ""
43
+ end
44
+
45
+ it "should raise an error if the length is invalid" do
46
+ scanner = StringScanner.new("fail:")
47
+ lambda {BEncodr::Parser.parse_string(scanner)}.should raise_error BEncodr::BEncodeError
48
+ end
49
+
50
+ it "should raise an error if length is too long" do
51
+ scanner = StringScanner.new("3:a")
52
+ lambda {BEncodr::Parser.parse_string(scanner)}.should raise_error BEncodr::BEncodeError
53
+ end
54
+
55
+ it "should raise an error if the colon is missing" do
56
+ scanner = StringScanner.new("3aaa")
57
+ lambda {BEncodr::Parser.parse_string(scanner)}.should raise_error BEncodr::BEncodeError
58
+ end
59
+ end
60
+
61
+ describe "#parse_integer" do
62
+ it "should parse a bencoded integer" do
63
+ scanner = StringScanner.new("i4e")
64
+ BEncodr::Parser.parse_integer(scanner).should == 4
65
+ end
66
+
67
+ it "should raise an error if there is no starting i" do
68
+ scanner = StringScanner.new("4e")
69
+ lambda{BEncodr::Parser.parse_integer(scanner)}.should raise_error BEncodr::BEncodeError
70
+ end
71
+
72
+ it "should raise an error if there is no integer" do
73
+ scanner = StringScanner.new("ie")
74
+ lambda{BEncodr::Parser.parse_integer(scanner)}.should raise_error BEncodr::BEncodeError
75
+ end
76
+
77
+ it "should raise an error if there is no closing e" do
78
+ scanner = StringScanner.new("i4")
79
+ lambda{BEncodr::Parser.parse_integer(scanner)}.should raise_error BEncodr::BEncodeError
80
+ end
81
+ end
82
+
83
+ describe "#parse_list" do
84
+ it "should parse an empty bencoded list" do
85
+ scanner = StringScanner.new("le")
86
+ BEncodr::Parser.parse_list(scanner).should == []
87
+ end
88
+
89
+ it "should parse a bencoded list containing a string" do
90
+ scanner = StringScanner.new("l6:stringeeeee")
91
+ BEncodr::Parser.parse_list(scanner).should == ["string"]
92
+ end
93
+
94
+ it "should parse a bencoded list containing more than one string" do
95
+ scanner = StringScanner.new("l6:string6:stringe")
96
+ BEncodr::Parser.parse_list(scanner).should == ["string", "string"]
97
+ end
98
+
99
+ it "should parse a bencoded list containing an integer" do
100
+ scanner = StringScanner.new("li1ee")
101
+ BEncodr::Parser.parse_list(scanner).should == [1]
102
+ end
103
+
104
+ it "should parse a bencoded list containing more than one integer" do
105
+ scanner = StringScanner.new("li1ei2ee")
106
+ BEncodr::Parser.parse_list(scanner).should == [1, 2]
107
+ end
108
+
109
+ it "should parse a bencoded list containing a list" do
110
+ scanner = StringScanner.new("llee")
111
+ BEncodr::Parser.parse_list(scanner).should == [[]]
112
+ end
113
+
114
+ it "should parse a bencoded list containing more than one list" do
115
+ scanner = StringScanner.new("llelee")
116
+ BEncodr::Parser.parse_list(scanner).should == [[], []]
117
+ end
118
+
119
+ it "should parse a bencoded list containing a dictionary" do
120
+ scanner = StringScanner.new("ldee")
121
+ BEncodr::Parser.parse_list(scanner).should == [{}]
122
+ end
123
+
124
+ it "should parse a bencoded list containing more than one dictionary" do
125
+ scanner = StringScanner.new("ldedee")
126
+ BEncodr::Parser.parse_list(scanner).should == [{}, {}]
127
+ end
128
+
129
+ it "should raise an error if there is no starting l" do
130
+ scanner = StringScanner.new("e")
131
+ lambda{BEncodr::Parser.parse_list(scanner)}.should raise_error BEncodr::BEncodeError
132
+ end
133
+
134
+ it "should raise an error if there is no closing e" do
135
+ scanner = StringScanner.new("l")
136
+ lambda{BEncodr::Parser.parse_list(scanner)}.should raise_error BEncodr::BEncodeError
137
+ end
138
+ end
139
+
140
+ describe "#parse_dictionary" do
141
+ it "should parse an empty bencoded dictionary" do
142
+ scanner = StringScanner.new("de")
143
+ BEncodr::Parser.parse_dictionary(scanner).should == {}
144
+ end
145
+
146
+ it "should parse a bencoded dictionary containing a key value pair" do
147
+ scanner = StringScanner.new("d6:stringi1ee")
148
+ BEncodr::Parser.parse_dictionary(scanner).should == {"string" => 1}
149
+ end
150
+
151
+ it "should parse a bencoded dictionary containing more than one key value pair" do
152
+ scanner = StringScanner.new("d7:anotherle6:stringi1ee")
153
+ BEncodr::Parser.parse_dictionary(scanner).should == {"string" => 1, "another" => []}
154
+ end
155
+
156
+ it "should raise an error if there is no starting d" do
157
+ scanner = StringScanner.new("e")
158
+ lambda{BEncodr::Parser.parse_dictionary(scanner)}.should raise_error BEncodr::BEncodeError
159
+ end
160
+
161
+ it "should raise an error if the key is not a string" do
162
+ scanner = StringScanner.new("di1ei1ee")
163
+ lambda{BEncodr::Parser.parse_dictionary(scanner)}.should raise_error BEncodr::BEncodeError
164
+ end
165
+
166
+ it "should raise an error if there is no closing e" do
167
+ scanner = StringScanner.new("d")
168
+ lambda{BEncodr::Parser.parse_dictionary(scanner)}.should raise_error BEncodr::BEncodeError
169
+ end
170
+
171
+ it "should raise an error if there is a key with no value" do
172
+ scanner = StringScanner.new("d1:ae")
173
+ lambda{BEncodr::Parser.parse_dictionary(scanner)}.should raise_error BEncodr::BEncodeError
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: UTF-8
2
+
3
+ require "spec"
4
+ require "spec_helper"
5
+
6
+ describe String do
7
+ describe "#bencodr" do
8
+ it "should encode a string" do
9
+ "string".bencode.should == "6:string"
10
+ end
11
+
12
+ it "should encode a zero length string" do
13
+ "".bencode.should == "0:"
14
+ end
15
+ end
16
+ end
17
+
18
+ describe Symbol do
19
+ describe "#bencodr" do
20
+ it "should encode a symbol" do
21
+ :symbol.bencode.should == "6:symbol"
22
+ end
23
+ end
24
+ end
25
+
26
+ describe URI::Generic do
27
+ describe "#bencodr" do
28
+ it "should encode a http uri" do
29
+ uri = URI.parse("http://github.com/blatyo/bencodr")
30
+ uri.bencode.should == "32:http://github.com/blatyo/bencodr"
31
+ end
32
+
33
+ it "should encode a https uri" do
34
+ uri = URI.parse("https://github.com/blatyo/bencodr")
35
+ uri.bencode.should == "33:https://github.com/blatyo/bencodr"
36
+ end
37
+
38
+ it "should encode a ftp uri" do
39
+ uri = URI.parse("ftp://github.com/blatyo/bencodr")
40
+ uri.bencode.should == "31:ftp://github.com/blatyo/bencodr"
41
+ end
42
+
43
+ it "should encode a ldap uri" do
44
+ uri = URI.parse("ldap://github.com/blatyo/bencodr")
45
+ uri.bencode.should == "32:ldap://github.com/blatyo/bencodr"
46
+ end
47
+
48
+ it "should encode a mailto uri" do
49
+ uri = URI.parse("mailto:sudo@sudoers.su")
50
+ uri.bencode.should == "22:mailto:sudo@sudoers.su"
51
+ end
52
+ end
53
+ end
54
+
55
+ describe BEncodr::String do
56
+ describe "#register" do
57
+ context "once an object has been registered as a BEncode string" do
58
+ before :all do
59
+ BEncodr::String.register Range
60
+ end
61
+
62
+ context "an instance of that object" do
63
+ it "should respond to bencodr" do
64
+ (1..2).should respond_to :bencode
65
+ end
66
+
67
+ it "should encode to a bencoded string" do
68
+ (1..2).bencode.should == "4:1..2"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1 @@
1
+ d8:announce42:http://tracker.openbittorrent.com/announce13:announce-listll42:http://tracker.openbittorrent.com/announce44:udp://tracker.openbittorrent.com:80/announceee7:comment28:Best bencoding library ever!10:created by13:uTorrent/185013:creation datei1264866817e8:encoding5:UTF-84:infod6:lengthi526e4:name10:bencode.rb12:piece lengthi65536e6:pieces20:�WiP�2�n�����U��W&��ee
@@ -0,0 +1 @@
1
+ d2:bai3ee
Binary file
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'bencodr'
6
+ require 'spec'
7
+ require 'spec/autorun'
8
+
9
+ Spec::Runner.configure do |config|
10
+
11
+ end