tap-http 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History CHANGED
@@ -1,8 +1,8 @@
1
- == 0.3.2 / 2009-02-23
1
+ == 0.4.0 / 2009-03-17
2
2
 
3
- Bug fix in handling of multipart/form data.
4
-
5
- * parse_rack_request now parses overloaded params
3
+ Significant rewrite using an updated version of TapServer
4
+ and Mechanize. TapHttp now serves its own redirection
5
+ commands via TapUbiquity.
6
6
 
7
7
  == 0.3.1 / 2009-02-19
8
8
 
data/README CHANGED
@@ -4,11 +4,11 @@ A task library for submitting http requests using {Tap}[http://tap.rubyforge.org
4
4
 
5
5
  == Description
6
6
 
7
- TapHttp provides modules to construct and submit HTTP requests from a hash
8
- that specifies the target url, headers, parameters, etc. TapHttp is
9
- designed to work with a {Ubiquity}[http://labs.mozilla.com/2008/08/introducing-ubiquity/]
10
- command called {redirect-http}[http://gist.github.com/25932]; together
11
- they allow the capture and resubmission of web forms.
7
+ TapHttp provides modules to capture and resubmit HTTP requests. TapHttp is
8
+ designed to work with a
9
+ {Ubiquity}[http://labs.mozilla.com/2008/08/introducing-ubiquity/] command
10
+ called redirect-http; together they allow the capture and resubmission of web
11
+ forms.
12
12
 
13
13
  * Lighthouse[http://bahuvrihi.lighthouseapp.com/projects/9908-tap-task-application/tickets]
14
14
  * Github[http://github.com/bahuvrihi/tap-http/tree/master]
@@ -16,28 +16,28 @@ they allow the capture and resubmission of web forms.
16
16
 
17
17
  === Usage
18
18
 
19
- TapHttp submits http requests using the Tap::Http::Request module. Headers,
20
- parameters, and other configurations may be specified, but only a request
21
- method and uri are required.
19
+ TapHttp submits HTTP requests using
20
+ Mechanize[http://mechanize.rubyforge.org/mechanize/], via the
21
+ Tap::Http::Submit task. Headers and parameters may be specified, but only a
22
+ uri is required.
22
23
 
23
- include Tap::Http
24
-
25
- res = Request.get('http://www.google.com/search')
26
- res.body[0,80] # => "<!doctype html><head><title>tap-http - Google Search</title><style>body{backgrou"
24
+ Tap::Http::Submit.new.process(:uri => 'http://www.google.com')[0, 80]
25
+ # => "<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=ISO-8859"
27
26
 
28
27
  === Submitting Web Forms
29
28
 
30
- Http requests from web forms may be captured and resubmitted using a combination
31
- of tools. To do so start a tap server from the command line (of course tap-http
32
- must be installed):
29
+ HTTP requests from web forms may be captured and resubmitted using a
30
+ combination of tools. To learn how, install tap-http and start a tap server
31
+ from the command line:
33
32
 
34
33
  % tap server
35
34
 
36
- Now open a browser and work through the {tutorial}[http://localhost:8080/capture].
35
+ Now open a browser and work through the {tutorial}[http://localhost:8080/capture/tutorial].
37
36
 
38
37
  == Installation
39
38
 
40
- TapHttp is available as a gem on RubyForge[http://rubyforge.org/projects/tap]. Use:
39
+ TapHttp is available as a gem on RubyForge[http://rubyforge.org/projects/tap].
40
+ Use:
41
41
 
42
42
  % gem install tap-http
43
43
 
@@ -0,0 +1,176 @@
1
+ require 'tap/controller'
2
+ require 'tap/http/utils'
3
+ require 'tap/ubiquity/utils'
4
+
5
+ module Tap
6
+ module Http
7
+ # ::controller
8
+ # ::ubiquity
9
+ class Capture < Tap::Controller
10
+ include Tap::Http::Utils
11
+ include Tap::Ubiquity::Utils
12
+
13
+ PREFIX = '__redirect_http_'
14
+ REDIRECT_PARAMETER = '__redirect_http_original_action'
15
+
16
+ # Brings up the tutorial.
17
+ def index
18
+ render 'index.erb', :locals => {:captures => persistence.index }
19
+ end
20
+
21
+ def create(id)
22
+ persistence.create(id) do |io|
23
+ io << YAML.dump([parse_request])
24
+ end
25
+ download(id)
26
+ end
27
+
28
+ def show(id)
29
+ response['Content-Type'] = "text/plain"
30
+ persistence.read(id)
31
+ end
32
+
33
+ def download(id)
34
+ path = persistence.path(id)
35
+ filename = id
36
+ filename += ".yml" if File.extname(id) == ""
37
+
38
+ response['Content-Type'] = "text/plain"
39
+ response['Content-Disposition'] = "attachment; filename=#{filename};"
40
+ persistence.read(id)
41
+ end
42
+
43
+ def update(id="request")
44
+ path = persistence.path(id)
45
+ requests = File.exists?(path) ? YAML.load_file(path) : []
46
+ requests << parse_request
47
+
48
+ persistence.update(id) do |io|
49
+ io << YAML.dump(requests)
50
+ end
51
+ download(id)
52
+ end
53
+
54
+ def destroy(id)
55
+ persistence.destroy(id)
56
+ index
57
+ end
58
+
59
+ # Brings up a tutorial teaching how to capture and resubmit forms.
60
+ def tutorial
61
+ serve js_injection(:redirect_http) do |link|
62
+ content = render 'tutorial.erb'
63
+ content + link
64
+ end
65
+ end
66
+
67
+ # Say is the target of the tutorial.
68
+ def say
69
+ "<pre>#{request.params['words']}</pre>"
70
+ end
71
+
72
+ # Returns the redirection script.
73
+ def redirect_http
74
+ style = render 'redirect.css'
75
+ script = render 'redirect.js', :locals => {
76
+ :redirect_parameter => REDIRECT_PARAMETER,
77
+ :redirect_action => uri(:update),
78
+ :header => render('header.erb'),
79
+ :footer => render('footer.erb')
80
+ }
81
+
82
+ if request.get?
83
+ response['Content-Type'] = 'text/plain'
84
+ %Q{
85
+ <style type="text/css">
86
+ #{style.strip}
87
+ </style>
88
+ <script type="text/javascript">
89
+ #{script.strip}
90
+ </script>}
91
+ else
92
+ response['Content-Type'] = 'text/javascript'
93
+ %Q{
94
+ var style = document.createElement("style");
95
+ style.type = "text/css";
96
+ style.innerHTML = #{style.to_json};
97
+ document.body.appendChild(style);
98
+
99
+ var script = document.createElement("script");
100
+ script.type = "text/javascript";
101
+ script.innerHTML = #{script.to_json};
102
+ document.body.appendChild(script);
103
+ }
104
+ end
105
+ end
106
+
107
+ # Parses HTTP request
108
+ def http
109
+ if request.get?
110
+ render 'http.erb'
111
+ else
112
+ keep_content = request.params['keep_content'] == "true"
113
+
114
+ hash = {}
115
+ parse_http_request(request.params['http'], keep_content).each_pair do |key, value|
116
+ hash[key.to_s] = value
117
+ end
118
+
119
+ # remove extranous data
120
+ hash.delete('headers')
121
+ hash.delete('version')
122
+
123
+ response['Content-Type'] = "text/plain"
124
+ YAML.dump(hash)
125
+ end
126
+ end
127
+
128
+ protected
129
+
130
+ def prefix
131
+ PREFIX
132
+ end
133
+
134
+ def capture_overloaded_parameters # :nodoc:
135
+ # perform the actions of Rack::Request::POST, but capture
136
+ # overloaded parameter names
137
+ env = request.env
138
+ env["rack.request.form_input"] = env["rack.input"]
139
+ unless env["rack.request.form_hash"] = Tap::Http::Utils.parse_multipart(env)
140
+ env["rack.request.form_vars"] = env["rack.input"].read
141
+ env["rack.request.form_hash"] = Rack::Utils.parse_query(env["rack.request.form_vars"])
142
+ env["rack.input"].rewind if env["rack.input"].respond_to?(:rewind)
143
+ end
144
+ end
145
+
146
+ # helper to parse the request into a request hash for
147
+ # use by a Tap::Http::Submit task
148
+ def parse_request(keep_content=true) # :nodoc:
149
+ capture_overloaded_parameters
150
+
151
+ hash = {}
152
+ parse_rack_request(request, keep_content).each_pair do |key, value|
153
+ hash[key.to_s] = value
154
+ end
155
+
156
+ action = hash['params'].delete(REDIRECT_PARAMETER)
157
+ hash['uri'] = if action =~ /^http/
158
+ # action is an href already
159
+ action
160
+ else
161
+ # make action relative to Referer
162
+ uri = URI.parse(hash['headers']['Referer'].to_s)
163
+ uri.path = action
164
+ uri.to_s
165
+ end
166
+
167
+ # remove extranous data
168
+ hash.delete('headers')
169
+ hash.delete('version')
170
+
171
+ hash
172
+ end
173
+
174
+ end
175
+ end
176
+ end
@@ -1,19 +1,13 @@
1
- require 'tap/http/request'
1
+ require 'tap/http/submit'
2
2
 
3
3
  module Tap
4
4
  module Http
5
5
  # Tap::Http::Get::manifest gets the uri
6
6
  # Submits an Http request to the specified uri and returns the message body.
7
- class Get < Tap::Task
8
- include Request
9
-
7
+ class Get < Submit
10
8
  def process(uri)
11
- log :get, uri
12
- res = Request.get(uri)
13
- log(nil, res.message)
14
- res.body
9
+ super(:uri => uri)
15
10
  end
16
-
17
11
  end
18
12
  end
19
13
  end
@@ -1,20 +1,63 @@
1
- require 'tap/http/request'
1
+ require 'mechanize'
2
2
 
3
3
  module Tap
4
4
  module Http
5
5
  # Tap::Http::Submit::manifest submits a captured http request
6
6
  class Submit < Tap::Task
7
+ Form = WWW::Mechanize::Form
7
8
 
8
- def process(opts)
9
- opts = symbolize(opts)
9
+ # mechanize agent configurations
10
+ nest :mechanize do
11
+ config :ca_file, nil
12
+ config :cert, nil
13
+ config :conditional_requests, true
14
+ config :follow_meta_refresh, false
15
+ config :keep_alive, true
16
+ config :keep_alive_time, 300
17
+ config :key, nil
18
+ config :open_timeout, nil
19
+ config :pass, nil
20
+ config :read_timeout, nil
21
+ config :redirect_ok, true
22
+ config :redirection_limit, 20
23
+ config :user_agent, "WWW-Mechanize/0.9.2 (http://rubyforge.org/projects/mechanize/)"
24
+ config :verify_callback, nil
25
+ config :watch_for_set, nil
26
+ end
27
+
28
+ def process(*requests)
29
+ # initialize mechanize agent
30
+ agent = WWW::Mechanize.new
31
+ agent.log = app.logger unless app.quiet
32
+
33
+ # reconfigures agent with mechanize config
34
+ mechanize.config.dup.bind(agent)
10
35
 
11
- request_method = opts.delete(:request_method) || 'GET'
12
- uri = opts.delete(:uri)
36
+ agent.pre_connect_hooks << lambda do |options|
37
+ connection = options[:connection]
38
+ unless connection.started?
39
+ log :mechanize, "#{connection.address}#{connection.use_ssl? ? ' (ssl)' : ''}"
40
+ end
41
+ end
13
42
 
14
- log request_method, uri
15
- res = Request.request(request_method, uri, opts)
16
- log(nil, res.message)
17
- res.body
43
+ # handle requests
44
+ requests.inject(nil) do |last_page, request|
45
+ request = symbolize(request)
46
+
47
+ # note get handles nil request methods (ie '') and has to
48
+ # reassign uri to url to make mechanize happy
49
+ page = case request[:request_method].to_s
50
+ when /get/i, '' then agent.get(request.merge(:url => request[:uri]))
51
+ when /post/i then agent.submit(prepare_form(request), nil)
52
+ when /head/i then agent.head(nil, nil, request)
53
+ when /put/i then agent.put(nil, nil, request)
54
+ when /delete/i then agent.delete(nil, nil, request)
55
+ else raise "unsupported request method: #{request.inspect}"
56
+ end
57
+
58
+ # process page results if desired
59
+ block_given? ? yield(page) : page.content
60
+ end
18
61
  end
19
62
 
20
63
  protected
@@ -26,6 +69,38 @@ module Tap
26
69
  options
27
70
  end
28
71
  end
72
+
73
+ # A subclass of Hash used exclusively as the node for a form.
74
+ # Evidently a search method is necessary.
75
+ class FormNode < Hash # :nodoc:
76
+ def search(*args); []; end
77
+ end
78
+
79
+ # prepares a Mechanize::Form for the request. this method is patterned
80
+ # after what happens in Mechanize#post.
81
+ def prepare_form(request) # :nodoc:
82
+ node = FormNode.new
83
+ node['method'] = request[:request_method]
84
+ node['action'] = request[:uri]
85
+ node['enctype'] = request[:headers] ? request[:headers]['Content-Type'] : nil
86
+
87
+ form = Form.new(node)
88
+ request[:params].each_pair do |key, value|
89
+ case value
90
+ when IO
91
+ form.enctype = 'multipart/form-data'
92
+ upload = Form::FileUpload.new(key.to_s, ::File.basename(value.path))
93
+ upload.file_data = value.read
94
+ form.file_uploads << upload
95
+ when Array
96
+ fields = value.collect {|val| Form::Field.new(key.to_s, val)}
97
+ form.fields.concat fields
98
+ else
99
+ form.fields << Form::Field.new(key.to_s, value)
100
+ end
101
+ end
102
+ form
103
+ end
29
104
  end
30
105
  end
31
106
  end
@@ -113,7 +113,7 @@ module Tap
113
113
  params[key] = splat(values)
114
114
  end if req.query
115
115
 
116
- { :url => headers['Host'] ? File.join("http://", headers['Host'], req.path_info) : req.path_info,
116
+ { :uri => headers['Host'] ? File.join("http://", headers['Host'], req.path_info) : req.path_info,
117
117
  :request_method => req.request_method,
118
118
  :version => req.http_version.to_s,
119
119
  :headers => headers,
@@ -199,16 +199,6 @@ module Tap
199
199
  $1.upcase + $2.downcase
200
200
  end.join("-")
201
201
  end
202
-
203
- # Inflates (ie unzips) a gzip string, as may be returned by requests
204
- # that accept 'gzip' and 'deflate' content encoding.
205
- #
206
- #--
207
- # Utils.inflate(res.body) if res['content-encoding'] == 'gzip'
208
- #
209
- def inflate(str)
210
- Zlib::GzipReader.new( StringIO.new( str ) ).read
211
- end
212
202
 
213
203
  EOL = Rack::Utils::Multipart::EOL
214
204
  # Lifted from Rack::Utils::Multipart, and modified to collect
@@ -14,12 +14,14 @@ module Tap
14
14
  module HttpTest
15
15
 
16
16
  class MockServer
17
- def initialize(body, status=200, headers={})
18
- @response = [status, headers, [body]]
17
+ def initialize(status=200, headers={}, &block)
18
+ @status = status
19
+ @headers = headers
20
+ @block = block
19
21
  end
20
-
22
+
21
23
  def call(env)
22
- @response
24
+ [@status, @headers, @block.call(env)]
23
25
  end
24
26
  end
25
27
 
@@ -0,0 +1,41 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "tap-http"
3
+ s.version = "0.4.0"
4
+ s.author = "Simon Chiang"
5
+ s.email = "simon.a.chiang@gmail.com"
6
+ s.homepage = "http://tap.rubyforge.org/tap-http"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = "A task library for submitting http requests using Tap."
9
+ s.require_path = "lib"
10
+ s.rubyforge_project = "tap"
11
+ s.add_dependency("tap-ubiquity", "= 0.2.0")
12
+ s.add_dependency("mechanize", ">= 0.9.2")
13
+ s.has_rdoc = true
14
+ s.rdoc_options.concat %W{--main README -S -N --title Tap\sHttp}
15
+
16
+ # list extra rdoc files like README here.
17
+ s.extra_rdoc_files = %W{
18
+ README
19
+ MIT-LICENSE
20
+ History
21
+ }
22
+
23
+ # list the files you want to include here. you can
24
+ # check this manifest using 'rake :print_manifest'
25
+ s.files = %W{
26
+ lib/tap/http/capture.rb
27
+ lib/tap/http/get.rb
28
+ lib/tap/http/submit.rb
29
+ lib/tap/http/utils.rb
30
+ lib/tap/test/http_test.rb
31
+ tap-http.gemspec
32
+ tap.yml
33
+ views/tap/http/capture/footer.erb
34
+ views/tap/http/capture/header.erb
35
+ views/tap/http/capture/index.erb
36
+ views/tap/http/capture/http.erb
37
+ views/tap/http/capture/redirect.css
38
+ views/tap/http/capture/redirect.js
39
+ views/tap/http/capture/tutorial.erb
40
+ }
41
+ end
@@ -0,0 +1,4 @@
1
+ <div class='<%= prefix %>message'>Redirecting HTTP Requests</div>
2
+ <div class='<%= prefix %>revert'>
3
+ <a href='javascript:RedirectHttp.revert();'>close</a>
4
+ </div>
@@ -0,0 +1,5 @@
1
+ <div class='<%= prefix %>controls'>
2
+ <input id='<%= prefix %>id' type='text' value=''>Name</input>
3
+ <input id='<%= prefix %>continue' type='checkbox'>continue</input>
4
+ </div>
5
+ <%= render 'footer.erb' %>
@@ -0,0 +1,31 @@
1
+ <h1>Parse HTTP Parameters</h1>
2
+
3
+ <p>Enter an HTTP request, like the ones you can capture using the
4
+ <a href='https://addons.mozilla.org/en-US/firefox/addon/3829'>LiveHTTPHeaders</a> addon for
5
+ <a href='http://www.mozilla.com/en-US/firefox/'>Firefox</a>.
6
+ </p>
7
+
8
+ <form action='<%= uri(:http) %>' method='post'>
9
+ <textarea rows='20' cols='60' name='http'></textarea>
10
+ <br/>
11
+ <input type='checkbox' name='keep_content' value='true' checked='true'> Keep File Content
12
+ <input type='submit' value='Parse'>
13
+ </form>
14
+
15
+ <p>Note the request must be properly formated. For example:</p>
16
+
17
+ <pre>
18
+ GET / HTTP/1.1
19
+ Host: tap.rubyforge.org
20
+ User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12
21
+ Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
22
+ Accept-Language: en-us,en;q=0.5
23
+ Accept-Encoding: gzip,deflate
24
+ Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
25
+ Keep-Alive: 300
26
+ Connection: keep-alive
27
+ </pre>
28
+
29
+ <p>Note that POST requests require an empty line between the headers and body.
30
+ Without it you get a WEBrick::HTTPStatus::BadRequest error.</p>
31
+
@@ -0,0 +1,11 @@
1
+ <p>Captures:</p>
2
+ <ul>
3
+ <% if captures.empty? %>
4
+ <li>(none)</li>
5
+ <% end %>
6
+ <% captures.each do |capture| %>
7
+ <li><a href="<%= uri("show/#{capture}") %>"><%= capture %></a> (<a href="<%= uri("destroy/#{capture}") %>">x</a>)</li>
8
+ <% end %>
9
+ </ul>
10
+
11
+ <p>See the <a href="<%= uri(:tutorial) %>">Tutorial</a> for help.</p>
@@ -0,0 +1,42 @@
1
+ div.<%= prefix %>notification {
2
+ position:relative;
3
+ text-align:center;
4
+ background-color:#EFE3BC;
5
+ border: thin solid white;
6
+ font-family: arial;
7
+ font-size: 16;
8
+ }
9
+
10
+ div#<%= prefix %>header {
11
+ position:relative;
12
+ top:0;
13
+ left:0;
14
+ right:0;
15
+ }
16
+
17
+ div#<%= prefix %>footer {
18
+ position:relative;
19
+ bottom:0;
20
+ left:0;
21
+ right:0;
22
+ }
23
+
24
+ div.<%= prefix %>message {
25
+ display:inline;
26
+ font-weight:bold;
27
+ }
28
+
29
+ div.<%= prefix %>revert {
30
+ display:inline;
31
+ position:absolute;
32
+ right:0;
33
+ font-size:0.8em;
34
+ font-weight:normal;
35
+ text-decoration:none;
36
+ }
37
+
38
+ div.<%= prefix %>controls {
39
+ display:inline;
40
+ position:absolute;
41
+ left:0;
42
+ }
@@ -0,0 +1,177 @@
1
+ var RedirectHttp = {
2
+
3
+ active: function() {
4
+ var header = document.getElementById("<%= prefix %>header");
5
+ if(header) return true;
6
+ else return false;
7
+ },
8
+
9
+ follow_request: function() {
10
+ var checkbox = document.getElementById("<%= prefix %>continue");
11
+ if(checkbox && checkbox.checked) return true;
12
+ else return false;
13
+ },
14
+
15
+ redirect_uri: function(tag) {
16
+ var id = document.getElementById("<%= prefix %>id");
17
+ return '<%= redirect_action %>/' + escape(id.value);
18
+ },
19
+
20
+ redirect_form: function(form) {
21
+ if(RedirectHttp.active()) {
22
+ form.setAttribute('action', RedirectHttp.redirect_uri());
23
+ form.submit();
24
+
25
+ if(RedirectHttp.follow_request()) {
26
+ this.revert();
27
+ form.submit();
28
+ }
29
+
30
+ return false;
31
+ }
32
+
33
+ return true;
34
+ },
35
+
36
+ redirect_anchor: function(anchor) {
37
+ if(RedirectHttp.active()) {
38
+ var original_href = anchor.getAttribute('<%= prefix %>original_href');
39
+ location.href = RedirectHttp.redirect_uri() + '?<%= redirect_parameter %>=' + escape(original_href);
40
+
41
+ if(RedirectHttp.follow_request()) {
42
+ this.revert();
43
+ location.href = anchor.href;
44
+ }
45
+
46
+ return false;
47
+ }
48
+
49
+ return true;
50
+ },
51
+
52
+ // Redirects forms and links to '<%= redirect_action %>'
53
+ redirect: function() {
54
+ // Revert if forms have already been redirected
55
+ if(RedirectHttp.active()) {
56
+ RedirectHttp.revert();
57
+ return false;
58
+ }
59
+
60
+ try {
61
+
62
+ // redirect forms to the redirect_action
63
+ var forms = document.getElementsByTagName('form');
64
+ for(i=0; i<forms.length; i++) {
65
+ var form = forms[i];
66
+
67
+ // store the original action
68
+ var input = document.createElement("input");
69
+ input.setAttribute("id", '<%= redirect_parameter %>_' + i);
70
+ input.setAttribute("name", '<%= redirect_parameter %>');
71
+ input.setAttribute("type", 'hidden');
72
+ input.setAttribute("value", form.getAttribute('action'));
73
+
74
+ form.setAttribute('<%= prefix %>index', i);
75
+ form.appendChild(input);
76
+
77
+ // redirect onSubmit
78
+ var current_on_submit = '';
79
+ if(form.hasAttribute("onSubmit")) current_on_submit = form.getAttribute("onSubmit");
80
+ var on_submit = "return RedirectHttp.redirect_form(this);";
81
+
82
+ form.setAttribute("onSubmit", current_on_submit + ";" + on_submit);
83
+ form.setAttribute("<%= prefix %>original_callback", current_on_submit);
84
+ }
85
+
86
+ // redirect anchors to the redirect_action
87
+ var anchors = document.getElementsByTagName('a');
88
+ for(i=0; i<anchors.length; i++) {
89
+ var anchor = anchors[i];
90
+
91
+ // store the original href
92
+ anchor.setAttribute("<%= prefix %>original_href", anchor.href);
93
+
94
+ // redirect onClick
95
+ var current_on_click = '';
96
+ if(anchor.hasAttribute("onClick")) current_on_click = anchor.getAttribute("onClick");
97
+ var on_click = "return RedirectHttp.redirect_anchor(this);";
98
+
99
+ anchor.setAttribute("onClick", current_on_click + ";" + on_click);
100
+ anchor.setAttribute("<%= prefix %>original_callback", current_on_click);
101
+ }
102
+
103
+ // position and show notifications
104
+ var header = document.createElement("div");
105
+ header.setAttribute("id", "<%= prefix %>header");
106
+ header.setAttribute("class", "<%= prefix %>notification");
107
+ header.innerHTML = <%= header.to_json %>;
108
+ document.body.insertBefore(header, document.body.firstChild);
109
+
110
+ var footer = document.createElement("div");
111
+ footer.setAttribute("id", "<%= prefix %>footer");
112
+ footer.setAttribute("class", "<%= prefix %>notification");
113
+ footer.innerHTML = <%= footer.to_json %>;
114
+ document.body.appendChild(footer);
115
+
116
+ } catch(ex) {
117
+ alert_error(ex);
118
+ }
119
+ },
120
+
121
+ // Reverts redirection.
122
+ revert: function() {
123
+ try {
124
+
125
+ // redirect forms back to original actions
126
+ var forms = document.getElementsByTagName('form');
127
+ for(i=0; i<forms.length; i++) {
128
+ var form = forms[i];
129
+
130
+ // check in case the form is not redirected
131
+ if(form.hasAttribute('<%= prefix %>index')) {
132
+ var index = form.getAttribute('<%= prefix %>index');
133
+ var input = document.getElementById("<%= redirect_parameter %>_" + index);
134
+
135
+ form.setAttribute('action', input.value);
136
+ form.removeAttribute('<%= prefix %>index');
137
+ form.removeChild(input);
138
+
139
+ form.setAttribute('onSubmit', form.getAttribute('<%= prefix %>original_callback'));
140
+ form.removeAttribute('<%= prefix %>original_callback');
141
+ }
142
+ }
143
+
144
+ // redirect anchors back to original hrefs
145
+ var anchors = document.getElementsByTagName('a');
146
+ for(i=0; i<anchors.length; i++) {
147
+ var anchor = anchors[i];
148
+
149
+ // check in case the anchor is not redirected
150
+ if(anchor.hasAttribute('<%= prefix %>original_action')) {
151
+ anchor.setAttribute('href', anchor.getAttribute('<%= prefix %>original_href'));
152
+ anchor.removeAttribute('<%= prefix %>original_href');
153
+
154
+ anchor.setAttribute('onClick', anchor.getAttribute('<%= prefix %>original_callback'));
155
+ anchor.removeAttribute('<%= prefix %>original_callback');
156
+ }
157
+ }
158
+
159
+ // remove notifications
160
+ var header = document.getElementById("<%= prefix %>header");
161
+ if(header) document.body.removeChild(header);
162
+
163
+ var footer = document.getElementById("<%= prefix %>footer");
164
+ if(footer) document.body.removeChild(footer);
165
+
166
+ } catch(ex) {
167
+ alert_error(ex);
168
+ }
169
+ },
170
+
171
+ // Alerts of errors due to RedirectHttp
172
+ alert_error: function(ex) {
173
+ alert("Redirect HTTP Error: " + ex.message + "\n\n The webpage may have been corrupted; be sure to refresh.");
174
+ }
175
+ };
176
+
177
+ RedirectHttp.redirect();
@@ -6,7 +6,7 @@ resubmission at a later date. First, get the environment right:
6
6
  <ul>
7
7
  <li>Be sure you're viewing this in <a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
8
8
  <li>Install <a href="http://labs.mozilla.com/2008/08/introducing-ubiquity/">Ubiquity</a></li>
9
- <li>Subscribe to the <a href="http://gist.github.com/25932">redirect-http</a> command</li>
9
+ <li>Subscribe to the redirect-http command on this page</li>
10
10
  </ul>
11
11
 
12
12
  <p>
@@ -16,7 +16,7 @@ else you need to capture the parameters of a form):
16
16
 
17
17
  <ul>
18
18
  <li>Bring up Ubiquity in Firefox by pressing 'option+space'</li>
19
- <li>Enter the command: redirect-http http://localhost:8080/capture/http_to_yaml</li>
19
+ <li>Enter the command: redirect_http</li>
20
20
  </ul>
21
21
 
22
22
  <p>
@@ -34,7 +34,20 @@ body. To prove everything worked right, click the close link in the
34
34
  redirection bar and resubmit the form.
35
35
  </p>
36
36
 
37
+ <a href="/capture/say?words=link+was+clicked">Link</a>
37
38
  <form action='/capture/say'>
38
- What you want to say: <input name='words' />
39
- <input type="submit" value="submit">
39
+ Form: <input name='words' value="form was submitted"/>
40
+ <input type="submit" value="submit" />
41
+ </form>
42
+
43
+ <p>
44
+ Note that redirection occurs via the on-click and on-submit callbacks for
45
+ links and forms, respectively. The existing behavior is preserved, as can
46
+ be demonstrated below:
47
+ </p>
48
+
49
+ <a href="/capture/say?words=link+was+clicked" onclick='alert("Link was clicked!")'>Link with OnClick</a>
50
+ <form action='/capture/say' onsubmit='alert("Form was submitted!")'>
51
+ Form with OnSubmit: <input name='words' value="form was submitted"/>
52
+ <input type="submit" value="submit" />
40
53
  </form>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tap-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiang
@@ -9,28 +9,28 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-23 00:00:00 -07:00
12
+ date: 2009-03-17 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: tap
16
+ name: tap-ubiquity
17
17
  type: :runtime
18
18
  version_requirement:
19
19
  version_requirements: !ruby/object:Gem::Requirement
20
20
  requirements:
21
- - - ">="
21
+ - - "="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.12.2
23
+ version: 0.2.0
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
- name: rack
26
+ name: mechanize
27
27
  type: :runtime
28
28
  version_requirement:
29
29
  version_requirements: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.9.1
33
+ version: 0.9.2
34
34
  version:
35
35
  description:
36
36
  email: simon.a.chiang@gmail.com
@@ -43,14 +43,20 @@ extra_rdoc_files:
43
43
  - MIT-LICENSE
44
44
  - History
45
45
  files:
46
- - controllers/capture_controller.rb
46
+ - lib/tap/http/capture.rb
47
47
  - lib/tap/http/get.rb
48
- - lib/tap/http/request.rb
49
48
  - lib/tap/http/submit.rb
50
49
  - lib/tap/http/utils.rb
51
50
  - lib/tap/test/http_test.rb
52
- - views/capture_controller/tutorial.erb
51
+ - tap-http.gemspec
53
52
  - tap.yml
53
+ - views/tap/http/capture/footer.erb
54
+ - views/tap/http/capture/header.erb
55
+ - views/tap/http/capture/index.erb
56
+ - views/tap/http/capture/http.erb
57
+ - views/tap/http/capture/redirect.css
58
+ - views/tap/http/capture/redirect.js
59
+ - views/tap/http/capture/tutorial.erb
54
60
  - README
55
61
  - MIT-LICENSE
56
62
  - History
@@ -1,82 +0,0 @@
1
- require 'tap/controller'
2
- require 'tap/http/utils'
3
-
4
- class CaptureController < Tap::Controller
5
- include Tap::Http::Utils
6
-
7
- def index
8
- tutorial
9
- end
10
-
11
- # Brings up a tutorial teaching how to capture and resubmit forms.
12
- def tutorial
13
- render 'tutorial.erb'
14
- end
15
-
16
- # Say is the target of the tutorial.
17
- def say
18
- "<pre>#{request.params['words']}</pre>"
19
- end
20
-
21
- # Echos back redirected HTTP requests as YAML, suitable for use with the
22
- # Tap::Http::Submit task. All HTTP parameters and headers are echoed back
23
- # directly, except for the '__original_action' parameter which is used in
24
- # conjuction with the 'Referer' header to reconstruct the original url of
25
- # the request. The '__original_action' parameter is not echoed.
26
- def http_to_yaml(keep_content='true')
27
- keep_content = keep_content.to_s =~ /true/i
28
- config = parse_request(keep_content)
29
-
30
- original_action = config['params'].delete("__original_action").to_s
31
- referer = config['headers']['Referer'].to_s
32
- config['uri'] = determine_url(original_action, referer)
33
- config['headers']['Host'] = URI.parse(config['uri']).host
34
-
35
- response['Content-Type'] = "text/plain"
36
- response['Content-Disposition'] = "attachment; filename=request.yml;"
37
-
38
- YAML.dump(config)
39
- end
40
-
41
- # Echos back redirected HTTP requests formatted as YAML.
42
- def echo
43
- response['Content-Type'] = "text/plain"
44
- YAML.dump(parse_request)
45
- end
46
-
47
- protected
48
-
49
- # helper to parse the request into a request hash for
50
- # use by a Tap::Http::Submit task
51
- def parse_request(keep_content=true) # :nodoc:
52
-
53
- # perform the actions of Rack::Request::POST, but capturing
54
- # overloaded parameter names
55
- env = request.env
56
- env["rack.request.form_input"] = env["rack.input"]
57
- unless env["rack.request.form_hash"] = Tap::Http::Utils.parse_multipart(env)
58
- env["rack.request.form_vars"] = env["rack.input"].read
59
- env["rack.request.form_hash"] = Rack::Utils.parse_query(env["rack.request.form_vars"])
60
- env["rack.input"].rewind if env["rack.input"].respond_to?(:rewind)
61
- end
62
-
63
- config = {}
64
- parse_rack_request(request, keep_content).each_pair do |key, value|
65
- config[key.to_s] = value
66
- end
67
- config
68
- end
69
-
70
- # helper to determine the url of a redirected action
71
- def determine_url(action, referer) # :nodoc:
72
- base = File.basename(referer)
73
-
74
- case action
75
- when /^https?:/ then action
76
- when /\//
77
- # only use host of page_url
78
- File.join(base, action)
79
- else File.join(base, action)
80
- end
81
- end
82
- end
@@ -1,263 +0,0 @@
1
- require 'tap/http/utils'
2
- require 'net/http'
3
- require 'rack/utils'
4
-
5
- module Tap
6
- module Http
7
-
8
- # Request is a module for submitting HTTP requests. Request take a request
9
- # method, a uri, and an options hash allowing these parameters:
10
- #
11
- # headers:: a hash of headers
12
- # params:: a hash of parameters, values may be arrays when multiple
13
- # values are assigned to a single key
14
- # version: the HTTP version, by default 1.1
15
- # redirection_limit:: the number of redirections allowed, by default 10
16
- #
17
- module Request
18
- module_function
19
-
20
- # Constructs and submits an http request to the uri using the specified
21
- # request method and options (see above). Currently only get and post
22
- # are supported as request methods. A block may be given to receive the
23
- # Net::HTTP and request just prior to submission.
24
- #
25
- # Returns the Net::HTTP response.
26
- def request(request_method, uri, opts={})
27
- uri = "http://#{uri}" unless uri.to_s =~ /^http/
28
- uri = URI.parse(uri) unless uri.kind_of?(URI)
29
- uri.path = "/" if uri.path.empty?
30
-
31
- headers = opts[:headers] || {}
32
- params = opts[:params] || {}
33
-
34
- # construct the request
35
- request = case request_method.to_s
36
- when /^get$/i then construct_get(uri, headers, params)
37
- when /^post$/i then construct_post(uri, headers, params)
38
- else
39
- raise ArgumentError, "unsupported request method: #{request_method}"
40
- end
41
-
42
- # set the http version
43
- version = opts[:version] || 1.1
44
- version_method = "version_#{version.to_s.gsub(".", "_")}".to_sym
45
- if ::Net::HTTP.respond_to?(version_method)
46
- ::Net::HTTP.send(version_method)
47
- else
48
- raise ArgumentError, "unsupported HTTP version: #{version}"
49
- end
50
-
51
- # submit the request
52
- res = ::Net::HTTP.new(uri.host, uri.port).start do |http|
53
- yield(http, request) if block_given?
54
- http.request(request)
55
- end
56
-
57
- # fetch redirections
58
- redirection_limit = opts[:redirection_limit] || 10
59
- redirection_limit ? fetch_redirection(res, redirection_limit) : res
60
- end
61
-
62
- # Shortcut to submit a get request.
63
- def get(uri, opts={})
64
- request(:get, uri, opts)
65
- end
66
-
67
- # Shortcut to submit a post request.
68
- def post(uri, opts={})
69
- request(:post, uri, opts)
70
- end
71
-
72
- def escape(str)
73
- Rack::Utils.escape(str)
74
- end
75
-
76
- # Constructs a Net::HTTP::Post query, setting headers and parameters.
77
- #
78
- # ==== Supported Content Types:
79
- #
80
- # - application/x-www-form-urlencoded (the default)
81
- # - multipart/form-data
82
- #
83
- # The multipart/form-data content type may specify a boundary. If no
84
- # boundary is specified, a randomly generated boundary will be used
85
- # to delimit the parameters.
86
- #
87
- # post = construct_post(
88
- # URI.parse('http://some.url/'),
89
- # {:content_type => 'multipart/form-data; boundary=1234'},
90
- # {:key => 'value'})
91
- #
92
- # post.body
93
- # # => %Q{--1234\r
94
- # # Content-Disposition: form-data; name="key"\r
95
- # # \r
96
- # # value\r
97
- # # --1234--\r
98
- # # }
99
- #
100
- # (Note the carriage returns are required in multipart content)
101
- #
102
- # The content-length header is determined automatically from the
103
- # formatted request body; manually specified content-length headers
104
- # will be overridden.
105
- #
106
- def construct_post(uri, headers, params)
107
- req = ::Net::HTTP::Post.new( "#{uri.path}#{format_query(uri)}" )
108
- headers = headerize_keys(headers)
109
- content_type = headers['Content-Type']
110
-
111
- case content_type
112
- when nil, /^application\/x-www-form-urlencoded$/i
113
- req.body = format_www_form_urlencoded(params)
114
- headers['Content-Type'] ||= "application/x-www-form-urlencoded"
115
- headers['Content-Length'] = req.body.length
116
-
117
- when /^multipart\/form-data(;\s*boundary=(.*))?$/i
118
- # extract the boundary if it exists
119
- boundary = $2 || rand.to_s[2..20]
120
-
121
- req.body = format_multipart_form_data(params, boundary)
122
- headers['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
123
- headers['Content-Length'] = req.body.length
124
-
125
- else
126
- raise ArgumentError, "unsupported Content-Type for POST: #{content_type}"
127
- end
128
-
129
- headers.each_pair {|key, value| req[key] = value }
130
- req
131
- end
132
-
133
- # Constructs a Net::HTTP::Get query. All parameters in uri and params are
134
- # encoded and added to the request URI.
135
- #
136
- # get = construct_get(URI.parse('http://some.url/path'), {}, {:key => 'value'})
137
- # get.path # => "/path?key=value"
138
- #
139
- def construct_get(uri, headers, params)
140
- req = ::Net::HTTP::Get.new( "#{uri.path}#{format_query(uri, params)}" )
141
- headerize_keys(headers).each_pair {|key, value| req[key] = value }
142
- req
143
- end
144
-
145
- # Checks the type of the response; if it is a redirection, fetches the
146
- # redirection. Otherwise return the response.
147
- #
148
- # Notes:
149
- # - Fetch will recurse up to the input redirection limit (default 10)
150
- # - Responses that are not Net::HTTPRedirection or Net::HTTPSuccess
151
- # raise an error.
152
- def fetch_redirection(res, limit=10)
153
- raise 'exceeded the redirection limit' if limit < 1
154
-
155
- case res
156
- when ::Net::HTTPRedirection
157
- redirect = ::Net::HTTP.get_response( URI.parse(res['location']) )
158
- fetch_redirection(redirect, limit - 1)
159
- when ::Net::HTTPSuccess
160
- res
161
- else
162
- raise StandardError, res.error!
163
- end
164
- end
165
-
166
- # Constructs a URI query string from the uri and the input parameters.
167
- # Multiple values for a parameter may be specified using an array.
168
- #
169
- # format_query(URI.parse('http://some.url/path'), {:key => 'value'})
170
- # # => "?key=value"
171
- #
172
- # format_query(URI.parse('http://some.url/path?one=1'), {:two => '2'})
173
- # # => "?one=1&two=2"
174
- #
175
- def format_query(uri, params={})
176
- query = []
177
- query << uri.query if uri.query
178
- params.each_pair do |key, values|
179
- values = [values] unless values.kind_of?(Array)
180
- values.each { |value| query << "#{escape(key)}=#{escape(value)}" }
181
- end
182
- "#{query.empty? ? '' : '?'}#{query.join('&')}"
183
- end
184
-
185
- # Formats params as 'application/x-www-form-urlencoded' for use as the
186
- # body of a post request. Multiple values for a parameter may be
187
- # specified using an array. The result is obviously URI encoded.
188
- #
189
- # format_www_form_urlencoded(:key => 'value with spaces')
190
- # # => "key=value+with+spaces"
191
- #
192
- def format_www_form_urlencoded(params={})
193
- query = []
194
- params.each_pair do |key, values|
195
- values = [values] unless values.kind_of?(Array)
196
- values.each { |value| query << "#{escape(key)}=#{escape(value)}" }
197
- end
198
- query.join('&')
199
- end
200
-
201
- # Formats params as 'multipart/form-data' using the specified boundary,
202
- # for use as the body of a post request. Multiple values for a parameter
203
- # may be specified using an array. All newlines include a carriage
204
- # return for proper formatting.
205
- #
206
- # format_multipart_form_data(:key => 'value')
207
- # # => %Q{--1234567890\r
208
- # # Content-Disposition: form-data; name="key"\r
209
- # # \r
210
- # # value\r
211
- # # --1234567890--\r
212
- # # }
213
- #
214
- # To specify a file, use a hash of file-related headers.
215
- #
216
- # format_multipart_form_data(:key => {
217
- # 'Content-Type' => 'text/plain',
218
- # 'Filename' => "path/to/file.txt"}
219
- # )
220
- # # => %Q{--1234567890\r
221
- # # Content-Disposition: form-data; name="key"; filename="path/to/file.txt"\r
222
- # # Content-Type: text/plain\r
223
- # # \r
224
- # # \r
225
- # # --1234567890--\r
226
- # # }
227
- #
228
- def format_multipart_form_data(params, boundary="1234567890")
229
- body = []
230
- params.each_pair do |key, values|
231
- values = [values] unless values.kind_of?(Array)
232
-
233
- values.each do |value|
234
- body << case value
235
- when Hash
236
- hash = headerize_keys(value)
237
- filename = hash.delete('Filename')
238
- content = hash.delete('Content') || (File.exists?(filename) ? File.read(filename) : "")
239
-
240
- header = "Content-Disposition: form-data; name=\"#{key.to_s}\"; filename=\"#{filename}\"\r\n"
241
- hash.each_pair { |key, value| header << "#{key}: #{value}\r\n" }
242
- "#{header}\r\n#{content}\r\n"
243
- else
244
- %Q{Content-Disposition: form-data; name="#{key.to_s}"\r\n\r\n#{value.to_s}\r\n}
245
- end
246
- end
247
- end
248
-
249
- body.collect {|p| "--#{boundary}\r\n#{p}" }.join('') + "--#{boundary}--\r\n"
250
- end
251
-
252
- # Helper to headerize the keys of a hash to headers.
253
- # See Utils#headerize.
254
- def headerize_keys(hash)
255
- result = {}
256
- hash.each_pair do |key, value|
257
- result[Utils.headerize(key)] = value
258
- end
259
- result
260
- end
261
- end
262
- end
263
- end