tap-http 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History 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