emonti-rbkb 0.6.2.1 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/History.txt +32 -0
  2. data/README.rdoc +10 -7
  3. data/Rakefile +47 -0
  4. data/bin/feed +5 -0
  5. data/bin/plugsrv +3 -3
  6. data/cli_usage.rdoc +44 -9
  7. data/doctor-bag.jpg +0 -0
  8. data/lib/rbkb.rb +47 -2
  9. data/lib/rbkb/cli.rb +8 -6
  10. data/lib/rbkb/cli/b64.rb +5 -0
  11. data/lib/rbkb/cli/bgrep.rb +14 -9
  12. data/lib/rbkb/cli/chars.rb +2 -1
  13. data/lib/rbkb/cli/crc32.rb +4 -1
  14. data/lib/rbkb/cli/d64.rb +3 -0
  15. data/lib/rbkb/cli/dedump.rb +5 -3
  16. data/lib/rbkb/cli/feed.rb +223 -0
  17. data/lib/rbkb/cli/hexify.rb +3 -3
  18. data/lib/rbkb/cli/len.rb +12 -9
  19. data/lib/rbkb/cli/rstrings.rb +13 -10
  20. data/lib/rbkb/cli/slice.rb +1 -0
  21. data/lib/rbkb/cli/telson.rb +21 -57
  22. data/lib/rbkb/cli/unhexify.rb +2 -6
  23. data/lib/rbkb/cli/urldec.rb +1 -0
  24. data/lib/rbkb/cli/urlenc.rb +1 -0
  25. data/lib/rbkb/extends.rb +41 -6
  26. data/lib/rbkb/http.rb +20 -0
  27. data/lib/rbkb/http/base.rb +172 -0
  28. data/lib/rbkb/http/body.rb +214 -0
  29. data/lib/rbkb/http/common.rb +74 -0
  30. data/lib/rbkb/http/headers.rb +356 -0
  31. data/lib/rbkb/http/parameters.rb +101 -0
  32. data/lib/rbkb/http/request.rb +58 -0
  33. data/lib/rbkb/http/response.rb +86 -0
  34. data/lib/rbkb/plug.rb +3 -3
  35. data/lib/rbkb/plug/cli.rb +83 -0
  36. data/lib/rbkb/plug/feed_import.rb +74 -0
  37. data/lib/rbkb/plug/plug.rb +36 -19
  38. data/lib/rbkb/plug/unix_domain.rb +75 -0
  39. data/rbkb.gemspec +38 -0
  40. data/spec/rbkb_spec.rb +7 -0
  41. data/spec/spec_helper.rb +16 -0
  42. data/tasks/ann.rake +80 -0
  43. data/tasks/bones.rake +20 -0
  44. data/tasks/gem.rake +201 -0
  45. data/tasks/git.rake +40 -0
  46. data/tasks/notes.rake +27 -0
  47. data/tasks/post_load.rake +34 -0
  48. data/tasks/rdoc.rake +51 -0
  49. data/tasks/rubyforge.rake +55 -0
  50. data/tasks/setup.rb +292 -0
  51. data/tasks/spec.rake +54 -0
  52. data/tasks/svn.rake +47 -0
  53. data/tasks/test.rake +40 -0
  54. data/test/test_cli_b64.rb +35 -0
  55. data/test/test_cli_bgrep.rb +137 -0
  56. data/test/test_cli_blit.rb +11 -0
  57. data/test/test_cli_chars.rb +21 -0
  58. data/test/test_cli_crc32.rb +108 -0
  59. data/test/test_cli_d64.rb +22 -0
  60. data/test/test_cli_dedump.rb +118 -0
  61. data/test/test_cli_feed.rb +11 -0
  62. data/test/test_cli_helper.rb +96 -0
  63. data/test/test_cli_hexify.rb +63 -0
  64. data/test/test_cli_len.rb +96 -0
  65. data/test/test_cli_rstrings.rb +15 -0
  66. data/test/test_cli_slice.rb +73 -0
  67. data/test/test_cli_telson.rb +11 -0
  68. data/test/test_cli_unhexify.rb +43 -0
  69. data/test/test_cli_urldec.rb +50 -0
  70. data/test/test_cli_urlenc.rb +44 -0
  71. data/test/test_cli_xor.rb +71 -0
  72. data/test/test_helper.rb +5 -0
  73. data/test/test_http.rb +27 -0
  74. data/test/test_http_helper.rb +60 -0
  75. data/test/test_http_request.rb +136 -0
  76. data/test/test_http_response.rb +222 -0
  77. data/test/test_rbkb.rb +19 -0
  78. metadata +127 -21
