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.
- data/History.txt +74 -0
- data/README.rdoc +149 -0
- data/Rakefile +47 -0
- data/bin/b64 +5 -0
- data/bin/bgrep +5 -0
- data/bin/blit +5 -0
- data/bin/c +5 -0
- data/bin/crc32 +5 -0
- data/bin/d64 +5 -0
- data/bin/dedump +5 -0
- data/bin/feed +5 -0
- data/bin/hexify +5 -0
- data/bin/len +5 -0
- data/bin/plugsrv +271 -0
- data/bin/rex +10 -0
- data/bin/rstrings +5 -0
- data/bin/slice +5 -0
- data/bin/telson +5 -0
- data/bin/unhexify +5 -0
- data/bin/urldec +5 -0
- data/bin/urlenc +5 -0
- data/bin/xor +5 -0
- data/cli_usage.rdoc +285 -0
- data/doctor-bag.jpg +0 -0
- data/lib/rbkb.rb +51 -0
- data/lib/rbkb/cli.rb +219 -0
- data/lib/rbkb/cli/b64.rb +35 -0
- data/lib/rbkb/cli/bgrep.rb +86 -0
- data/lib/rbkb/cli/blit.rb +89 -0
- data/lib/rbkb/cli/chars.rb +24 -0
- data/lib/rbkb/cli/crc32.rb +35 -0
- data/lib/rbkb/cli/d64.rb +28 -0
- data/lib/rbkb/cli/dedump.rb +52 -0
- data/lib/rbkb/cli/feed.rb +229 -0
- data/lib/rbkb/cli/hexify.rb +65 -0
- data/lib/rbkb/cli/len.rb +76 -0
- data/lib/rbkb/cli/rstrings.rb +108 -0
- data/lib/rbkb/cli/slice.rb +47 -0
- data/lib/rbkb/cli/telson.rb +87 -0
- data/lib/rbkb/cli/unhexify.rb +50 -0
- data/lib/rbkb/cli/urldec.rb +35 -0
- data/lib/rbkb/cli/urlenc.rb +35 -0
- data/lib/rbkb/cli/xor.rb +43 -0
- data/lib/rbkb/extends.rb +725 -0
- data/lib/rbkb/http.rb +21 -0
- data/lib/rbkb/http/base.rb +172 -0
- data/lib/rbkb/http/body.rb +214 -0
- data/lib/rbkb/http/common.rb +74 -0
- data/lib/rbkb/http/headers.rb +370 -0
- data/lib/rbkb/http/parameters.rb +104 -0
- data/lib/rbkb/http/request.rb +58 -0
- data/lib/rbkb/http/response.rb +86 -0
- data/lib/rbkb/plug.rb +9 -0
- data/lib/rbkb/plug/blit.rb +222 -0
- data/lib/rbkb/plug/cli.rb +83 -0
- data/lib/rbkb/plug/feed_import.rb +74 -0
- data/lib/rbkb/plug/peer.rb +67 -0
- data/lib/rbkb/plug/plug.rb +215 -0
- data/lib/rbkb/plug/proxy.rb +26 -0
- data/lib/rbkb/plug/unix_domain.rb +75 -0
- data/lib_usage.rdoc +176 -0
- data/rbkb.gemspec +38 -0
- data/spec/rbkb_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/test/test_cli_b64.rb +35 -0
- data/test/test_cli_bgrep.rb +137 -0
- data/test/test_cli_blit.rb +11 -0
- data/test/test_cli_chars.rb +21 -0
- data/test/test_cli_crc32.rb +108 -0
- data/test/test_cli_d64.rb +22 -0
- data/test/test_cli_dedump.rb +118 -0
- data/test/test_cli_feed.rb +11 -0
- data/test/test_cli_helper.rb +96 -0
- data/test/test_cli_hexify.rb +63 -0
- data/test/test_cli_len.rb +96 -0
- data/test/test_cli_rstrings.rb +15 -0
- data/test/test_cli_slice.rb +73 -0
- data/test/test_cli_telson.rb +11 -0
- data/test/test_cli_unhexify.rb +43 -0
- data/test/test_cli_urldec.rb +50 -0
- data/test/test_cli_urlenc.rb +44 -0
- data/test/test_cli_xor.rb +71 -0
- data/test/test_helper.rb +5 -0
- data/test/test_http.rb +27 -0
- data/test/test_http_helper.rb +60 -0
- data/test/test_http_request.rb +136 -0
- data/test/test_http_response.rb +222 -0
- data/test/test_rbkb.rb +19 -0
- 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
|
+
|