rat-hole 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,35 @@
1
+ = 0.1.9
2
+
3
+ * test tidy
4
+
5
+ = 0.1.8
6
+
7
+ * bug fixes
8
+ * use jeweler instead of hoe
9
+ * add test harness
10
+
11
+ = 0.1.7
12
+
13
+ * add support for tidy
14
+ * update api to not require request/response to be returned
15
+ * require rack 0.9.1 or greater
16
+
17
+ = 0.1.6
18
+
19
+ * properly calculate request_uri if query_string is empty
20
+
21
+ = 0.1.5
22
+
23
+ * calculate request_uri = path_info + query_string
24
+
25
+ = 0.1.4
26
+
27
+ * update process_server_response hook to include rack_request
28
+
29
+ = 0.1.2
30
+
31
+ * use patched-hoe from newgem
32
+
33
+ = 0.1.0
34
+
35
+ * First release.
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ CHANGELOG.rdoc
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/rat_hole.rb
6
+ lib/rat_hole_test.rb
7
+ lib/util.rb
8
+ rat-hole.gemspec
9
+ test/mock_request.rb
10
+ test/test_rat_hole.rb
11
+ VERSION.yml
data/README.rdoc ADDED
@@ -0,0 +1,107 @@
1
+ Rat Hole is a handy library for creating a rack compliant http proxy that allows you to modify the request from the user and the response from the server.
2
+ The name is inspired by why's mousehole[http://code.whytheluckystiff.net/mouseHole/]
3
+
4
+ == Why
5
+ Use Rat Hole to proxy site A into the namespace of site B.
6
+
7
+ Along the way you can modify the request from the user (example: proxy to an ip and set the Host header to support virtual hosts without DNS).
8
+
9
+ You can also modify the response from the server to cleanup html tweak headers etc.
10
+
11
+ == Usage
12
+ require 'rat_hole'
13
+ require 'hpricot'
14
+
15
+ class PoliticalAgendaRatHole < RatHole
16
+ def process_user_request(rack_request)
17
+ # optionally munge the request before passing it to the old server
18
+ end
19
+
20
+ def process_server_response(rack_response, rack_request)
21
+ # For any html pages proxied replace all links with http://ronpaul.com and
22
+ # add a Ron-Paul header.
23
+
24
+ if(rack_response.content_type == 'text/html')
25
+
26
+ # dump the body into hpricot so we can use hpricot's search/replace goodness
27
+ doc = Hpricot(rack_response.body.first)
28
+
29
+ # update all links to help spread our political views
30
+ (doc/"a").set('href', 'http://ronpaul.com')
31
+
32
+ # update the original string with our modified html
33
+ rack_response.body.first.replace(doc.to_html)
34
+
35
+ rack_response.headers['Ron-Paul'] = 'wish I could have voted for this guy'
36
+ end
37
+ end
38
+ end
39
+
40
+ app = Rack::Builder.new do
41
+ use Rack::CommonLogger # display apache style common logs
42
+ use Rack::ShowExceptions # show exceptions
43
+ use Rack::Reloader # reload app when an included file changes
44
+ run PoliticalAgendaRatHole.new('www.google.com')
45
+ end
46
+
47
+ Rack::Handler::Mongrel.run(app, {:Host => 'localhost', :Port => 5001})
48
+
49
+ === Testing your RatHole into existance
50
+ class PoliticalAgendaRatHoleTest < RatHoleTest
51
+ def test_has_proper_response
52
+ through_the(PoliticalAgendaRatHole, 'terralien.com') do |raw_response, app_response|
53
+ assert_equal raw_response.status.to_i, app_response.status.to_i
54
+ assert !raw_response.headers.has_key?('Ron-Paul')
55
+ assert app_response.headers.has_key?('Ron-Paul')
56
+
57
+ assert !raw_response.body.to_s.include?('http://ronpaul.com')
58
+ assert app_response.body.to_s.include?('http://ronpaul.com')
59
+ end
60
+ end
61
+ end
62
+
63
+ == INSTALL:
64
+ * sudo gem install mikehale-rat-hole
65
+
66
+ == How it Works
67
+ User Request --->
68
+ --- RatHoleProxy.process_user_request(rack_request) --->
69
+ <==========> OLD SERVER
70
+ <--- RatHoleProxy.process_server_response(rack_response, rack_request) ---
71
+ User Response <---
72
+
73
+ == TODO
74
+ * handle server down
75
+ * handle gziped content (accept-encoding, transfer-encoding)
76
+ * maybe use a pool of Net::HTTP connections to speed things up
77
+ * write up some rathole's for common tasks (logging)
78
+ * move to rspec?
79
+ * support post, head, delete, etc?
80
+ * add caching? can apache do this for us?
81
+
82
+ == Credits
83
+ * Michael Hale (http://halethegeek.com)
84
+ * David Bogus
85
+
86
+ == LICENSE
87
+ The MIT License
88
+
89
+ Copyright (c) 2008-2009 Michael Hale & David Bogus
90
+
91
+ Permission is hereby granted, free of charge, to any person obtaining a copy
92
+ of this software and associated documentation files (the "Software"), to deal
93
+ in the Software without restriction, including without limitation the rights
94
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
95
+ copies of the Software, and to permit persons to whom the Software is
96
+ furnished to do so, subject to the following conditions:
97
+
98
+ The above copyright notice and this permission notice shall be included in
99
+ all copies or substantial portions of the Software.
100
+
101
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
102
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
103
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
104
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
105
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
106
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
107
+ THE SOFTWARE.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 9
data/lib/rat_hole.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'net/http'
2
+ require 'rack'
3
+ require 'delegate'
4
+ require 'util'
5
+ require 'open3'
6
+
7
+ class RatHole
8
+
9
+ def initialize(host, tidy=false)
10
+ @host = host
11
+ @tidy = tidy
12
+ end
13
+
14
+ def process_user_request(rack_request)
15
+ rack_request
16
+ end
17
+
18
+ def process_server_response(rack_response, rack_request)
19
+ rack_response
20
+ end
21
+
22
+ def call(env)
23
+ Net::HTTP.start(@host) do |http|
24
+ http.instance_eval{@socket = MethodSpy.new(@socket){|method| method =~ /#{ENV['RH_METHOD_SPY_FILTER']}/}} if $DEBUG
25
+ env.delete('HTTP_ACCEPT_ENCODING')
26
+ source_request = Rack::Request.new(env)
27
+ process_user_request(source_request)
28
+ source_headers = request_headers(source_request.env)
29
+
30
+ if source_request.query_string.nil? || source_request.query_string == ''
31
+ request_uri = source_request.path_info
32
+ else
33
+ request_uri = "#{source_request.path_info}?#{source_request.query_string}"
34
+ end
35
+
36
+ if source_request.get?
37
+ response = http.get(request_uri, source_headers)
38
+ elsif source_request.post?
39
+ post = Net::HTTP::Post.new(request_uri, source_headers)
40
+ post.form_data = source_request.POST
41
+ response = http.request(post)
42
+ end
43
+
44
+ code = response.code.to_i
45
+ headers = response.to_hash
46
+ body = response.body || ''
47
+ headers.delete('Transfer-Encoding')
48
+
49
+ server_response = Rack::Response.new(body, code, headers)
50
+ whack_array(server_response)
51
+ tidy_html(body) if server_response["Content-Type"] =~ /text\/html/ && @tidy
52
+
53
+ process_server_response(server_response, source_request)
54
+ server_response.finish
55
+ end
56
+ end
57
+
58
+ def whack_array(server_response)
59
+ server_response.headers.each do |k, v|
60
+ if v.is_a?(Array) && v.size == 1
61
+ server_response[k] = v.first
62
+ end
63
+ end
64
+ end
65
+
66
+ def tidy_html(body)
67
+ if `which tidy` == ''
68
+ $stderr.puts "tidy not found in path"
69
+ return
70
+ end
71
+ tidied, errors = Open3.popen3('tidy -ascii') do |stdin, stdout, stderr|
72
+ stdin.print body
73
+ stdin.close
74
+ [stdout.read, stderr.read]
75
+ end
76
+ $stderr.puts errors if $DEBUG
77
+ body.replace(tidied) if tidied != ""
78
+ end
79
+
80
+ def request_headers(env)
81
+ env.select{|k,v| k =~ /^HTTP/}.inject({}) do |h, e|
82
+ k,v = e
83
+ h.merge(k.split('_')[1..-1].join('-').to_camel_case => v)
84
+ end
85
+ end
86
+ end
87
+
88
+ # This class simply extends RatHole and does nothing.
89
+ # It's only useful for making sure that you have everything hooked up correctly.
90
+ class EmptyRatHole < RatHole
91
+ end
@@ -0,0 +1,40 @@
1
+ require 'test/unit'
2
+
3
+ class RatHoleTest < Test::Unit::TestCase
4
+ def through_the(rathole_klass, host, uri='/', headers={})
5
+ app = Rack::Builder.new do
6
+ use Rack::ShowExceptions
7
+ use Rack::ShowStatus
8
+ run rathole_klass.new(host)
9
+ end
10
+
11
+ app_response = Rack::MockRequest.new(app).get(uri, headers)
12
+ raw_response = Net::HTTP.start(host) do |http|
13
+ http.get(uri, headers)
14
+ end
15
+ # Wrap raw_response in Rack::Response to make things easier to work with.
16
+ raw_headers = raw_response.to_hash
17
+ raw_response = Rack::Response.new(raw_response.body, raw_response.code, raw_headers)
18
+ normalize_headers(raw_response.headers)
19
+ normalize_headers(app_response.headers)
20
+ yield(raw_response, app_response)
21
+ end
22
+
23
+ def normalize_headers(headers)
24
+ new_headers = headers.inject({}){|h,e|
25
+ k,v = e
26
+ # the value of these headers changes
27
+ v = nil if k =~ /cookie|date|runtime|last-modified/i
28
+ # skip headers that rat-hole removes
29
+ unless k =~ /transfer/i
30
+ v = v.first if v.is_a?(Array) && v.size == 1 #normalize things
31
+ h.merge!(k => v)
32
+ end
33
+ h
34
+ }
35
+ headers.replace(new_headers)
36
+ end
37
+
38
+ def test_nothing #make autotest happy
39
+ end
40
+ end
data/lib/util.rb ADDED
@@ -0,0 +1,61 @@
1
+ class SocketSpy < SimpleDelegator
2
+ def write(content)
3
+ p :writing => content
4
+ __getobj__.write content
5
+ end
6
+
7
+ [:readline, :readuntil, :read_all, :read].each{|symbol|
8
+ define_method(symbol) do |*args|
9
+ content = __getobj__.send(symbol, *args)
10
+ p :reading => content
11
+ content
12
+ end
13
+ }
14
+ end
15
+
16
+ class MethodSpy
17
+ def initialize(delegate, &block)
18
+ @delegate = delegate
19
+ @filter = block
20
+ end
21
+
22
+ def method_missing(symbol, *args, &block)
23
+ result = @delegate.send(symbol, *args, &block)
24
+ p [symbol, args, result, block] if @filter && @filter.call(symbol.to_s)
25
+ result
26
+ end
27
+ end
28
+
29
+ class String
30
+ def to_camel_case(split_on='-')
31
+ self.split(split_on).collect{|e| e.capitalize}.join(split_on)
32
+ end
33
+ end
34
+
35
+ module Net::HTTPHeader
36
+ def to_hash
37
+ new_headers = @header.dup
38
+ @header.each do |k,v|
39
+ new_headers.delete(k)
40
+ new_headers[k.to_camel_case] = v
41
+ end
42
+ new_headers
43
+ end
44
+ end
45
+
46
+ class Net::HTTP::Post
47
+ # handle multiple parameters with the same name
48
+ def form_data=(params, sep = '&')
49
+ self.body = params.map {|key,value|
50
+ if value.is_a?(Array)
51
+ value.map{|v| param_line(key, v) }
52
+ else
53
+ param_line(key, value)
54
+ end
55
+ }.join(sep)
56
+ end
57
+
58
+ def param_line(k, v)
59
+ "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}"
60
+ end
61
+ end
@@ -0,0 +1,35 @@
1
+ class MockRequest
2
+
3
+ attr_reader :headers, :body, :uri
4
+
5
+ def initialize(request_string)
6
+ lines = request_string.split("\r\n")
7
+
8
+ # find blank line which seperates the headers from the body
9
+ index_of_blank = nil
10
+ lines.each_with_index{|e,i|
11
+ index_of_blank = i if e == ""
12
+ }
13
+
14
+ @type, @uri = lines.first.split(/\s+/)
15
+ if index_of_blank
16
+ @headers = lines[1..index_of_blank]
17
+ @body = lines[(index_of_blank + 1)..-1].first
18
+ else
19
+ @headers = lines[1..-1]
20
+ end
21
+
22
+ @headers = @headers.inject({}){|h,e|
23
+ k,v = e.split(/:\s+/)
24
+ h.merge k => v
25
+ }
26
+ end
27
+
28
+ def get?
29
+ @type == 'GET'
30
+ end
31
+
32
+ def post?
33
+ @type == 'POST'
34
+ end
35
+ end
@@ -0,0 +1,233 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'rr'
4
+ require 'delegate'
5
+ require 'test/unit'
6
+ require 'rat_hole'
7
+ require 'rat_hole_test'
8
+ require 'mock_request'
9
+ require 'hpricot'
10
+
11
+ class Test::Unit::TestCase
12
+ include RR::Adapters::TestUnit
13
+ end
14
+
15
+ class TestRatHole < Test::Unit::TestCase
16
+ def mock_server(opts={})
17
+ opts[:host] = opts[:host] || '127.0.0.1'
18
+ opts[:code] = opts[:code] || 200
19
+ opts[:headers] = opts[:headers] || {}
20
+
21
+ host = opts[:host]
22
+ code = opts[:code]
23
+ headers = opts[:headers]
24
+ body = opts[:body]
25
+
26
+ response = [%(HTTP/1.1 #{code} OK)]
27
+ headers.each{|k,vs|
28
+ if vs.is_a?(Array)
29
+ response << vs.map{|v| "#{k.to_s}: #{v.to_s}" }
30
+ else
31
+ response << "#{k.to_s}: #{vs.to_s}"
32
+ end
33
+ }
34
+ response << ''
35
+ response << %(#{body})
36
+ response = response.join("\r\n")
37
+
38
+ @io = StringIO.new(response)
39
+ class << @io
40
+ attr_reader :written
41
+
42
+ def write(content)
43
+ @written = '' unless @written
44
+ @written << content
45
+ 0
46
+ end
47
+ end
48
+
49
+ mock(TCPSocket).open(host, 80) { @io }
50
+ end
51
+
52
+ def proxied_request
53
+ MockRequest.new(@io.written)
54
+ end
55
+
56
+ def send_get_request(rack_env={}, uri='/someuri', tidy=false)
57
+ opts = {:lint=>true}.merge(rack_env)
58
+ rh = RatHole.new('127.0.0.1', tidy)
59
+ Rack::MockRequest.new(rh).get(uri, opts)
60
+ end
61
+
62
+ def send_post_request(body='', uri='/someuri')
63
+ rh = RatHole.new('127.0.0.1')
64
+ Rack::MockRequest.new(rh).post(uri, {:lint=>true, :input=> body})
65
+ end
66
+
67
+ def test_response_unchanged
68
+ expected_body = 'the body'
69
+ mock_server(:body => expected_body)
70
+ response = send_get_request
71
+
72
+ assert_equal 200, response.status
73
+ assert_equal expected_body, response.body
74
+ end
75
+
76
+ def test_headers_normalized
77
+ mock_server(:headers => {'server' => 'freedom-2.0', 'set-cookie' => 'ronpaul=true'})
78
+ response = send_get_request
79
+ assert_equal('ronpaul=true', response.headers['Set-Cookie'])
80
+ assert_equal('freedom-2.0', response.headers['Server'])
81
+ end
82
+
83
+ def test_default_body
84
+ mock_server(:body => nil)
85
+ response = send_get_request
86
+ assert_equal '', response.body
87
+ end
88
+
89
+ def test_get_request
90
+ mock_server
91
+ send_get_request
92
+ assert proxied_request.get?
93
+ end
94
+
95
+ def test_post_request
96
+ mock_server
97
+ send_post_request("field1=value1")
98
+ assert proxied_request.post?
99
+ assert proxied_request.body.include?("field1=value1")
100
+ end
101
+
102
+ def test_post_duplicate_keys
103
+ mock_server
104
+ send_post_request("field1=value1&field1=value2")
105
+ assert_equal("field1=value1&field1=value2", proxied_request.body)
106
+ end
107
+
108
+ def test_post_data_urlencoded
109
+ mock_server
110
+ send_post_request("field%201=value%201")
111
+ assert("field%201=value%201", proxied_request.body)
112
+ end
113
+
114
+ def test_convert_rack_env_to_http_headers
115
+ headers_added_by_rack = {"Accept"=>"*/*", "Host"=>"127.0.0.1"}
116
+ expected_headers = {"X-Forwarded-Host"=>"www.example.com"}.merge(headers_added_by_rack)
117
+
118
+ mock_server
119
+ send_get_request({"HTTP_X_FORWARDED_HOST"=>"www.example.com", "NON_HTTP_HEADER" => '42'})
120
+ assert_equal(expected_headers, proxied_request.headers)
121
+ end
122
+
123
+ def test_content_type
124
+ mock_server(:headers=>{'content-type' => ['image/gif']})
125
+ response = send_get_request
126
+ assert_equal 'image/gif', response.headers['Content-Type']
127
+
128
+ mock_server
129
+ response = send_get_request
130
+ assert_equal 'text/html', response.headers['Content-Type']
131
+ end
132
+
133
+ def test_convert_rack_env_to_http_headers_more_data
134
+ expected_headers = {
135
+ "X-Forwarded-Host"=>"www.example.com",
136
+ "User-Agent"=>"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4",
137
+ "Cache-Control"=>"max-age=0",
138
+ "If-None-Match"=>"\"58dc30c-216-3d878fe2\"-gzip",
139
+ "Accept-Language"=>"en-us,en;q=0.5",
140
+ "Host"=>"localhost:4001",
141
+ "Referer"=>"http://www.example.com/posts/",
142
+ "Cookie"=>"cookie1=YWJj; cookie2=ZGVm",
143
+ "Accept-Charset"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7",
144
+ "X-Forwarded-Server"=>"www.example.com",
145
+ "If-Modified-Since"=>"Tue, 17 Sep 2002 20:26:10 GMT",
146
+ "X-Forwarded-For"=>"127.0.0.1",
147
+ "Accept"=>"image/png,image/*;q=0.8,*/*;q=0.5",
148
+ "Connection"=>"Keep-Alive"}
149
+
150
+ rack_env = {"SERVER_NAME"=>"localhost",
151
+ "HTTP_X_FORWARDED_HOST"=>"www.example.com",
152
+ "HTTP_ACCEPT_ENCODING"=>"gzip,deflate",
153
+ "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4",
154
+ "HTTP_CACHE_CONTROL"=>"max-age=0",
155
+ "HTTP_IF_NONE_MATCH"=>"\"58dc30c-216-3d878fe2\"-gzip",
156
+ "HTTP_ACCEPT_LANGUAGE"=>"en-us,en;q=0.5",
157
+ "HTTP_HOST"=>"localhost:4001",
158
+ "HTTP_REFERER"=>"http://www.example.com/posts/",
159
+ "HTTP_COOKIE"=>"cookie1=YWJj; cookie2=ZGVm",
160
+ "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7",
161
+ "HTTP_X_FORWARDED_SERVER"=>"www.example.com",
162
+ "HTTP_IF_MODIFIED_SINCE"=>"Tue, 17 Sep 2002 20:26:10 GMT",
163
+ "HTTP_X_FORWARDED_FOR"=>"127.0.0.1",
164
+ "HTTP_ACCEPT"=>"image/png,image/*;q=0.8,*/*;q=0.5",
165
+ "HTTP_CONNECTION"=>"Keep-Alive",}
166
+
167
+ mock_server(:body => 'not testing this')
168
+ send_get_request(rack_env)
169
+ assert_equal(expected_headers, proxied_request.headers)
170
+ end
171
+
172
+ def test_request_uri
173
+ mock_server
174
+ send_get_request({}, '/uri?with=param')
175
+ assert_equal('/uri?with=param', proxied_request.uri)
176
+
177
+ mock_server
178
+ send_get_request({}, '/uri')
179
+ assert_equal('/uri', proxied_request.uri)
180
+
181
+ mock_server
182
+ send_post_request('', '/uri?with=param')
183
+ assert_equal('/uri?with=param', proxied_request.uri)
184
+ end
185
+
186
+ def test_tidy_fails
187
+ body = '<something tidy <will </barf on'
188
+ mock_server(:body => body)
189
+ response = send_get_request({}, '/', true)
190
+ assert_equal(response.body, body)
191
+ end
192
+
193
+ def test_tidy_tidies
194
+ mock_server(:body => 'tidy me')
195
+ response = send_get_request({:lint=>false}, '/', true)
196
+ assert response.body =~ /html/i
197
+ end
198
+ end
199
+
200
+ class TestEmptyRatHole < RatHoleTest
201
+ def test_has_proper_response
202
+ through_the(EmptyRatHole, 'halethegeek.com') do |raw_response, app_response|
203
+ assert_not_equal 0, raw_response.headers
204
+ assert_equal raw_response.status.to_i, app_response.status.to_i
205
+ assert_equal raw_response.headers, app_response.headers
206
+ assert_equal raw_response.body.to_s, app_response.body.to_s
207
+ end
208
+ end
209
+ end
210
+
211
+ class PoliticalAgendaRatHoleTest < RatHoleTest
212
+ def test_has_proper_response
213
+ through_the(PoliticalAgendaRatHole, 'terralien.com') do |raw_response, app_response|
214
+ assert_equal raw_response.status.to_i, app_response.status.to_i
215
+ assert !raw_response.headers.has_key?('Ron-Paul')
216
+ assert app_response.headers.has_key?('Ron-Paul')
217
+
218
+ assert !raw_response.body.to_s.include?('http://ronpaul.com')
219
+ assert app_response.body.to_s.include?('http://ronpaul.com')
220
+ end
221
+ end
222
+ end
223
+
224
+ class PoliticalAgendaRatHole < RatHole
225
+ def process_server_response(rack_response, rack_request)
226
+ if(rack_response.content_type =~ /text\/html/)
227
+ doc = Hpricot(rack_response.body.first)
228
+ (doc/"a").set('href', 'http://ronpaul.com')
229
+ rack_response.body.first.replace(doc.to_html)
230
+ rack_response.headers['Ron-Paul'] = 'wish I could have voted for this guy'
231
+ end
232
+ end
233
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rat-hole
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.9
5
+ platform: ruby
6
+ authors:
7
+ - Michael Hale
8
+ - David Bogus
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-02-26 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rack
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.9.1
25
+ version:
26
+ description: Rat Hole is a handy library for creating a rack compliant http proxy that allows you to modify the request from the user and the response from the server.
27
+ email: mikehale@gmail.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files: []
33
+
34
+ files:
35
+ - CHANGELOG.rdoc
36
+ - Manifest.txt
37
+ - README.rdoc
38
+ - VERSION.yml
39
+ - lib/rat_hole.rb
40
+ - lib/rat_hole_test.rb
41
+ - lib/util.rb
42
+ - test/mock_request.rb
43
+ - test/test_rat_hole.rb
44
+ has_rdoc: true
45
+ homepage: http://github.com/mikehale/rat-hole
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --inline-source
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: Rack compliant http proxy
73
+ test_files: []
74
+