bencode 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/bencode.rb +183 -0
- data/test/tc_bdecode.rb +42 -0
- data/test/tc_bencode.rb +30 -0
- metadata +48 -0
data/lib/bencode.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
# Thanks to Daniel Martin and all the other lads at ruby-lang for
|
2
|
+
# helping me out.
|
3
|
+
|
4
|
+
|
5
|
+
class Object
|
6
|
+
# Raises an exception. Subclasses of Object must themselves
|
7
|
+
# define meaningful #bencode methods.
|
8
|
+
def bencode
|
9
|
+
raise BencodeError, self.class
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Integer
|
14
|
+
# Bencodes the Integer object. Bencoded integers are represented
|
15
|
+
# as +ixe+, where +x+ is the integer with an optional
|
16
|
+
# hyphen prepended, indicating negativity.
|
17
|
+
#
|
18
|
+
# 42.bencode #=> "i42e"
|
19
|
+
# -7.bencode #=> "i-7e"
|
20
|
+
def bencode
|
21
|
+
"i#{self}e"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class String
|
26
|
+
# Bencodes the String object. Bencoded strings are represented
|
27
|
+
# as +x:y+, where +y+ is the string and x is the length of the
|
28
|
+
# string.
|
29
|
+
#
|
30
|
+
# "foo".bencode #=> "3:foo"
|
31
|
+
# "".bencode #=> "0:"
|
32
|
+
def bencode
|
33
|
+
"#{length}:#{self}"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Bdecodes the String object and returns the data serialized
|
37
|
+
# through bencoding.
|
38
|
+
#
|
39
|
+
# "li1ei2ei3ee".bdecode #=> [1, 2, 3]
|
40
|
+
def bdecode
|
41
|
+
Bencode.load(self)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Tests whether the String object is a valid bencoded string.
|
45
|
+
def bencoded?
|
46
|
+
bdecode
|
47
|
+
rescue BdecodeError
|
48
|
+
false
|
49
|
+
else
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Array
|
55
|
+
# Bencodes the Array object. Bencoded arrays are represented as
|
56
|
+
# +lxe+, where x is zero or more bencoded objects.
|
57
|
+
#
|
58
|
+
# [1, "foo"].bencode #=> "li1e3:fooe"
|
59
|
+
def bencode
|
60
|
+
"l#{map{|obj| obj.bencode}.join('') }e"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Hash
|
65
|
+
# Bencodes the Hash object. Bencoded hashes are represented as
|
66
|
+
# +dxe+, where x is zero or a power of two bencoded objects.
|
67
|
+
# each key is immediately followed by its associated value.
|
68
|
+
# All keys must be strings. The keys of the bencoded hash will
|
69
|
+
# be in lexicographical order.
|
70
|
+
def bencode
|
71
|
+
pairs = map{|key, val| [key.to_str.bencode, val.bencode] }
|
72
|
+
pairs.sort!{|a, b| a.first <=> b.first }
|
73
|
+
"d#{pairs.join('')}e"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class IO
|
78
|
+
def self.bdecode(*args)
|
79
|
+
new(*args).bdecode
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.bencode(*args)
|
83
|
+
new(*args).bencode
|
84
|
+
end
|
85
|
+
|
86
|
+
def bdecode
|
87
|
+
read.chomp.bdecode
|
88
|
+
end
|
89
|
+
|
90
|
+
def bencode
|
91
|
+
read.chomp.bdecode
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class BencodeError < StandardError
|
96
|
+
def initialize(object_class = nil) # :nodoc:
|
97
|
+
@object_class = object_class
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_s # :nodoc:
|
101
|
+
"could not bencode #{@object_class}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class BdecodeError < StandardError
|
106
|
+
attr_reader :pos
|
107
|
+
|
108
|
+
def initialize(pos = nil) # :nodoc:
|
109
|
+
@pos = pos
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_s # :nodoc:
|
113
|
+
if pos.nil?
|
114
|
+
"syntax error"
|
115
|
+
else
|
116
|
+
"syntax error near position #{pos}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module Bencode
|
122
|
+
class << self
|
123
|
+
def dump(obj)
|
124
|
+
obj.bencode
|
125
|
+
end
|
126
|
+
|
127
|
+
def load(str)
|
128
|
+
require 'strscan'
|
129
|
+
|
130
|
+
scanner = StringScanner.new(str)
|
131
|
+
obj = parse(scanner)
|
132
|
+
raise BdecodeError unless scanner.eos?
|
133
|
+
return obj
|
134
|
+
end
|
135
|
+
|
136
|
+
def load_file(path)
|
137
|
+
load(File.open(path).read)
|
138
|
+
end
|
139
|
+
|
140
|
+
def parse(scanner) # :nodoc:
|
141
|
+
case token = scanner.scan(/[ild]|\d+:|\s/)
|
142
|
+
when nil
|
143
|
+
raise BdecodeError, scanner.pos
|
144
|
+
when "i"
|
145
|
+
number = scanner.scan(/0|(?:-?[1-9][0-9]*)/)
|
146
|
+
unless number and scanner.scan(/e/)
|
147
|
+
raise BdecodeError, scanner.pos
|
148
|
+
end
|
149
|
+
return number.to_i
|
150
|
+
when "l"
|
151
|
+
ary = []
|
152
|
+
until scanner.peek(1) == "e"
|
153
|
+
ary.push(parse(scanner))
|
154
|
+
end
|
155
|
+
scanner.pos += 1
|
156
|
+
return ary
|
157
|
+
when "d"
|
158
|
+
hsh = {}
|
159
|
+
until scanner.peek(1) == "e"
|
160
|
+
key, value = parse(scanner), parse(scanner)
|
161
|
+
unless key.is_a? String
|
162
|
+
raise BdecodeError, "error at #{scanner.pos}: " +
|
163
|
+
"key must be a string"
|
164
|
+
end
|
165
|
+
hsh.store(key, value)
|
166
|
+
end
|
167
|
+
scanner.pos += 1
|
168
|
+
return hsh
|
169
|
+
when /\d+:/
|
170
|
+
length = token.chop.to_i
|
171
|
+
str = scanner.peek(length)
|
172
|
+
scanner.pos += length
|
173
|
+
return str
|
174
|
+
when /\s/
|
175
|
+
nil
|
176
|
+
else
|
177
|
+
raise BdecodeError, scanner.pos
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
private :parse
|
182
|
+
end
|
183
|
+
end
|
data/test/tc_bdecode.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require 'bencode'
|
4
|
+
|
5
|
+
class BdecodeTest < Test::Unit::TestCase
|
6
|
+
def test_string
|
7
|
+
assert_equal "foo", "3:foo".bdecode
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_integer
|
11
|
+
assert_equal 42, "i42e".bdecode
|
12
|
+
assert_raise BdecodeError do
|
13
|
+
"i01e".bdecode
|
14
|
+
end
|
15
|
+
assert_raise BdecodeError do
|
16
|
+
"i-01e".bdecode
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_array
|
21
|
+
assert_equal [1, 2, 3], "li1ei2ei3ee".bdecode
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_hash
|
25
|
+
hsh = {"a" => "monkey", "h" => "elephant", "z" => "zebra"}
|
26
|
+
assert_equal hsh, "d1:a6:monkey1:h8:elephant1:z5:zebrae".bdecode
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_invalid
|
30
|
+
assert_raises BdecodeError do
|
31
|
+
"foobar".bdecode
|
32
|
+
"i1ei2e".bdecode
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_valid
|
37
|
+
assert "3:foo".bencoded?
|
38
|
+
assert !"3:foo3:bar".bencoded?
|
39
|
+
assert "i42e".bencoded?
|
40
|
+
assert !"i42ei101e".bencoded?
|
41
|
+
end
|
42
|
+
end
|
data/test/tc_bencode.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
require 'unit/test'
|
3
|
+
require 'bencode'
|
4
|
+
|
5
|
+
class BencodeTest < Test::Unit::TestCase
|
6
|
+
def test_string
|
7
|
+
assert_equal "3:foo", "foo".bencode
|
8
|
+
assert_equal "0:", "".bencode
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_integer
|
12
|
+
assert_equal "i42e", 42.bencode
|
13
|
+
assert_equal "i-3e", -3.bencode
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_float
|
17
|
+
assert_raise BencodeError do
|
18
|
+
42.4.bencode
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_array
|
23
|
+
assert_equal "li1ei2ei3ee", [1, 2, 3].bencode
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_hash
|
27
|
+
assert_equal "d1:a3:foo1:g3:bar1:z3:baze",
|
28
|
+
{"a" => "foo", "g" => "bar", "z" => "baz"}.bencode
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: bencode
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2006-09-17 00:00:00 +02:00
|
8
|
+
summary: Bencode and -decode data
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email:
|
12
|
+
homepage:
|
13
|
+
rubyforge_project: bencode
|
14
|
+
description:
|
15
|
+
autorequire: bencode
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Daniel Schierbeck
|
30
|
+
files:
|
31
|
+
- lib/bencode.rb
|
32
|
+
- test/tc_bdecode.rb
|
33
|
+
- test/tc_bencode.rb
|
34
|
+
test_files:
|
35
|
+
- test/tc_bdecode.rb
|
36
|
+
- test/tc_bencode.rb
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
executables: []
|
42
|
+
|
43
|
+
extensions: []
|
44
|
+
|
45
|
+
requirements: []
|
46
|
+
|
47
|
+
dependencies: []
|
48
|
+
|