rbkb 0.6.10

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.
Files changed (101) hide show
  1. data/History.txt +74 -0
  2. data/README.rdoc +149 -0
  3. data/Rakefile +47 -0
  4. data/bin/b64 +5 -0
  5. data/bin/bgrep +5 -0
  6. data/bin/blit +5 -0
  7. data/bin/c +5 -0
  8. data/bin/crc32 +5 -0
  9. data/bin/d64 +5 -0
  10. data/bin/dedump +5 -0
  11. data/bin/feed +5 -0
  12. data/bin/hexify +5 -0
  13. data/bin/len +5 -0
  14. data/bin/plugsrv +271 -0
  15. data/bin/rex +10 -0
  16. data/bin/rstrings +5 -0
  17. data/bin/slice +5 -0
  18. data/bin/telson +5 -0
  19. data/bin/unhexify +5 -0
  20. data/bin/urldec +5 -0
  21. data/bin/urlenc +5 -0
  22. data/bin/xor +5 -0
  23. data/cli_usage.rdoc +285 -0
  24. data/doctor-bag.jpg +0 -0
  25. data/lib/rbkb.rb +51 -0
  26. data/lib/rbkb/cli.rb +219 -0
  27. data/lib/rbkb/cli/b64.rb +35 -0
  28. data/lib/rbkb/cli/bgrep.rb +86 -0
  29. data/lib/rbkb/cli/blit.rb +89 -0
  30. data/lib/rbkb/cli/chars.rb +24 -0
  31. data/lib/rbkb/cli/crc32.rb +35 -0
  32. data/lib/rbkb/cli/d64.rb +28 -0
  33. data/lib/rbkb/cli/dedump.rb +52 -0
  34. data/lib/rbkb/cli/feed.rb +229 -0
  35. data/lib/rbkb/cli/hexify.rb +65 -0
  36. data/lib/rbkb/cli/len.rb +76 -0
  37. data/lib/rbkb/cli/rstrings.rb +108 -0
  38. data/lib/rbkb/cli/slice.rb +47 -0
  39. data/lib/rbkb/cli/telson.rb +87 -0
  40. data/lib/rbkb/cli/unhexify.rb +50 -0
  41. data/lib/rbkb/cli/urldec.rb +35 -0
  42. data/lib/rbkb/cli/urlenc.rb +35 -0
  43. data/lib/rbkb/cli/xor.rb +43 -0
  44. data/lib/rbkb/extends.rb +725 -0
  45. data/lib/rbkb/http.rb +21 -0
  46. data/lib/rbkb/http/base.rb +172 -0
  47. data/lib/rbkb/http/body.rb +214 -0
  48. data/lib/rbkb/http/common.rb +74 -0
  49. data/lib/rbkb/http/headers.rb +370 -0
  50. data/lib/rbkb/http/parameters.rb +104 -0
  51. data/lib/rbkb/http/request.rb +58 -0
  52. data/lib/rbkb/http/response.rb +86 -0
  53. data/lib/rbkb/plug.rb +9 -0
  54. data/lib/rbkb/plug/blit.rb +222 -0
  55. data/lib/rbkb/plug/cli.rb +83 -0
  56. data/lib/rbkb/plug/feed_import.rb +74 -0
  57. data/lib/rbkb/plug/peer.rb +67 -0
  58. data/lib/rbkb/plug/plug.rb +215 -0
  59. data/lib/rbkb/plug/proxy.rb +26 -0
  60. data/lib/rbkb/plug/unix_domain.rb +75 -0
  61. data/lib_usage.rdoc +176 -0
  62. data/rbkb.gemspec +38 -0
  63. data/spec/rbkb_spec.rb +7 -0
  64. data/spec/spec_helper.rb +16 -0
  65. data/tasks/ann.rake +80 -0
  66. data/tasks/bones.rake +20 -0
  67. data/tasks/gem.rake +201 -0
  68. data/tasks/git.rake +40 -0
  69. data/tasks/notes.rake +27 -0
  70. data/tasks/post_load.rake +34 -0
  71. data/tasks/rdoc.rake +51 -0
  72. data/tasks/rubyforge.rake +55 -0
  73. data/tasks/setup.rb +292 -0
  74. data/tasks/spec.rake +54 -0
  75. data/tasks/svn.rake +47 -0
  76. data/tasks/test.rake +40 -0
  77. data/test/test_cli_b64.rb +35 -0
  78. data/test/test_cli_bgrep.rb +137 -0
  79. data/test/test_cli_blit.rb +11 -0
  80. data/test/test_cli_chars.rb +21 -0
  81. data/test/test_cli_crc32.rb +108 -0
  82. data/test/test_cli_d64.rb +22 -0
  83. data/test/test_cli_dedump.rb +118 -0
  84. data/test/test_cli_feed.rb +11 -0
  85. data/test/test_cli_helper.rb +96 -0
  86. data/test/test_cli_hexify.rb +63 -0
  87. data/test/test_cli_len.rb +96 -0
  88. data/test/test_cli_rstrings.rb +15 -0
  89. data/test/test_cli_slice.rb +73 -0
  90. data/test/test_cli_telson.rb +11 -0
  91. data/test/test_cli_unhexify.rb +43 -0
  92. data/test/test_cli_urldec.rb +50 -0
  93. data/test/test_cli_urlenc.rb +44 -0
  94. data/test/test_cli_xor.rb +71 -0
  95. data/test/test_helper.rb +5 -0
  96. data/test/test_http.rb +27 -0
  97. data/test/test_http_helper.rb +60 -0
  98. data/test/test_http_request.rb +136 -0
  99. data/test/test_http_response.rb +222 -0
  100. data/test/test_rbkb.rb +19 -0
  101. metadata +238 -0
