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
@@ -0,0 +1,370 @@
1
+ require 'uri'
2
+
3
+ module Rbkb::Http
4
+
5
+ # A base class for RequestHeaders and ResponseHeaders
6
+ #
7
+ # Includes common implementations of to_raw, to_raw_array, capture, and
8
+ # the class method parse
9
+ #
10
+ # The Headers array are stored internally as an named value pairs array.
11
+ #
12
+ # The headers are generally name/value pairs in the form of:
13
+ #
14
+ # [ ["Name1", "value1"], ["Name2", "value2"], ... ]
15
+ #
16
+ # Which will be rendered with to_raw() to (or captured with capture() from):
17
+ #
18
+ # Name1: value1
19
+ # Name2: value2
20
+ # ...
21
+ #
22
+ # This has the benefit of letting the data= accessor automatically render a
23
+ # Hash or any other Enumerable to a Headers object through the use of to_a.
24
+ # However it has the caveat that named pairs are expected on various
25
+ # operations.
26
+ class Headers < Array
27
+ include CommonInterface
28
+
29
+ # Class method to instantiate a new RequestHeaders object
30
+ def self.request_hdr(*args)
31
+ Headers.new(*args).extend(RequestHeaders)
32
+ end
33
+
34
+ # Class method to instantiate a new ResponseHeaders object
35
+ def self.response_hdr(*args)
36
+ Headers.new(*args).extend(ResponseHeaders)
37
+ end
38
+
39
+ # Instantiates a new Headers object and returns the result of capture(str)
40
+ # Note, this method does not distinguish between ResponseHeaders or
41
+ # RequestHeaders, and so the object may need to be extended with one
42
+ # or the other, if you need access to specific behviors from either.
43
+ def self.parse(str)
44
+ new().capture(str)
45
+ end
46
+
47
+ # Instantiates a new Headers object and returns the result of
48
+ # capture_full_headers(str, first_obj)
49
+ def self.parse_full_headers(str, first_obj)
50
+ new().capture_full_headers(str, first_obj)
51
+ end
52
+
53
+ # Instantiates a new Headers object.
54
+ #
55
+ # Arguments:
56
+ # raw: String or Enumerable. Strings are parsed with capture.
57
+ # Enumerables are converted with 'to_a' and stored directly.
58
+ #
59
+ # opts: Options which affect the behavior of the Headers object.
60
+ # (none currently defined)
61
+ #
62
+ def initialize(*args)
63
+ super()
64
+ if args.first.kind_of? Enumerable
65
+ raw=args.first
66
+ args[0]=nil
67
+ _common_init(*args)
68
+ self.data = raw.to_a
69
+ else
70
+ _common_init(*args)
71
+ end
72
+ end
73
+
74
+ attr_reader :base
75
+
76
+ # Conditionally sets the @base class variable if it is a kind of Base
77
+ # object.
78
+ def base=(b)
79
+ if b.nil? or b.kind_of? Base
80
+ @base = b
81
+ else
82
+ raise "base must be a kind of Base object or nil"
83
+ end
84
+ end
85
+
86
+ # The data method provides a common interface to access internal
87
+ # non-raw information stored in the object.
88
+ #
89
+ # The Headers incarnation returns the internal headers array
90
+ # (actually self).
91
+ def data
92
+ self
93
+ end
94
+
95
+ # The data= method provides a common interface to access internal
96
+ # non-raw information stored in the object.
97
+ #
98
+ # This method stores creates a shallow copy for anything but another
99
+ # Headers object which it references directly. A few rules are enforced:
100
+ # * 1-dimensional elements will be expanded to tuples with 'nil' as the
101
+ # second value.
102
+ #
103
+ # * Names which are enumerables will be 'join()'ed, but not values.
104
+ def data=(d)
105
+ if d.kind_of? Headers
106
+ self.replace d
107
+ else
108
+ self.replace []
109
+ d.to_a.each do |k, v|
110
+ k = k.to_s if k.is_a? Numeric
111
+ self << [k,v]
112
+ end
113
+ end
114
+ return self
115
+ end
116
+
117
+ # The to_raw_array method returns an interim formatted array of raw
118
+ # "Cookie: Value" strings.
119
+ def to_raw_array
120
+ self.map {|h,v| "#{h}: #{v}" }
121
+ end
122
+
123
+ def get_all(k)
124
+ self.select {|h| h[0].downcase == k.downcase }
125
+ end
126
+
127
+ def get_all_values_for(k)
128
+ self.get_all(k).collect {|h,v| v }
129
+ end
130
+ alias all_values_for get_all_values_for
131
+
132
+ def get_header(k)
133
+ self.find {|h| h[0].downcase == k.downcase }
134
+ end
135
+
136
+ def get_value_for(k)
137
+ if h=self.get_header(k)
138
+ return h[1]
139
+ end
140
+ end
141
+ alias get_header_value get_value_for
142
+ alias value_for get_value_for
143
+
144
+ def delete_header(k)
145
+ self.delete_if {|h| h[0].downcase == k.downcase }
146
+ end
147
+
148
+ def set_header(k,v)
149
+ sel = get_all(k)
150
+
151
+ if sel.empty?
152
+ self << [k,v]
153
+ return [[k,v]]
154
+ else
155
+ sel.each {|h| h[1] = v }
156
+ return sel
157
+ end
158
+ end
159
+ alias set_all_for set_header
160
+
161
+ # The to_raw method returns a raw string of headers as they appear
162
+ # on the wire.
163
+ def to_raw
164
+ to_raw_array.join("\r\n") << "\r\n"
165
+ end
166
+
167
+ # Captures a raw string of headers into this instance's internal array.
168
+ # Note: This method expects not to include the first element such as a
169
+ # RequestAction or ResponseStatus. See capture_full_headers for a version
170
+ # that can handle this.
171
+ def capture(str)
172
+
173
+ raise "arg 0 must be a string" unless str.is_a?(String)
174
+ heads = str.split(/\s*\r?\n/)
175
+
176
+ # pass interim parsed headers to a block if given
177
+ yield(self, heads) if block_given?
178
+
179
+ self.replace [] if capture_complete?
180
+ heads.each do |s|
181
+ k,v = s.split(/\s*:\s*/, 2)
182
+ self << [k,v]
183
+ end
184
+ return self
185
+ end
186
+
187
+ # See capture_full_headers. This method is used to resolve the parser
188
+ # for the first entity above the HTTP headers. This instance is designed
189
+ # to raise an exception when capturing.
190
+ def get_first_obj; raise "get_first_obj called on base stub"; end
191
+
192
+ # This method parses a full set of raw headers from the 'str' argument.
193
+ # Unlike the regular capture method, the string is expected to start
194
+ # with a line which will be parsed by first_obj using its own capture
195
+ # method. For example, first_obj would parse something like
196
+ # "GET / HTTP/1.1" for RequestAction or "HTTP/1.1 200 OK" for
197
+ # ResponseStatus. If first_obj is not defined, there will be an attempt
198
+ # to resolve it by calling get_first_obj which should return the
199
+ # appropriate type of object or raise an exception.
200
+ #
201
+ # Returns a 2 element array containing [first_entity, headers]
202
+ # where first entity is the instantiated first_obj object and headers
203
+ # is self.
204
+ def capture_full_headers(str, first_obj=nil)
205
+ first_obj ||= get_first_obj() {|x|}
206
+
207
+ first = nil
208
+ capture(str) do |this, heads|
209
+ first = first_obj.capture(heads.shift)
210
+ yield(heads) if block_given?
211
+ end
212
+ return [first, self]
213
+ end
214
+
215
+ # This method will non-destructively reset the capture state on this object.
216
+ # The existing headers are maintained when this is called.
217
+ # See also: capture_complete? reset_capture!
218
+ def reset_capture
219
+ @capture_state = nil
220
+ self
221
+ end
222
+
223
+ # This method will destructively reset the capture state on this object.
224
+ # The existing headers array is emptied when this is called.
225
+ # See also: capture_complete?, reset_capture
226
+ def reset_capture!
227
+ @capture_state = nil
228
+ self.data = []
229
+ end
230
+
231
+ # Indicates whether this object is ready to capture fresh data, or is
232
+ # waiting for additional data or a reset from a previous incomplete or
233
+ # otherwise broken capture. See also: reset_capture, reset_capture!
234
+ def capture_complete?
235
+ not @capture_state
236
+ end
237
+ end
238
+
239
+
240
+ # A mixin for HTTP Request headers to add specific request header
241
+ # behaviors and features.
242
+ #
243
+ # To instantiate a new request header, use Headers.request_hdr
244
+ module RequestHeaders
245
+ # This method is used to resolve the parser for the first entity above the
246
+ # HTTP headers. The incarnation for ResponseHeaders returns ResponseStatus
247
+ # See Headers.capture_full_headers for more information.
248
+ def get_first_obj(*args)
249
+ RequestAction.new(*args)
250
+ end
251
+ end
252
+
253
+
254
+ # A mixin for HTTP Response headers to add specific response header
255
+ # behaviors and features.
256
+ #
257
+ # To instantiate a new response header, use Headers.response_hdr
258
+ module ResponseHeaders
259
+
260
+ # This method is used to resolve the parser for the first entity above the
261
+ # HTTP headers. The incarnation for ResponseHeaders returns ResponseStatus
262
+ # See Headers.capture_full_headers for more information.
263
+ def get_first_obj(*args)
264
+ ResponseStatus.new(*args)
265
+ end
266
+ end
267
+
268
+
269
+ # A class for HTTP request actions, i.e. the first
270
+ # header sent in an HTTP request, as in "GET / HTTP/1.1"
271
+ class RequestAction
272
+ include CommonInterface
273
+
274
+ def self.parse(str)
275
+ new().capture(str)
276
+ end
277
+
278
+ attr_accessor :verb, :uri, :version
279
+
280
+ def initialize(*args)
281
+ _common_init(*args)
282
+ @verb ||= "GET"
283
+ @uri ||= URI.parse("/")
284
+ @version ||= "HTTP/1.1"
285
+ end
286
+
287
+ def to_raw
288
+ ary = [ @verb, @uri ]
289
+ ary << @version if @version
290
+ ary.join(" ")
291
+ end
292
+
293
+ # This method parses a request action String into the current instance.
294
+ def capture(str)
295
+ raise "arg 0 must be a string" unless str.is_a?(String)
296
+ unless m=/^([^\s]+)\s+([^\s]+)(?:\s+([^\s]+))?\s*$/.match(str)
297
+ raise "invalid action #{str.inspect}"
298
+ end
299
+ @verb = m[1]
300
+ @uri = URI.parse m[2]
301
+ @version = m[3]
302
+ return self
303
+ end
304
+
305
+ # Returns the URI path as a String if defined
306
+ def path
307
+ @uri.path if @uri
308
+ end
309
+
310
+ # Returns the URI query as a String if it is defined
311
+ def query
312
+ @uri.query if @uri
313
+ end
314
+
315
+ # Returns the URI query parameters as a FormUrlencodedParams object if
316
+ # the query string is defined.
317
+ # XXX note parameters cannot currently be modified in this form.
318
+ def parameters
319
+ FormUrlencodedParams.parse(query) if query
320
+ end
321
+
322
+ attr_reader :base
323
+
324
+ def base=(b)
325
+ raise "base must be a kind of Base object" if not b.is_a? Base
326
+ @base = b
327
+ end
328
+ end
329
+
330
+
331
+ # A class for HTTP response status messages, i.e. the first
332
+ # header returned by a server, as in "HTTP/1.0 200 OK"
333
+ class ResponseStatus
334
+ include CommonInterface
335
+
336
+ def self.parse(str)
337
+ new().capture(str)
338
+ end
339
+
340
+ attr_accessor :version, :code, :text
341
+
342
+ def initialize(*args)
343
+ _common_init(*args)
344
+ @version ||= DEFAULT_HTTP_VERSION
345
+ end
346
+
347
+ def to_raw
348
+ [@version, @code, @text].join(" ")
349
+ end
350
+
351
+ def capture(str)
352
+ raise "arg 0 must be a string" unless str.is_a?(String)
353
+ unless m=/^([^\s]+)\s+(\d+)(?:\s+(.*))?$/.match(str)
354
+ raise "invalid status #{str.inspect}"
355
+ end
356
+ @version = m[1]
357
+ @code = m[2] =~ /^\d+$/ ? m[2].to_i : m[2]
358
+ @text = m[3]
359
+ return self
360
+ end
361
+
362
+ attr_reader :base
363
+
364
+ def base=(b)
365
+ raise "base must be a kind of Base object" if not b.is_a? Base
366
+ @base = b
367
+ end
368
+ end
369
+ end
370
+
@@ -0,0 +1,104 @@
1
+ module Rbkb::Http
2
+
3
+ # The Parameters class is for handling named parameter values. This is a
4
+ # stub base class from which to derive specific parameter parsers such as:
5
+ #
6
+ # FormUrlencodedParams for request query string parameters and POST
7
+ # content using application/www-form-urlencoded format.
8
+ #
9
+ # MultiPartFormParams for POST content using multipart/form-data
10
+ class Parameters < Array
11
+ include CommonInterface
12
+
13
+ def self.parse(str)
14
+ new().capture(str)
15
+ end
16
+
17
+ def initialize(*args)
18
+ _common_init(*args)
19
+ end
20
+
21
+ def get_all(k)
22
+ self.select {|p| p[0] == k}
23
+ end
24
+
25
+ def get_all_values_for(k)
26
+ self.get_all(k).collect {|p,v| v }
27
+ end
28
+ alias all_values_for get_all_values_for
29
+
30
+ def get_param(k)
31
+ self.find {|p| p[0] == k}
32
+ end
33
+
34
+ def get_value_for(k)
35
+ if p=self.get_param(k)
36
+ return p[1]
37
+ end
38
+ end
39
+ alias get_param_value get_value_for
40
+ alias value_for get_value_for
41
+
42
+ def set_param(k, v)
43
+ if p=self.get_param(k)
44
+ p[1]=v
45
+ else
46
+ p << [k,v]
47
+ end
48
+ return [[k,v]]
49
+ end
50
+
51
+ def set_all_for(k, v)
52
+ sel=self.get_all(k)
53
+ if sel.empty?
54
+ self << [k,v]
55
+ return [[k,v]]
56
+ else
57
+ sel.each {|p| p[1] = v}
58
+ return sel
59
+ end
60
+ end
61
+
62
+ def delete_param(k)
63
+ self.delete_if {|p| p[0] == k }
64
+ end
65
+ end
66
+
67
+ # The FormUrlencodedParams class is for Parameters values in the
68
+ # form of 'q=foo&l=1&z=baz' as found in GET query strings and
69
+ # application/www-form-urlencoded or application/x-url-encoded POST
70
+ # contents.
71
+ class FormUrlencodedParams < Parameters
72
+ def to_raw
73
+ self.map {|k,v| "#{k}=#{v}"}.join('&')
74
+ end
75
+
76
+ def capture(str)
77
+ raise "arg 0 must be a string" unless String === str
78
+ str.split('&').each do |p|
79
+ var,val = p.split('=',2)
80
+ self << [var,val]
81
+ end
82
+ return self
83
+ end
84
+ end
85
+
86
+
87
+ # The MultipartFormParams class is for Parameters in POST data when using
88
+ # the multipart/form-data content type. This is often used for file uploads.
89
+ class MultipartFormParams < Parameters
90
+ def to_raw
91
+ self.map {|k,v| "#{k}=#{v}"}.join('&')
92
+ end
93
+
94
+ def capture(str)
95
+ raise "arg 0 must be a string" unless String === str
96
+ str.split('&').each do |p|
97
+ var,val = p.split('=',2)
98
+ self << [var,val]
99
+ end
100
+ return self
101
+ end
102
+ end
103
+ end
104
+