raptor-io 0.0.1

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 (91) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +30 -0
  3. data/README.md +51 -0
  4. data/lib/rack/handler/raptor-io.rb +130 -0
  5. data/lib/raptor-io.rb +11 -0
  6. data/lib/raptor-io/error.rb +19 -0
  7. data/lib/raptor-io/protocol.rb +6 -0
  8. data/lib/raptor-io/protocol/error.rb +10 -0
  9. data/lib/raptor-io/protocol/http.rb +34 -0
  10. data/lib/raptor-io/protocol/http/client.rb +685 -0
  11. data/lib/raptor-io/protocol/http/error.rb +16 -0
  12. data/lib/raptor-io/protocol/http/headers.rb +132 -0
  13. data/lib/raptor-io/protocol/http/message.rb +67 -0
  14. data/lib/raptor-io/protocol/http/request.rb +307 -0
  15. data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
  16. data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
  17. data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
  18. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
  19. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
  20. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
  21. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
  22. data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
  23. data/lib/raptor-io/protocol/http/response.rb +166 -0
  24. data/lib/raptor-io/protocol/http/server.rb +446 -0
  25. data/lib/raptor-io/ruby.rb +4 -0
  26. data/lib/raptor-io/ruby/hash.rb +24 -0
  27. data/lib/raptor-io/ruby/ipaddr.rb +15 -0
  28. data/lib/raptor-io/ruby/openssl.rb +23 -0
  29. data/lib/raptor-io/ruby/string.rb +27 -0
  30. data/lib/raptor-io/socket.rb +175 -0
  31. data/lib/raptor-io/socket/comm.rb +143 -0
  32. data/lib/raptor-io/socket/comm/local.rb +94 -0
  33. data/lib/raptor-io/socket/comm/sapni.rb +75 -0
  34. data/lib/raptor-io/socket/comm/socks.rb +237 -0
  35. data/lib/raptor-io/socket/comm_chain.rb +30 -0
  36. data/lib/raptor-io/socket/error.rb +45 -0
  37. data/lib/raptor-io/socket/switch_board.rb +183 -0
  38. data/lib/raptor-io/socket/switch_board/route.rb +42 -0
  39. data/lib/raptor-io/socket/tcp.rb +231 -0
  40. data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
  41. data/lib/raptor-io/socket/tcp_server.rb +16 -0
  42. data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
  43. data/lib/raptor-io/socket/udp.rb +0 -0
  44. data/lib/raptor-io/version.rb +6 -0
  45. data/lib/tasks/yard.rake +26 -0
  46. data/spec/rack/handler/raptor_spec.rb +140 -0
  47. data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
  48. data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
  49. data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
  50. data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
  51. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
  52. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
  53. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
  54. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
  55. data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
  56. data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
  57. data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
  58. data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
  59. data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
  60. data/spec/raptor-io/ruby/hash_spec.rb +20 -0
  61. data/spec/raptor-io/ruby/string_spec.rb +20 -0
  62. data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
  63. data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
  64. data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
  65. data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
  66. data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
  67. data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
  68. data/spec/raptor-io/socket/tcp_spec.rb +14 -0
  69. data/spec/raptor-io/socket_spec.rb +16 -0
  70. data/spec/raptor-io/version_spec.rb +10 -0
  71. data/spec/spec_helper.rb +56 -0
  72. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
  73. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
  74. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
  75. data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
  76. data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
  77. data/spec/support/lib/path_helpers.rb +11 -0
  78. data/spec/support/lib/webserver_option_parser.rb +26 -0
  79. data/spec/support/lib/webservers.rb +120 -0
  80. data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
  81. data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
  82. data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
  83. data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
  84. data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
  85. data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
  86. data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
  87. data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
  88. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
  89. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
  90. data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
  91. metadata +336 -0