data/lib/rbkb/http.rb ADDED
@@ -0,0 +1,21 @@
1
+
2
+ # ???Why???? would anyone create their own HTTP implementation in ruby with
3
+ # so many options out there? Short answer: Net:HTTP and others just don't cut
4
+ # it in lots of edge cases. I needed something I could control completely.
5
+
6
+ module Rbkb
7
+ module Http
8
+ VERSION = "0.0.3"
9
+ end
10
+ end
11
+
12
+ require 'time' # gives us Time.httpdate parser and output methods
13
+
14
+ require "rbkb/http/common.rb"
15
+ require "rbkb/http/base.rb"
16
+ require "rbkb/http/request.rb"
17
+ require "rbkb/http/response.rb"
18
+ require "rbkb/http/headers.rb"
19
+ require "rbkb/http/body.rb"
20
+ require "rbkb/http/parameters.rb"
21
+
@@ -0,0 +1,172 @@
1
+ module Rbkb::Http
2
+
3
+ # A base class containing some common features for Request and Response
4
+ # objects.
5
+ #
6
+ # Don't use this class directly, it's intended for being overridden
7
+ # from its derived classes or mixins.
8
+ class Base
9
+ include CommonInterface
10
+
11
+ def self.parse(*args)
12
+ new(*args)
13
+ end
14
+
15
+ # Initializes a new Base object
16
+ def initialize(*args)
17
+ _common_init(*args)
18
+ end
19
+
20
+ # This method parses just HTTP message body. Expects body to be split
21
+ # from the headers before-hand.
22
+ def capture_body(bstr)
23
+ self.body ||= default_body_obj
24
+ @body.capture(bstr)
25
+ end
26
+
27
+ # XXX stub
28
+ def first_entity
29
+ @first_entity
30
+ end
31
+
32
+ # XXX stub
33
+ def first_entity=(f)
34
+ @first_entity=(f)
35
+ end
36
+
37
+ # This method parses only HTTP response headers. Expects headers to be
38
+ # split from the body before-hand.
39
+ def capture_headers(hstr)
40
+ self.headers ||= default_headers_obj
41
+
42
+ if @body and not @body.capture_complete?
43
+ return
44
+ elsif @headers.capture_complete?
45
+ self.first_entity, @headers = default_headers_obj.capture_full_headers(hstr)
46
+ else
47
+ @headers.capture(hstr)
48
+ end
49
+ end
50
+
51
+ # This method returns the content length from Headers. This is
52
+ # mostly useful if you are using a BoundBody object for the body.
53
+ #
54
+ # Returns nil if no "Content-Length" is not found.
55
+ #
56
+ # The opts parameter :ignore_content_length affects this method and
57
+ # will cause it always to return nil. This is useful, for example,
58
+ # for the responses to the HTTP HEAD request method, which return
59
+ # a Content-Length without actual content.
60
+ #
61
+ def content_length(hdrs=@headers)
62
+ raise "headers is nil?" if not hdrs
63
+ if( (not @opts[:ignore_content_length]) and
64
+ hdrs.get_header_value("Content-Length").to_s =~ /^(\d+)$/ )
65
+
66
+ $1.to_i
67
+ end
68
+ end
69
+
70
+ def attach_new_header(hdr_obj=nil)
71
+ self.headers = hdr_obj
72
+ return hdr_obj
73
+ end
74
+
75
+ def attach_new_body(body_obj=nil)
76
+ self.body = body_obj
77
+ return body_obj
78
+ end
79
+
80
+ # XXX doc override!
81
+ def default_headers_obj(*args)
82
+ Header.new(*args)
83
+ end
84
+
85
+ # XXX doc override!
86
+ def default_body_obj(*args)
87
+ Body.new(*args)
88
+ end
89
+
90
+ # This method will non-destructively reset the capture state on this
91
+ # object and all child entities. Note, however, If child entities are not
92
+ # defined, it may instantiate new ones.
93
+ # See also: capture_complete?, reset_capture!
94
+ def reset_capture
95
+ if @headers
96
+ @headers.reset_capture if not @headers.capture_complete?
97
+ else
98
+ attach_new_header()
99
+ end
100
+
101
+ if @body
102
+ @body.reset_capture if not @body.capture_complete?
103
+ else
104
+ attach_new_body()
105
+ end
106
+ @capture_state = nil
107
+ self
108
+ end
109
+
110
+ # This method will destructively reset the capture state on this object.
111
+ # It does so by initializing fresh child entities and discarding the old
112
+ # ones. See also: capture_complete?, reset_capture
113
+ def reset_capture!
114
+ attach_new_header()
115
+ attach_new_body()
116
+ @capture_state = nil
117
+ self
118
+ end
119
+
120
+ # Indicates whether this object is ready to capture fresh data, or is
121
+ # waiting for additional data or a reset from a previous incomplete or
122
+ # otherwise broken capture. See also: reset_capture, reset_capture!
123
+ def capture_complete?
124
+ if( (@headers and not @headers.capture_complete?) or
125
+ (@body and not @body.capture_complete?) )
126
+ return false
127
+ else
128
+ true
129
+ end
130
+ end
131
+
132
+ attr_reader :body, :headers
133
+
134
+ # This accessor will attempt to always do the "right thing" while
135
+ # setting this object's body entity.
136
+ #
137
+ # See also: default_body_obj
138
+ def body=(b)
139
+ if @body
140
+ @body.data = b
141
+ elsif b.kind_of? Body
142
+ @body = b.dup
143
+ @body.opts = b.opts
144
+ else
145
+ @body = default_body_obj(b)
146
+ end
147
+ @body.base = self
148
+ return @body
149
+ end
150
+
151
+ # This accessor will attempt to always do the "right thing" while
152
+ # setting this object's headers entity.
153
+ #
154
+ # See also: default_headers_obj
155
+ def headers=(h)
156
+ if @headers
157
+ @headers.data = h
158
+ elsif h.kind_of? Headers
159
+ @headers = h.dup
160
+ @headers.opts = h.opts
161
+ else
162
+ @headers = default_headers_obj(h)
163
+ end
164
+ @headers.base = self
165
+ return @body
166
+ end
167
+
168
+ end
169
+
170
+
171
+ end
172
+
@@ -0,0 +1,214 @@
1
+ require 'stringio'
2
+
3
+ module Rbkb::Http
4
+ class Body < String
5
+ include CommonInterface
6
+
7
+ def self.parse(str)
8
+ new().capture(str)
9
+ end
10
+
11
+ attr_reader :expect_length
12
+
13
+ def initialize(str=nil, opts=nil)
14
+ self.opts = opts
15
+ if Body === str
16
+ self.replace(str)
17
+ @opts = str.opts.merge(@opts)
18
+ elsif String === str
19
+ super(str)
20
+ else
21
+ super()
22
+ end
23
+
24
+ yield(self) if block_given?
25
+ end
26
+
27
+ # The capture method is used when parsing HTTP requests/responses.
28
+ # This can and probably should be overridden in derived classes.
29
+ def capture(str)
30
+ yield(str) if block_given?
31
+ self.data=(str)
32
+ end
33
+
34
+ # The to_raw method is used when writing HTTP requests/responses.
35
+ # This can and probably should be overridden in derived classes.
36
+ def to_raw
37
+ (block_given?) ? yield(self.data) : self.data
38
+ end
39
+
40
+ attr_reader :base
41
+
42
+ def base=(b)
43
+ if b.nil? or b.is_a? Base
44
+ @base = b
45
+ else
46
+ raise "base must be a Response or Request object or nil"
47
+ end
48
+ end
49
+
50
+ def data
51
+ self
52
+ end
53
+
54
+ # Sets internal raw string data without any HTTP decoration.
55
+ def data=(str)
56
+ self.replace(str.to_s)
57
+ end
58
+
59
+ # Returns the content length from the HTTP base object if
60
+ # there is one and content-length is available.
61
+ def get_content_length
62
+ @base.content_length if @base
63
+ end
64
+
65
+ # This method will non-destructively reset the capture state on this object.
66
+ # It is non-destructive in that it will not affect existing captured data
67
+ # if present.
68
+ def reset_capture
69
+ @expect_length = nil
70
+ @base.reset_capture() if @base and @base.capture_complete?
71
+ end
72
+
73
+ # This method will destructively reset the capture state on this object.
74
+ # This method is destructive in that it will clear any previously captured
75
+ # data.
76
+ def reset_capture!
77
+ reset_capture()
78
+ self.data=""
79
+ end
80
+
81
+ def capture_complete?
82
+ not @expect_length
83
+ end
84
+ end
85
+
86
+
87
+ # BoundBody is designed for handling an HTTP body when using the usual
88
+ # "Content-Length: NNN" HTTP header.
89
+ class BoundBody < Body
90
+
91
+ # This method may throw :expect_length with one of the following values
92
+ # to indicate certain content-length conditions:
93
+ #
94
+ # > 0 : Got incomplete data in this capture. The object expects
95
+ # capture to be called again with more body data.
96
+ #
97
+ # < 0 : Got more data than expected, the caller should truncate and
98
+ # handle the extra data in some way. Note: Calling capture again
99
+ # on this instance will start a fresh body capture.
100
+ #
101
+ # Caller can also detect the above conditions by checking the expect_length
102
+ # attribute but should still be prepared handle the throw().
103
+ #
104
+ # 0/nil: Got exactly what was expected. Caller can proceed with fresh
105
+ # captures on this or other Body objects.
106
+ #
107
+ # See also reset_capture and reset_capture!
108
+ def capture(str)
109
+ raise "arg 0 must be a string" unless String === str
110
+
111
+ # Start fresh unless we're expecting more data
112
+ self.data="" unless @expect_length and @expect_length > 0
113
+
114
+ if not clen=get_content_length()
115
+ raise "content-length is unknown. aborting capture"
116
+ else
117
+ @expect_length = clen - (self.size + str.size)
118
+ self << str[0, clen - self.size]
119
+ if @expect_length > 0
120
+ throw(:expect_length, @expect_length)
121
+ elsif @expect_length < 0
122
+ throw(:expect_length, @expect_length)
123
+ else
124
+ reset_capture()
125
+ end
126
+ end
127
+ return self
128
+ end
129
+
130
+ def to_raw(*args)
131
+ if @base
132
+ @base.headers.set_header("Content-Length", self.size)
133
+ end
134
+ super(*args)
135
+ end
136
+ end
137
+
138
+
139
+ # ChunkedBody is designed for handling an HTTP body when using a
140
+ # "Transfer-Encoding: chunked" HTTP header.
141
+ class ChunkedBody < Body
142
+ DEFAULT_CHUNK_SIZE = 2048
143
+
144
+ # Throws :expect_length with 'true' when given incomplete data and expects
145
+ # to be called again with more body data to parse.
146
+ #
147
+ # The caller can also detect this condition by checking the expect_length
148
+ # attribute but must still handle the throw().
149
+ #
150
+ # See also reset_capture and reset_capture!
151
+ def capture(str)
152
+ # chunked encoding is gross...
153
+ if @expect_length
154
+ sio = StringIO.new(@last_chunk.to_s + str)
155
+ else
156
+ sio = StringIO.new(str)
157
+ self.data=""
158
+ end
159
+ @last_chunk = nil
160
+
161
+ @expect_length = true
162
+ while not sio.eof?
163
+ unless m=/^([a-fA-F0-9]+)\s*(;[[:print:]\s]*)?\r?\n$/.match(line=sio.readline)
164
+ raise "invalid chunk at #{line.chomp.inspect}"
165
+ end
166
+ if (chunksz = m[1].hex) == 0
167
+ @expect_length = false
168
+ # XXX ignore Trailer headers
169
+ break
170
+ end
171
+
172
+ if ( (not sio.eof?) and
173
+ (chunk=sio.read(chunksz)) and
174
+ chunk.size == chunksz and
175
+ (not sio.eof?) and (extra = sio.readline) and
176
+ (not sio.eof?) and (extra << sio.readline)
177
+ )
178
+ if extra =~ /^\r?\n\r?\n$/
179
+ yield(chunk) if block_given?
180
+ self << chunk
181
+ else
182
+ raise "expected CRLF"
183
+ end
184
+ else
185
+ @last_chunk = line + chunk.to_s + extra.to_s
186
+ break
187
+ end
188
+ end
189
+ throw(:expect_length, @expect_length) if @expect_length
190
+ return self
191
+ end
192
+
193
+
194
+ def to_raw(csz=nil)
195
+ csz ||= (@opts[:output_chunk_size] || DEFAULT_CHUNK_SIZE)
196
+ unless csz.kind_of? Integer and csz > 0
197
+ raise "chunk size must be an integer >= 1"
198
+ end
199
+
200
+ out=[]
201
+ i=0
202
+ while i <= self.size
203
+ chunk = self[i, csz]
204
+ out << "#{chunk.size.to_s(16)}\r\n#{chunk}\r\n\r\n"
205
+ yield(self, out.last) if block_given?
206
+ i+=csz
207
+ end
208
+ out << "0\r\n"
209
+ yield(self, out.last) if block_given?
210
+ return out.join
211
+ end
212
+ end
213
+ end
214
+
@@ -0,0 +1,74 @@
1
+ module Rbkb::Http
2
+ DEFAULT_HTTP_VERSION = "HTTP/1.1"
3
+
4
+ module CommonInterface
5
+ # This provides a common method for use in 'initialize' to slurp in
6
+ # opts parameters and optionally capture a raw blob. This method also
7
+ # accepts a block to which it yields 'self'
8
+ def _common_init(raw=nil, opts=nil)
9
+ self.opts = opts
10
+ yield self if block_given?
11
+ capture(raw) if raw
12
+ return self
13
+ end
14
+
15
+ # Implements a common interface for an opts hash which is stored internally
16
+ # as the class variable @opts.
17
+ #
18
+ # The opts hash is designed to contain various named values for
19
+ # configuration, etc. The values and names are determined entirely
20
+ # by the class that uses it.
21
+ def opts
22
+ @opts
23
+ end
24
+
25
+ # Implements a common interface for setting a new opts hash containing
26
+ # various named values for configuration, etc. This also performs a
27
+ # minimal sanity check to ensure the object is a Hash.
28
+ def opts=(o=nil)
29
+ raise "opts must be a hash" unless (o ||= {}).is_a? Hash
30
+ @opts = o
31
+ end
32
+ end
33
+
34
+
35
+ # A generic cheat for an Array of named value pairs to pretend to
36
+ # be like Hash when using [] and []=
37
+ class NamedValueArray < Array
38
+
39
+ # Act like a hash with named values. Return the named value if a string
40
+ # or Symbol is supplied as the index argument.
41
+ #
42
+ # Note, this doesn't do any magic with String / Symbol conversion.
43
+ def [](*args)
44
+ if args.size == 1 and (String === args[0] or Symbol === args[0])
45
+ if h=find {|x| x[0] == args[0]}
46
+ return h[1]
47
+ end
48
+ else
49
+ super(*args)
50
+ end
51
+ end
52
+
53
+ # Act like a hash with named values. Set the named value if a String
54
+ # or Symbol is supplied as the index argument.
55
+ #
56
+ # Note, this doesn't do any magic with String / Symbol conversion.
57
+ def []=(*args)
58
+ if args.size > 1 and (String === args[0] or Symbol === args[0])
59
+ if h=find {|x| x[0] == args[0]}
60
+ h[1] = args[1]
61
+ else
62
+ self << args[0,2]
63
+ end
64
+ else
65
+ super(*args)
66
+ end
67
+ end
68
+
69
+ def delete_key(key)
70
+ delete_if {|x| x[0] == key }
71
+ end
72
+ end
73
+
74
+ end