bencodr 1.0.1

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,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