@@ -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
@@ -0,0 +1,356 @@
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_header(k)
124
+ self.select {|h| h[0].downcase == k.downcase }
125
+ end
126
+
127
+ def get_header_value(k)
128
+ get_header(k).map {|h| h[1]}
129
+ end
130
+
131
+ def delete_header(k)
132
+ self.delete_if {|h| h[0].downcase == k.downcase }
133
+ end
134
+
135
+ def set_header(k,v)
136
+ sel = get_header(k)
137
+
138
+ if sel.empty?
139
+ self << [k,v]
140
+ return [[k,v]]
141
+ else
142
+ sel.each {|h| h[1] = v }
143
+ return sel
144
+ end
145
+ end
146
+
147
+ # The to_raw method returns a raw string of headers as they appear
148
+ # on the wire.
149
+ def to_raw
150
+ to_raw_array.join("\r\n") << "\r\n"
151
+ end
152
+
153
+ # Captures a raw string of headers into this instance's internal array.
154
+ # Note: This method expects not to include the first element such as a
155
+ # RequestAction or ResponseStatus. See capture_full_headers for a version
156
+ # that can handle this.
157
+ def capture(str)
158
+
159
+ raise "arg 0 must be a string" unless str.is_a?(String)
160
+ heads = str.split(/\s*\r?\n/)
161
+
162
+ # pass interim parsed headers to a block if given
163
+ yield(self, heads) if block_given?
164
+
165
+ self.replace [] if capture_complete?
166
+ heads.each do |s|
167
+ k,v = s.split(/\s*:\s*/, 2)
168
+ self << [k,v]
169
+ end
170
+ return self
171
+ end
172
+
173
+ # See capture_full_headers. This method is used to resolve the parser
174
+ # for the first entity above the HTTP headers. This instance is designed
175
+ # to raise an exception when capturing.
176
+ def get_first_obj; raise "get_first_obj called on base stub"; end
177
+
178
+ # This method parses a full set of raw headers from the 'str' argument.
179
+ # Unlike the regular capture method, the string is expected to start
180
+ # with a line which will be parsed by first_obj using its own capture
181
+ # method. For example, first_obj would parse something like
182
+ # "GET / HTTP/1.1" for RequestAction or "HTTP/1.1 200 OK" for
183
+ # ResponseStatus. If first_obj is not defined, there will be an attempt
184
+ # to resolve it by calling get_first_obj which should return the
185
+ # appropriate type of object or raise an exception.
186
+ #
187
+ # Returns a 2 element array containing [first_entity, headers]
188
+ # where first entity is the instantiated first_obj object and headers
189
+ # is self.
190
+ def capture_full_headers(str, first_obj=nil)
191
+ first_obj ||= get_first_obj() {|x|}
192
+
193
+ first = nil
194
+ capture(str) do |this, heads|
195
+ first = first_obj.capture(heads.shift)
196
+ yield(heads) if block_given?
197
+ end
198
+ return [first, self]
199
+ end
200
+
201
+ # This method will non-destructively reset the capture state on this object.
202
+ # The existing headers are maintained when this is called.
203
+ # See also: capture_complete? reset_capture!
204
+ def reset_capture
205
+ @capture_state = nil
206
+ self
207
+ end
208
+
209
+ # This method will destructively reset the capture state on this object.
210
+ # The existing headers array is emptied when this is called.
211
+ # See also: capture_complete?, reset_capture
212
+ def reset_capture!
213
+ @capture_state = nil
214
+ self.data = []
215
+ end
216
+
217
+ # Indicates whether this object is ready to capture fresh data, or is
218
+ # waiting for additional data or a reset from a previous incomplete or
219
+ # otherwise broken capture. See also: reset_capture, reset_capture!
220
+ def capture_complete?
221
+ not @capture_state
222
+ end
223
+ end
224
+
225
+
226
+ # A mixin for HTTP Request headers to add specific request header
227
+ # behaviors and features.
228
+ #
229
+ # To instantiate a new request header, use Headers.request_hdr
230
+ module RequestHeaders
231
+ # This method is used to resolve the parser for the first entity above the
232
+ # HTTP headers. The incarnation for ResponseHeaders returns ResponseStatus
233
+ # See Headers.capture_full_headers for more information.
234
+ def get_first_obj(*args)
235
+ RequestAction.new(*args)
236
+ end
237
+ end
238
+
239
+
240
+ # A mixin for HTTP Response headers to add specific response header
241
+ # behaviors and features.
242
+ #
243
+ # To instantiate a new response header, use Headers.response_hdr
244
+ module ResponseHeaders
245
+
246
+ # This method is used to resolve the parser for the first entity above the
247
+ # HTTP headers. The incarnation for ResponseHeaders returns ResponseStatus
248
+ # See Headers.capture_full_headers for more information.
249
+ def get_first_obj(*args)
250
+ ResponseStatus.new(*args)
251
+ end
252
+ end
253
+
254
+
255
+ # A class for HTTP request actions, i.e. the first
256
+ # header sent in an HTTP request, as in "GET / HTTP/1.1"
257
+ class RequestAction
258
+ include CommonInterface
259
+
260
+ def self.parse(str)
261
+ new().capture(str)
262
+ end
263
+
264
+ attr_accessor :verb, :uri, :version
265
+
266
+ def initialize(*args)
267
+ _common_init(*args)
268
+ @verb ||= "GET"
269
+ @uri ||= URI.parse("/")
270
+ @version ||= "HTTP/1.1"
271
+ end
272
+
273
+ def to_raw
274
+ ary = [ @verb, @uri ]
275
+ ary << @version if @version
276
+ ary.join(" ")
277
+ end
278
+
279
+ # This method parses a request action String into the current instance.
280
+ def capture(str)
281
+ raise "arg 0 must be a string" unless str.is_a?(String)
282
+ unless m=/^([^\s]+)\s+([^\s]+)(?:\s+([^\s]+))?\s*$/.match(str)
283
+ raise "invalid action #{str.inspect}"
284
+ end
285
+ @verb = m[1]
286
+ @uri = URI.parse m[2]
287
+ @version = m[3]
288
+ return self
289
+ end
290
+
291
+ # Returns the URI path as a String if defined
292
+ def path
293
+ @uri.path if @uri
294
+ end
295
+
296
+ # Returns the URI query as a String if it is defined
297
+ def query
298
+ @uri.query if @uri
299
+ end
300
+
301
+ # Returns the URI query parameters as a FormUrlencodedParams object if
302
+ # the query string is defined.
303
+ # XXX note parameters cannot currently be modified in this form.
304
+ def parameters
305
+ FormUrlencodedParams.parse(query) if query
306
+ end
307
+
308
+ attr_reader :base
309
+
310
+ def base=(b)
311
+ raise "base must be a kind of Base object" if not b.is_a? Base
312
+ @base = b
313
+ end
314
+ end
315
+
316
+
317
+ # A class for HTTP response status messages, i.e. the first
318
+ # header returned by a server, as in "HTTP/1.0 200 OK"
319
+ class ResponseStatus
320
+ include CommonInterface
321
+
322
+ def self.parse(str)
323
+ new().capture(str)
324
+ end
325
+
326
+ attr_accessor :version, :code, :text
327
+
328
+ def initialize(*args)
329
+ _common_init(*args)
330
+ @version ||= DEFAULT_HTTP_VERSION
331
+ end
332
+
333
+ def to_raw
334
+ [@version, @code, @text].join(" ")
335
+ end
336
+
337
+ def capture(str)
338
+ raise "arg 0 must be a string" unless str.is_a?(String)
339
+ unless m=/^([^\s]+)\s+(\d+)(?:\s+(.*))?$/.match(str)
340
+ raise "invalid status #{str.inspect}"
341
+ end
342
+ @version = m[1]
343
+ @code = m[2] =~ /^\d+$/ ? m[2].to_i : m[2]
344
+ @text = m[3]
345
+ return self
346
+ end
347
+
348
+ attr_reader :base
349
+
350
+ def base=(b)
351
+ raise "base must be a kind of Base object" if not b.is_a? Base
352
+ @base = b
353
+ end
354
+ end
355
+ end
356
+
@@ -0,0 +1,101 @@
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_param(k)
26
+ self.find {|p| p[0] == k}
27
+ end
28
+
29
+ def get_value_for(k)
30
+ if v=self.get(k)
31
+ return v[1]
32
+ end
33
+ end
34
+
35
+ def get_all_values_for(k)
36
+ self.get_all(k).map {|p,v| v }
37
+ end
38
+
39
+ def set_param(k, v)
40
+ if p=self.get_param(k)
41
+ p[1]=v
42
+ else
43
+ p <<
44
+ end
45
+ return v
46
+ end
47
+
48
+ def set_all_for(k, v)
49
+ sel=self.get_all(k)
50
+ if sel.empty?
51
+ self << [k,v]
52
+ return [[k,v]]
53
+ else
54
+ sel.each {|p| p[1] = v}
55
+ return sel
56
+ end
57
+ end
58
+
59
+ def delete_param(k)
60
+ self.delete_if {|p| p[0] == k }
61
+ end
62
+ end
63
+
64
+ # The FormUrlencodedParams class is for Parameters values in the
65
+ # form of 'q=foo&l=1&z=baz' as found in GET query strings and
66
+ # application/www-form-urlencoded or application/x-url-encoded POST
67
+ # contents.
68
+ class FormUrlencodedParams < Parameters
69
+ def to_raw
70
+ self.map {|k,v| "#{k}=#{v}"}.join('&')
71
+ end
72
+
73
+ def capture(str)
74
+ raise "arg 0 must be a string" unless String === str
75
+ str.split('&').each do |p|
76
+ var,val = p.split('=',2)
77
+ self << [var,val]
78
+ end
79
+ return self
80
+ end
81
+ end
82
+
83
+
84
+ # The MultipartFormParams class is for Parameters in POST data when using
85
+ # the multipart/form-data content type. This is often used for file uploads.
86
+ class MultipartFormParams < Parameters
87
+ def to_raw
88
+ self.map {|k,v| "#{k}=#{v}"}.join('&')
89
+ end
90
+
91
+ def capture(str)
92
+ raise "arg 0 must be a string" unless String === str
93
+ str.split('&').each do |p|
94
+ var,val = p.split('=',2)
95
+ self << [var,val]
96
+ end
97
+ return self
98
+ end
99
+ end
100
+ end
101
+