@@ -0,0 +1,16 @@
1
+ module RaptorIO
2
+
3
+ module Protocol::HTTP
4
+
5
+ #
6
+ # {HTTP} error namespace.
7
+ #
8
+ # All {HTTP} errors inherit from and live under it.
9
+ #
10
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
11
+ #
12
+ class Error < Protocol::Error
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,132 @@
1
+ require 'webrick'
2
+ require 'uri'
3
+
4
+ module RaptorIO
5
+ module Protocol::HTTP
6
+
7
+ #
8
+ # HTTP Headers, holds shared attributes of {Request} and {Response}.
9
+ #
10
+ # For convenience, Hash-like getters and setters provide case-insensitive access.
11
+ #
12
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
13
+ #
14
+ class Headers < Hash
15
+
16
+ # @param [Headers, Hash] headers
17
+ def initialize( headers = {} )
18
+ (headers || {}).each do |k, v|
19
+ self[k] = v
20
+ end
21
+ end
22
+
23
+ # @note `field` will be capitalized appropriately before storing.
24
+ # @param [String] field Field name
25
+ # @return [String] Field value.
26
+ def delete( field )
27
+ super format_field_name( field.to_s.downcase )
28
+ end
29
+
30
+ # @note `field` will be capitalized appropriately before storing.
31
+ # @param [String] field Field name
32
+ # @return [String] Field value.
33
+ def include?( field )
34
+ super format_field_name( field.to_s.downcase )
35
+ end
36
+
37
+ # @note `field` will be capitalized appropriately before storing.
38
+ # @param [String] field Field name
39
+ # @return [String] Field value.
40
+ def []( field )
41
+ super format_field_name( field.to_s.downcase )
42
+ end
43
+
44
+ # @note `field` will be capitalized appropriately before storing.
45
+ # @param [String] field Field name
46
+ # @param [Array<String>, String] value Field value.
47
+ # @return [String] Field `value`.
48
+ def []=( field, value )
49
+ super format_field_name( field.to_s.downcase ),
50
+ value.is_a?( Array ) ? value : value.to_s
51
+ end
52
+
53
+ # @return [Array<String>] Set-cookie strings.
54
+ def set_cookie
55
+ return [] if self['set-cookie'].to_s.empty?
56
+ [self['set-cookie']].flatten
57
+ end
58
+
59
+ # @return [Array<Hash>] Cookies as hashes.
60
+ def parsed_set_cookie
61
+ return [] if set_cookie.empty?
62
+
63
+ set_cookie.map { |set_cookie_string|
64
+ WEBrick::Cookie.parse_set_cookies( set_cookie_string ).flatten.uniq.map do |cookie|
65
+ cookie_hash = {}
66
+ cookie.instance_variables.each do |var|
67
+ cookie_hash[var.to_s.gsub( /@/, '' ).to_sym] = cookie.instance_variable_get( var )
68
+ end
69
+
70
+ # Replace the string with a Time object.
71
+ cookie_hash[:expires] = cookie.expires
72
+
73
+ cookie_hash
74
+ end
75
+ }.flatten.compact
76
+ end
77
+
78
+ # @return [Array<Hash>] Request cookies.
79
+ def cookies
80
+ return [] if !self['cookie']
81
+
82
+ WEBrick::Cookie.parse( self['cookie'] ).flatten.uniq.map do |cookie|
83
+ cookie_hash = {}
84
+ cookie.instance_variables.each do |var|
85
+ cookie_hash[var.to_s.gsub( /@/, '' ).to_sym] = cookie.instance_variable_get( var )
86
+ end
87
+
88
+ # Replace the string with a Time object.
89
+ cookie_hash[:expires] = cookie.expires
90
+
91
+ cookie_hash
92
+ end
93
+ end
94
+
95
+ # @return [String] HTTP headers formatted for transmission.
96
+ def to_s
97
+ map { |k, v|
98
+ if v.is_a? Array
99
+ v.map do |cv|
100
+ "#{k}: #{cv}"
101
+ end
102
+ else
103
+ "#{k}: #{v}"
104
+ end
105
+ }.flatten.join( CRLF )
106
+ end
107
+
108
+ # @param [String] headers_string
109
+ # @return [Headers]
110
+ def self.parse( headers_string )
111
+ return Headers.new if headers_string.to_s.empty?
112
+
113
+ headers = Hash.new { |h, k| h[k] = [] }
114
+ headers_string.split( CRLF_PATTERN ).each do |header|
115
+ k, v = header.split( ':', 2 )
116
+ headers[k.to_s.strip] << v.to_s.strip
117
+ end
118
+
119
+ headers.each { |k, v| headers[k] = v.first if v.size == 1 }
120
+ new headers
121
+ end
122
+
123
+ private
124
+
125
+ def format_field_name( field )
126
+ field.to_s.split( '-' ).map( &:capitalize ).join( '-' )
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,67 @@
1
+ module RaptorIO
2
+ module Protocol::HTTP
3
+
4
+ #
5
+ # HTTP message, holds shared attributes of {Request} and {Response}.
6
+ #
7
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
8
+ #
9
+ class Message
10
+
11
+ # @return [String] HTTP version.
12
+ attr_reader :version
13
+
14
+ # @return [Headers<String, String>] HTTP headers as a Hash-like object.
15
+ attr_reader :headers
16
+
17
+ # @return [String] {Request}/{Response} body.
18
+ attr_accessor :body
19
+
20
+ #
21
+ # @note All options will be sent through the class setters whenever
22
+ # possible to allow for normalization.
23
+ #
24
+ # @param [Hash] options Message options.
25
+ # @option options [String] :url The URL of the remote resource.
26
+ # @option options [Hash] :headers HTTP headers.
27
+ # @option options [String] :body Body.
28
+ # @option options [String] :version (1.1) HTTP version.
29
+ #
30
+ def initialize( options = {} )
31
+ options.each do |k, v|
32
+ begin
33
+ send( "#{k}=", v )
34
+ rescue NoMethodError
35
+ instance_variable_set( "@#{k}".to_sym, v )
36
+ end
37
+ end
38
+
39
+ @headers = Headers.new( @headers )
40
+ @version ||= '1.1'
41
+ end
42
+
43
+ # @return [Bool]
44
+ # `true` if the connections should be reused, `false` otherwise.
45
+ def keep_alive?
46
+ connection = headers['Connection'].to_s.downcase
47
+
48
+ return connection == 'keep-alive' if version.to_f < 1.1
49
+ connection != 'close'
50
+ end
51
+
52
+ # @return [Boolean]
53
+ # `true` when {#version} is `1.1`, `false` otherwise.
54
+ def http_1_1?
55
+ version == '1.1'
56
+ end
57
+
58
+ # @return [Boolean]
59
+ # `true` when {#version} is `1.0`, `false` otherwise.
60
+ def http_1_0?
61
+ version == '1.0'
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,307 @@
1
+ module RaptorIO
2
+ module Protocol::HTTP
3
+
4
+ #
5
+ # HTTP Request.
6
+ #
7
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
8
+ #
9
+ class Request < Message
10
+
11
+ #
12
+ # {HTTP::Request} error namespace.
13
+ #
14
+ # All {HTTP::Request} errors inherit from and live under it.
15
+ #
16
+ # @author Tasos "Zapotek" Laskos
17
+ #
18
+ class Error < Protocol::HTTP::Error
19
+ end
20
+
21
+ require_relative 'request/manipulators'
22
+
23
+ # Acceptable response callback types.
24
+ CALLBACK_TYPES = [:on_complete, :on_failure, :on_success]
25
+
26
+ # @return [Symbol] HTTP method.
27
+ attr_reader :http_method
28
+
29
+ # @return [String] URL of the targeted resource.
30
+ attr_reader :url
31
+
32
+ # @return [URI] Parsed version of {#url}.
33
+ attr_reader :parsed_url
34
+
35
+ # @return [Hash] Request parameters.
36
+ attr_reader :parameters
37
+
38
+ # @return [Integer, Float] Timeout in seconds.
39
+ attr_accessor :timeout
40
+
41
+ # @note Defaults to `true`.
42
+ # @return [Bool]
43
+ # Whether or not to automatically continue on responses with status 100.
44
+ attr_reader :continue
45
+
46
+ # @note Defaults to `false`.
47
+ # @return [Bool]
48
+ # Whether or not encode any of the given data for HTTP transmission.
49
+ attr_accessor :raw
50
+
51
+ attr_accessor :callbacks
52
+
53
+ # @private
54
+ attr_accessor :root_redirect_id
55
+
56
+ # @return [String] IP address -- populated by {Server}.
57
+ attr_accessor :client_address
58
+
59
+ #
60
+ # @note This class' options are in addition to {Message#initialize}.
61
+ #
62
+ # @param [Hash] options Request options.
63
+ # @option options [String] :version ('1.1') HTTP version to use.
64
+ # @option options [Symbol, String] :http_method (:get) HTTP method to use.
65
+ # @option options [Hash] :parameters ({})
66
+ # Parameters to send. If performing a GET request and the URL has parameters
67
+ # of its own they will be merged and overwritten.
68
+ # @option options [Integer] :timeout
69
+ # Max time to wait for a response in seconds.
70
+ # @option options [Bool] :continue
71
+ # Whether or not to automatically continue on responses with status 100.
72
+ # Only applicable when the 'Expect' header has been set to '100-continue'.
73
+ # @option options [Bool] :raw (false)
74
+ # `true` to not encode any of the given data for HTTP transmission, `false`
75
+ # otherwise.
76
+ #
77
+ # @see Message#initialize
78
+ # @see #parameters=
79
+ # @see #http_method=
80
+ #
81
+ def initialize( options = {} )
82
+ super( options )
83
+
84
+ clear_callbacks
85
+
86
+ fail ArgumentError, "Missing ':url' option." if !@url
87
+
88
+ @parameters ||= {}
89
+ @http_method ||= :get
90
+ @continue = true if @continue.nil?
91
+ @raw = false if @raw.nil?
92
+ end
93
+
94
+ # Clears all callbacks.
95
+ def clear_callbacks
96
+ @callbacks = CALLBACK_TYPES.inject( {} ) { |h, type| h[type] = []; h }
97
+ nil
98
+ end
99
+
100
+ # @return [Bool]
101
+ # Whether or not encode any of the given data for HTTP transmission.
102
+ def raw?
103
+ !!@raw
104
+ end
105
+
106
+ # @return [Bool]
107
+ # Whether or not to automatically continue on responses with status 100.
108
+ def continue?
109
+ !!@continue
110
+ end
111
+
112
+ # @param [String] uri Request URL.
113
+ # @return [String] `uri`
114
+ def url=( uri )
115
+ @url = uri
116
+ @parsed_url= URI(@url)
117
+ @url
118
+ end
119
+
120
+ # @return [Integer] Identification for the remote host:port.
121
+ def connection_id
122
+ "#{parsed_url.host}:#{parsed_url.port}".hash
123
+ end
124
+
125
+ #
126
+ # @note All keys and values will be recursively converted to strings.
127
+ #
128
+ # Sets request parameters.
129
+ #
130
+ # @param [Hash] params
131
+ # Parameters to assign to this request.
132
+ # If performing a GET request and the URL has parameters of its own they
133
+ # will be merged and overwritten.
134
+ #
135
+ # @return [Hash] Normalized parameters.
136
+ #
137
+ def parameters=( params )
138
+ @parameters = params.stringify
139
+ end
140
+
141
+ # @return [Hash] Parameters to be used for the query part of the resource.
142
+ def query_parameters
143
+ query = parsed_url.query
144
+ if !query
145
+ return http_method == :get ? parameters : {}
146
+ end
147
+
148
+ qparams = query.split('&').inject({}) do |h, pair|
149
+ k, v = pair.split('=', 2)
150
+ h.merge( decode_if_not_raw(k) => decode_if_not_raw(v) )
151
+ end
152
+ return qparams if http_method != :get
153
+
154
+ qparams.merge( parameters )
155
+ end
156
+
157
+ # @return [URI] Location of the resource to request.
158
+ def effective_url
159
+ cparsed_url = parsed_url.dup
160
+ cparsed_url.query = query_parameters.map do |k, v|
161
+ "#{encode_if_not_raw(k)}=#{encode_if_not_raw(v)}"
162
+ end.join('&') if query_parameters.any?
163
+
164
+ cparsed_url.normalize
165
+ end
166
+
167
+ # @return [String] Response body to use.
168
+ def effective_body
169
+ return '' if headers['Expect'] == '100-continue'
170
+ return encode_if_not_raw(body.to_s)
171
+
172
+ body_params = if !body.to_s.empty?
173
+ body.split('&').inject({}) do |h, pair|
174
+ k, v = pair.split('=', 2)
175
+ h.merge( decode_if_not_raw(k) => decode_if_not_raw(v) )
176
+ end
177
+ else
178
+ {}
179
+ end
180
+
181
+ return '' if body_params.empty? && parameters.empty?
182
+
183
+ body_params.merge( parameters ).map do |k, v|
184
+ "#{encode_if_not_raw(k)}=#{encode_if_not_raw(v)}"
185
+ end.join('&')
186
+ end
187
+
188
+ #
189
+ # @note Method will be normalized to a lower-case symbol.
190
+ #
191
+ # Sets the request HTTP method.
192
+ #
193
+ # @param [#to_s] http_verb HTTP method.
194
+ #
195
+ # @return [Symbol] HTTP method.
196
+ #
197
+ def http_method=( http_verb )
198
+ @http_method = http_verb.to_s.downcase.to_sym
199
+ end
200
+
201
+ # @return [Bool] `true` if the request if idempotent, `false` otherwise.
202
+ def idempotent?
203
+ http_method != :post
204
+ end
205
+
206
+ # @return [String] Server-side resource to request.
207
+ def resource
208
+ req_resource = "#{effective_url.path}"
209
+ req_resource << "?#{effective_url.query}" if effective_url.query
210
+ req_resource
211
+ end
212
+
213
+ # @return [String]
214
+ # String representation of the request, ready for HTTP transmission.
215
+ def to_s
216
+ final_body = effective_body
217
+
218
+ computed_headers = Headers.new( 'Host' => "#{effective_url.host}:#{effective_url.port}" )
219
+ computed_headers['Content-Length'] = final_body.size.to_s if !final_body.to_s.empty?
220
+
221
+ request = "#{http_method.to_s.upcase} #{resource} HTTP/#{version}#{CRLF}"
222
+ request << computed_headers.merge(headers).to_s
223
+ request << HEADER_SEPARATOR
224
+
225
+ return request if final_body.to_s.empty?
226
+
227
+ request << final_body.to_s
228
+ end
229
+
230
+ CALLBACK_TYPES.each do |type|
231
+ define_method type, ->( &block ) do
232
+ return @callbacks[type] if !block
233
+ @callbacks[type] << block
234
+ self
235
+ end
236
+
237
+ define_method "#{type}=" do |callbacks|
238
+ @callbacks[type] = [callbacks].flatten.compact
239
+ self
240
+ end
241
+ end
242
+
243
+ # @!method on_complete( &block )
244
+ # Assigns a block to be called with the response.
245
+ # @param [Block] block Block to be passed the response.
246
+
247
+ # @!method on_success( &block )
248
+ # Assigns a block to be called with the response if the request was successful.
249
+ # @param [Block] block Block to be passed the response.
250
+
251
+ # @!method on_failure( &block )
252
+ # Assigns a block to be called if the request fails.
253
+ # @param [Block] block Block to call on failure.
254
+
255
+ #
256
+ # Handles the `response` to `self` by passing to the appropriate callbacks.
257
+ #
258
+ # @param [Response] response
259
+ #
260
+ # @private
261
+ def handle_response( response )
262
+ response.request = self
263
+
264
+ type = (response.code.to_i == 0) ? :on_failure : :on_success
265
+
266
+ @callbacks[type].each { |block| block.call response }
267
+ @callbacks[:on_complete].each { |block| block.call response }
268
+ true
269
+ end
270
+
271
+ # @return [Request] Duplicate of `self`.
272
+ def dup
273
+ r = self.class.new( url: url )
274
+ instance_variables.each do |iv|
275
+ r.instance_variable_set iv, instance_variable_get( iv )
276
+ end
277
+ r
278
+ end
279
+
280
+ # @param [String] request HTTP request message to parse.
281
+ # @return [Request]
282
+ def self.parse( request )
283
+ data = {}
284
+ first_line, headers_and_body = request.split( CRLF_PATTERN, 2 )
285
+ data[:http_method], data[:url], data[:version] = first_line.scan( /([A-Z]+)\s+(.*)\s+HTTP\/([0-9\.]+)/ ).flatten
286
+ headers, data[:body] = headers_and_body.split( HEADER_SEPARATOR_PATTERN, 2 )
287
+
288
+ # Use Host to fill in the parsed_uri stuff.
289
+ data[:headers] = Headers.parse( headers.to_s )
290
+
291
+ new data
292
+ end
293
+
294
+ private
295
+
296
+ def encode_if_not_raw( str )
297
+ raw? ? str : CGI.escape( str )
298
+ end
299
+
300
+ def decode_if_not_raw( str )
301
+ raw? ? str : CGI.unescape( str )
302
+ end
303
+
304
+ end
305
+
306
+ end
307
+ end