rat-hole 0.1.9

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/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
+