diamond-mechanize 2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. data/CHANGELOG.rdoc +718 -0
  2. data/EXAMPLES.rdoc +187 -0
  3. data/FAQ.rdoc +11 -0
  4. data/GUIDE.rdoc +163 -0
  5. data/LICENSE.rdoc +20 -0
  6. data/Manifest.txt +159 -0
  7. data/README.rdoc +64 -0
  8. data/Rakefile +49 -0
  9. data/lib/mechanize.rb +1079 -0
  10. data/lib/mechanize/content_type_error.rb +13 -0
  11. data/lib/mechanize/cookie.rb +232 -0
  12. data/lib/mechanize/cookie_jar.rb +194 -0
  13. data/lib/mechanize/download.rb +59 -0
  14. data/lib/mechanize/element_matcher.rb +36 -0
  15. data/lib/mechanize/file.rb +65 -0
  16. data/lib/mechanize/file_connection.rb +17 -0
  17. data/lib/mechanize/file_request.rb +26 -0
  18. data/lib/mechanize/file_response.rb +74 -0
  19. data/lib/mechanize/file_saver.rb +39 -0
  20. data/lib/mechanize/form.rb +543 -0
  21. data/lib/mechanize/form/button.rb +6 -0
  22. data/lib/mechanize/form/check_box.rb +12 -0
  23. data/lib/mechanize/form/field.rb +54 -0
  24. data/lib/mechanize/form/file_upload.rb +21 -0
  25. data/lib/mechanize/form/hidden.rb +3 -0
  26. data/lib/mechanize/form/image_button.rb +19 -0
  27. data/lib/mechanize/form/keygen.rb +34 -0
  28. data/lib/mechanize/form/multi_select_list.rb +94 -0
  29. data/lib/mechanize/form/option.rb +50 -0
  30. data/lib/mechanize/form/radio_button.rb +55 -0
  31. data/lib/mechanize/form/reset.rb +3 -0
  32. data/lib/mechanize/form/select_list.rb +44 -0
  33. data/lib/mechanize/form/submit.rb +3 -0
  34. data/lib/mechanize/form/text.rb +3 -0
  35. data/lib/mechanize/form/textarea.rb +3 -0
  36. data/lib/mechanize/headers.rb +23 -0
  37. data/lib/mechanize/history.rb +82 -0
  38. data/lib/mechanize/http.rb +8 -0
  39. data/lib/mechanize/http/agent.rb +1004 -0
  40. data/lib/mechanize/http/auth_challenge.rb +59 -0
  41. data/lib/mechanize/http/auth_realm.rb +31 -0
  42. data/lib/mechanize/http/content_disposition_parser.rb +188 -0
  43. data/lib/mechanize/http/www_authenticate_parser.rb +155 -0
  44. data/lib/mechanize/monkey_patch.rb +16 -0
  45. data/lib/mechanize/page.rb +440 -0
  46. data/lib/mechanize/page/base.rb +7 -0
  47. data/lib/mechanize/page/frame.rb +27 -0
  48. data/lib/mechanize/page/image.rb +30 -0
  49. data/lib/mechanize/page/label.rb +20 -0
  50. data/lib/mechanize/page/link.rb +98 -0
  51. data/lib/mechanize/page/meta_refresh.rb +68 -0
  52. data/lib/mechanize/parser.rb +173 -0
  53. data/lib/mechanize/pluggable_parsers.rb +144 -0
  54. data/lib/mechanize/redirect_limit_reached_error.rb +19 -0
  55. data/lib/mechanize/redirect_not_get_or_head_error.rb +21 -0
  56. data/lib/mechanize/response_code_error.rb +21 -0
  57. data/lib/mechanize/response_read_error.rb +27 -0
  58. data/lib/mechanize/robots_disallowed_error.rb +28 -0
  59. data/lib/mechanize/test_case.rb +663 -0
  60. data/lib/mechanize/unauthorized_error.rb +3 -0
  61. data/lib/mechanize/unsupported_scheme_error.rb +6 -0
  62. data/lib/mechanize/util.rb +101 -0
  63. data/test/data/htpasswd +1 -0
  64. data/test/data/server.crt +16 -0
  65. data/test/data/server.csr +12 -0
  66. data/test/data/server.key +15 -0
  67. data/test/data/server.pem +15 -0
  68. data/test/htdocs/alt_text.html +10 -0
  69. data/test/htdocs/bad_form_test.html +9 -0
  70. data/test/htdocs/button.jpg +0 -0
  71. data/test/htdocs/canonical_uri.html +9 -0
  72. data/test/htdocs/dir with spaces/foo.html +1 -0
  73. data/test/htdocs/empty_form.html +6 -0
  74. data/test/htdocs/file_upload.html +26 -0
  75. data/test/htdocs/find_link.html +41 -0
  76. data/test/htdocs/form_multi_select.html +16 -0
  77. data/test/htdocs/form_multival.html +37 -0
  78. data/test/htdocs/form_no_action.html +18 -0
  79. data/test/htdocs/form_no_input_name.html +16 -0
  80. data/test/htdocs/form_order_test.html +11 -0
  81. data/test/htdocs/form_select.html +16 -0
  82. data/test/htdocs/form_set_fields.html +14 -0
  83. data/test/htdocs/form_test.html +188 -0
  84. data/test/htdocs/frame_referer_test.html +10 -0
  85. data/test/htdocs/frame_test.html +30 -0
  86. data/test/htdocs/google.html +13 -0
  87. data/test/htdocs/index.html +6 -0
  88. data/test/htdocs/link with space.html +5 -0
  89. data/test/htdocs/meta_cookie.html +11 -0
  90. data/test/htdocs/no_title_test.html +6 -0
  91. data/test/htdocs/noindex.html +9 -0
  92. data/test/htdocs/rails_3_encoding_hack_form_test.html +27 -0
  93. data/test/htdocs/relative/tc_relative_links.html +21 -0
  94. data/test/htdocs/robots.html +8 -0
  95. data/test/htdocs/robots.txt +2 -0
  96. data/test/htdocs/tc_bad_charset.html +9 -0
  97. data/test/htdocs/tc_bad_links.html +5 -0
  98. data/test/htdocs/tc_base_link.html +8 -0
  99. data/test/htdocs/tc_blank_form.html +11 -0
  100. data/test/htdocs/tc_charset.html +6 -0
  101. data/test/htdocs/tc_checkboxes.html +19 -0
  102. data/test/htdocs/tc_encoded_links.html +5 -0
  103. data/test/htdocs/tc_field_precedence.html +11 -0
  104. data/test/htdocs/tc_follow_meta.html +8 -0
  105. data/test/htdocs/tc_form_action.html +48 -0
  106. data/test/htdocs/tc_links.html +19 -0
  107. data/test/htdocs/tc_meta_in_body.html +9 -0
  108. data/test/htdocs/tc_pretty_print.html +17 -0
  109. data/test/htdocs/tc_referer.html +16 -0
  110. data/test/htdocs/tc_relative_links.html +19 -0
  111. data/test/htdocs/tc_textarea.html +23 -0
  112. data/test/htdocs/test_click.html +11 -0
  113. data/test/htdocs/unusual______.html +5 -0
  114. data/test/test_mechanize.rb +1164 -0
  115. data/test/test_mechanize_cookie.rb +451 -0
  116. data/test/test_mechanize_cookie_jar.rb +483 -0
  117. data/test/test_mechanize_download.rb +43 -0
  118. data/test/test_mechanize_file.rb +61 -0
  119. data/test/test_mechanize_file_connection.rb +21 -0
  120. data/test/test_mechanize_file_request.rb +19 -0
  121. data/test/test_mechanize_file_saver.rb +21 -0
  122. data/test/test_mechanize_form.rb +875 -0
  123. data/test/test_mechanize_form_check_box.rb +38 -0
  124. data/test/test_mechanize_form_encoding.rb +114 -0
  125. data/test/test_mechanize_form_field.rb +63 -0
  126. data/test/test_mechanize_form_file_upload.rb +20 -0
  127. data/test/test_mechanize_form_image_button.rb +12 -0
  128. data/test/test_mechanize_form_keygen.rb +32 -0
  129. data/test/test_mechanize_form_multi_select_list.rb +84 -0
  130. data/test/test_mechanize_form_option.rb +55 -0
  131. data/test/test_mechanize_form_radio_button.rb +78 -0
  132. data/test/test_mechanize_form_select_list.rb +76 -0
  133. data/test/test_mechanize_form_textarea.rb +52 -0
  134. data/test/test_mechanize_headers.rb +35 -0
  135. data/test/test_mechanize_history.rb +103 -0
  136. data/test/test_mechanize_http_agent.rb +1225 -0
  137. data/test/test_mechanize_http_auth_challenge.rb +39 -0
  138. data/test/test_mechanize_http_auth_realm.rb +49 -0
  139. data/test/test_mechanize_http_content_disposition_parser.rb +118 -0
  140. data/test/test_mechanize_http_www_authenticate_parser.rb +146 -0
  141. data/test/test_mechanize_link.rb +80 -0
  142. data/test/test_mechanize_page.rb +118 -0
  143. data/test/test_mechanize_page_encoding.rb +182 -0
  144. data/test/test_mechanize_page_frame.rb +16 -0
  145. data/test/test_mechanize_page_link.rb +390 -0
  146. data/test/test_mechanize_page_meta_refresh.rb +127 -0
  147. data/test/test_mechanize_parser.rb +289 -0
  148. data/test/test_mechanize_pluggable_parser.rb +52 -0
  149. data/test/test_mechanize_redirect_limit_reached_error.rb +24 -0
  150. data/test/test_mechanize_redirect_not_get_or_head_error.rb +14 -0
  151. data/test/test_mechanize_subclass.rb +22 -0
  152. data/test/test_mechanize_util.rb +103 -0
  153. data/test/test_multi_select.rb +119 -0
  154. metadata +216 -0
@@ -0,0 +1,19 @@
1
+ ##
2
+ # Raised when too many redirects are sent
3
+
4
+ class Mechanize::RedirectLimitReachedError < Mechanize::Error
5
+
6
+ attr_reader :page
7
+ attr_reader :redirects
8
+ attr_reader :response_code
9
+
10
+ def initialize page, redirects
11
+ @page = page
12
+ @redirects = redirects
13
+ @response_code = page.code
14
+
15
+ super "Redirect limit of #{redirects} reached"
16
+ end
17
+
18
+ end
19
+
@@ -0,0 +1,21 @@
1
+ ##
2
+ # Raised when a POST, PUT, or DELETE request results in a redirect
3
+ # see RFC 2616 10.3.2, 10.3.3 http://www.ietf.org/rfc/rfc2616.txt
4
+
5
+ class Mechanize::RedirectNotGetOrHeadError < Mechanize::Error
6
+ attr_reader :page, :response_code, :verb, :uri
7
+ def initialize(page, verb)
8
+ @page = page
9
+ @verb = verb
10
+ @uri = page.uri
11
+ @response_code = page.code
12
+ end
13
+
14
+ def to_s
15
+ method = @verb.to_s.upcase
16
+ "#{@response_code} redirect received after a #{method} request"
17
+ end
18
+
19
+ alias :inspect :to_s
20
+ end
21
+
@@ -0,0 +1,21 @@
1
+ # This error is raised when Mechanize encounters a response code it does not
2
+ # know how to handle. Currently, this exception will be thrown if Mechanize
3
+ # encounters response codes other than 200, 301, or 302. Any other response
4
+ # code is up to the user to handle.
5
+
6
+ class Mechanize::ResponseCodeError < Mechanize::Error
7
+ attr_reader :response_code
8
+ attr_reader :page
9
+
10
+ def initialize(page)
11
+ @page = page
12
+ @response_code = page.code.to_s
13
+ end
14
+
15
+ def to_s
16
+ "#{@response_code} => #{Net::HTTPResponse::CODE_TO_OBJ[@response_code]}"
17
+ end
18
+
19
+ alias inspect to_s
20
+ end
21
+
@@ -0,0 +1,27 @@
1
+ ##
2
+ # Raised when Mechanize encounters an error while reading the response body
3
+ # from the server. Contains the response headers and the response body up to
4
+ # the error along with the initial error.
5
+
6
+ class Mechanize::ResponseReadError < Mechanize::Error
7
+
8
+ attr_reader :body_io
9
+ attr_reader :error
10
+ attr_reader :response
11
+
12
+ ##
13
+ # Creates a new ResponseReadError with the +error+ raised, the +response+
14
+ # and the +body_io+ for content read so far.
15
+
16
+ def initialize error, response, body_io
17
+ @error = error
18
+ @response = response
19
+ @body_io = body_io
20
+ end
21
+
22
+ def message # :nodoc:
23
+ "#{@error.message} (#{self.class})"
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,28 @@
1
+ # Exception that is raised when an access to a resource is disallowed by
2
+ # robots.txt or by HTML document itself.
3
+
4
+ class Mechanize::RobotsDisallowedError < Mechanize::Error
5
+ def initialize(url)
6
+ if url.is_a?(URI)
7
+ @url = url.to_s
8
+ @uri = url
9
+ else
10
+ @url = url.to_s
11
+ end
12
+ end
13
+
14
+ # Returns the URL (string) of the resource that caused this error.
15
+ attr_reader :url
16
+
17
+ # Returns the URL (URI object) of the resource that caused this
18
+ # error. URI::InvalidURIError may be raised if the URL happens to
19
+ # be invalid or not understood by the URI library.
20
+ def uri
21
+ @uri ||= URI.parse(url)
22
+ end
23
+
24
+ def to_s
25
+ "Robots access is disallowed for URL: #{url}"
26
+ end
27
+ alias :inspect :to_s
28
+ end
@@ -0,0 +1,663 @@
1
+ require 'mechanize'
2
+ require 'logger'
3
+ require 'tempfile'
4
+ require 'tmpdir'
5
+ require 'webrick'
6
+ require 'zlib'
7
+
8
+ require 'rubygems'
9
+
10
+ begin
11
+ gem 'minitest'
12
+ rescue Gem::LoadError
13
+ end
14
+
15
+ require 'minitest/autorun'
16
+
17
+ class Mechanize::TestCase < MiniTest::Unit::TestCase
18
+
19
+ TEST_DIR = File.expand_path '../../../test', __FILE__
20
+ REQUESTS = []
21
+
22
+ def setup
23
+ super
24
+
25
+ REQUESTS.clear
26
+ @mech = Mechanize.new
27
+ @ssl_private_key = nil
28
+ @ssl_certificate = nil
29
+ end
30
+
31
+ def fake_page agent = @mech
32
+ uri = URI 'http://fake.example/'
33
+ html = <<-END
34
+ <html>
35
+ <body>
36
+ <form><input type="submit" value="submit" /></form>
37
+ </body>
38
+ </html>
39
+ END
40
+
41
+ response = { 'content-type' => 'text/html' }
42
+
43
+ Mechanize::Page.new uri, response, html, 200, agent
44
+ end
45
+
46
+ def have_encoding?
47
+ Object.const_defined? :Encoding
48
+ end
49
+
50
+ def html_page body
51
+ uri = URI 'http://example/'
52
+ Mechanize::Page.new uri, { 'content-type' => 'text/html' }, body, 200, @mech
53
+ end
54
+
55
+ def in_tmpdir
56
+ Dir.mktmpdir do |dir|
57
+ Dir.chdir dir do
58
+ yield
59
+ end
60
+ end
61
+ end
62
+
63
+ def node element, attributes = {}
64
+ doc = Nokogiri::HTML::Document.new
65
+
66
+ node = Nokogiri::XML::Node.new element, doc
67
+
68
+ attributes.each do |name, value|
69
+ node[name] = value
70
+ end
71
+
72
+ node
73
+ end
74
+
75
+ def page uri, content_type = 'text/html', body = '', code = 200
76
+ uri = URI uri unless URI::Generic === uri
77
+
78
+ Mechanize::Page.new(uri, { 'content-type' => content_type }, body, code,
79
+ @mech)
80
+ end
81
+
82
+ def requests
83
+ REQUESTS
84
+ end
85
+
86
+ def ssl_private_key
87
+ @ssl_private_key ||= OpenSSL::PKey::RSA.new <<-KEY
88
+ -----BEGIN RSA PRIVATE KEY-----
89
+ MIG7AgEAAkEA8pmEfmP0Ibir91x6pbts4JmmsVZd3xvD5p347EFvBCbhBW1nv1Gs
90
+ bCBEFlSiT1q2qvxGb5IlbrfdhdgyqdTXUQIBAQIBAQIhAPumXslvf6YasXa1hni3
91
+ p80joKOug2UUgqOLD2GUSO//AiEA9ssY6AFxjHWuwo/+/rkLmkfO2s1Lz3OeUEWq
92
+ 6DiHOK8CAQECAQECIQDt8bc4vS6wh9VXApNSKIpVygtxSFe/IwLeX26n77j6Qg==
93
+ -----END RSA PRIVATE KEY-----
94
+ KEY
95
+ end
96
+
97
+ def ssl_certificate
98
+ @ssl_certificate ||= OpenSSL::X509::Certificate.new <<-CERT
99
+ -----BEGIN CERTIFICATE-----
100
+ MIIBQjCB7aADAgECAgEAMA0GCSqGSIb3DQEBBQUAMCoxDzANBgNVBAMMBm5vYm9k
101
+ eTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUwIBcNMTExMTAzMjEwODU5WhgPOTk5
102
+ OTEyMzExMjU5NTlaMCoxDzANBgNVBAMMBm5vYm9keTEXMBUGCgmSJomT8ixkARkW
103
+ B2V4YW1wbGUwWjANBgkqhkiG9w0BAQEFAANJADBGAkEA8pmEfmP0Ibir91x6pbts
104
+ 4JmmsVZd3xvD5p347EFvBCbhBW1nv1GsbCBEFlSiT1q2qvxGb5IlbrfdhdgyqdTX
105
+ UQIBATANBgkqhkiG9w0BAQUFAANBAAAB////////////////////////////////
106
+ //8AMCEwCQYFKw4DAhoFAAQUePiv+QrJxyjtEJNnH5pB9OTWIqA=
107
+ -----END CERTIFICATE-----
108
+ CERT
109
+ end
110
+
111
+ end
112
+
113
+ class BasicAuthServlet < WEBrick::HTTPServlet::AbstractServlet
114
+ def do_GET(req,res)
115
+ htpd = WEBrick::HTTPAuth::Htpasswd.new('dot.htpasswd')
116
+ htpd.set_passwd('Blah', 'user', 'pass')
117
+ authenticator = WEBrick::HTTPAuth::BasicAuth.new({
118
+ :UserDB => htpd,
119
+ :Realm => 'Blah',
120
+ :Logger => Logger.new(nil)
121
+ }
122
+ )
123
+ begin
124
+ authenticator.authenticate(req,res)
125
+ res.body = 'You are authenticated'
126
+ rescue WEBrick::HTTPStatus::Unauthorized
127
+ res.status = 401
128
+ end
129
+ FileUtils.rm('dot.htpasswd')
130
+ end
131
+ alias :do_POST :do_GET
132
+ end
133
+
134
+ class ContentTypeServlet < WEBrick::HTTPServlet::AbstractServlet
135
+ def do_GET(req, res)
136
+ ct = req.query['ct'] || "text/html; charset=utf-8"
137
+ res['Content-Type'] = ct
138
+ res.body = "Hello World"
139
+ end
140
+ end
141
+
142
+ class DigestAuthServlet < WEBrick::HTTPServlet::AbstractServlet
143
+ htpd = WEBrick::HTTPAuth::Htdigest.new('digest.htpasswd')
144
+ htpd.set_passwd('Blah', 'user', 'pass')
145
+ @@authenticator = WEBrick::HTTPAuth::DigestAuth.new({
146
+ :UserDB => htpd,
147
+ :Realm => 'Blah',
148
+ :Algorithm => 'MD5',
149
+ :Logger => Logger.new(nil)
150
+ }
151
+ )
152
+ def do_GET(req,res)
153
+ def req.request_time; Time.now; end
154
+ def req.request_uri; '/digest_auth'; end
155
+ def req.request_method; "GET"; end
156
+
157
+ begin
158
+ @@authenticator.authenticate(req,res)
159
+ res.body = 'You are authenticated'
160
+ rescue WEBrick::HTTPStatus::Unauthorized
161
+ res.status = 401
162
+ end
163
+ FileUtils.rm('digest.htpasswd') if File.exists?('digest.htpasswd')
164
+ end
165
+ alias :do_POST :do_GET
166
+ end
167
+
168
+ class FileUploadServlet < WEBrick::HTTPServlet::AbstractServlet
169
+ def do_POST(req, res)
170
+ res.body = req.body
171
+ end
172
+ end
173
+
174
+ class FormServlet < WEBrick::HTTPServlet::AbstractServlet
175
+ def do_GET(req, res)
176
+ res.body = "<HTML><body>"
177
+ req.query.each_key { |k|
178
+ req.query[k].each_data { |data|
179
+ res.body << "<a href=\"#\">#{WEBrick::HTTPUtils.unescape(k)}:#{WEBrick::HTTPUtils.unescape(data)}</a><br />"
180
+ }
181
+ }
182
+ res.body << "<div id=\"query\">#{res.query}</div></body></HTML>"
183
+ res['Content-Type'] = "text/html"
184
+ end
185
+
186
+ def do_POST(req, res)
187
+ res.body = "<HTML><body>"
188
+
189
+ req.query.each_key { |k|
190
+ req.query[k].each_data { |data|
191
+ res.body << "<a href=\"#\">#{k}:#{data}</a><br />"
192
+ }
193
+ }
194
+
195
+ res.body << "<div id=\"query\">#{req.body}</div></body></HTML>"
196
+ res['Content-Type'] = "text/html"
197
+ end
198
+ end
199
+
200
+ class GzipServlet < WEBrick::HTTPServlet::AbstractServlet
201
+ def do_GET(req, res)
202
+ if req['Accept-Encoding'] =~ /gzip/
203
+ if name = req.query['file'] then
204
+ open("#{Mechanize::TestCase::TEST_DIR}/htdocs/#{name}", 'r') do |io|
205
+ string = ""
206
+ zipped = StringIO.new string, 'w'
207
+ Zlib::GzipWriter.wrap zipped do |gz|
208
+ gz.write io.read
209
+ end
210
+ res.body = string
211
+ end
212
+ else
213
+ res.body = ''
214
+ end
215
+ res['Content-Encoding'] = req['X-ResponseContentEncoding'] || 'gzip'
216
+ res['Content-Type'] = "text/html"
217
+ else
218
+ res.code = 400
219
+ res.body = 'no gzip'
220
+ end
221
+ end
222
+ end
223
+
224
+ class HeaderServlet < WEBrick::HTTPServlet::AbstractServlet
225
+ def do_GET(req, res)
226
+ res['Content-Type'] = "text/html"
227
+
228
+ req.query.each do |x,y|
229
+ res[x] = y
230
+ end
231
+
232
+ body = ''
233
+ req.each_header do |k,v|
234
+ body << "#{k}|#{v}\n"
235
+ end
236
+ res.body = body
237
+ end
238
+ end
239
+
240
+ class HttpRefreshServlet < WEBrick::HTTPServlet::AbstractServlet
241
+ def do_GET(req, res)
242
+ res['Content-Type'] = req.query['ct'] || "text/html"
243
+ refresh_time = req.query['refresh_time'] || 0
244
+ refresh_url = req.query['refresh_url'] || '/index.html'
245
+ res['Refresh'] = " #{refresh_time};url=#{refresh_url}\r\n";
246
+ end
247
+ end
248
+
249
+ class InfiniteRedirectServlet < WEBrick::HTTPServlet::AbstractServlet
250
+ def do_GET(req, res)
251
+ res['Content-Type'] = req.query['ct'] || "text/html"
252
+ res.status = req.query['code'] ? req.query['code'].to_i : '302'
253
+ number = req.query['q'] ? req.query['q'].to_i : 0
254
+ res['Location'] = "/infinite_redirect?q=#{number + 1}"
255
+ end
256
+ alias :do_POST :do_GET
257
+ end
258
+
259
+ class InfiniteRefreshServlet < WEBrick::HTTPServlet::AbstractServlet
260
+ def do_GET(req, res)
261
+ res['Content-Type'] = req.query['ct'] || "text/html"
262
+ res.status = req.query['code'] ? req.query['code'].to_i : '302'
263
+ number = req.query['q'] ? req.query['q'].to_i : 0
264
+ res['Refresh'] = " 0;url=http://localhost/infinite_refresh?q=#{number + 1}\r\n";
265
+ end
266
+ end
267
+
268
+ class ManyCookiesAsStringServlet < WEBrick::HTTPServlet::AbstractServlet
269
+ def do_GET(req, res)
270
+ cookies = []
271
+ name_cookie = WEBrick::Cookie.new("name", "Aaron")
272
+ name_cookie.path = "/"
273
+ name_cookie.expires = Time.now + 86400
274
+ name_cookie.domain = 'localhost'
275
+ cookies << name_cookie
276
+ cookies << name_cookie
277
+ cookies << name_cookie
278
+ cookies << "#{name_cookie}; HttpOnly"
279
+
280
+ expired_cookie = WEBrick::Cookie.new("expired", "doh")
281
+ expired_cookie.path = "/"
282
+ expired_cookie.expires = Time.now - 86400
283
+ cookies << expired_cookie
284
+
285
+ different_path_cookie = WEBrick::Cookie.new("a_path", "some_path")
286
+ different_path_cookie.path = "/some_path"
287
+ different_path_cookie.expires = Time.now + 86400
288
+ cookies << different_path_cookie
289
+
290
+ no_path_cookie = WEBrick::Cookie.new("no_path", "no_path")
291
+ no_path_cookie.expires = Time.now + 86400
292
+ cookies << no_path_cookie
293
+
294
+ no_exp_path_cookie = WEBrick::Cookie.new("no_expires", "nope")
295
+ no_exp_path_cookie.path = "/"
296
+ cookies << no_exp_path_cookie
297
+
298
+ res['Set-Cookie'] = cookies.join(', ')
299
+
300
+ res['Content-Type'] = "text/html"
301
+ res.body = "<html><body>hello</body></html>"
302
+ end
303
+ end
304
+
305
+ class ManyCookiesServlet < WEBrick::HTTPServlet::AbstractServlet
306
+ def do_GET(req, res)
307
+ name_cookie = WEBrick::Cookie.new("name", "Aaron")
308
+ name_cookie.path = "/"
309
+ name_cookie.expires = Time.now + 86400
310
+ res.cookies << name_cookie
311
+ res.cookies << name_cookie
312
+ res.cookies << name_cookie
313
+ res.cookies << name_cookie
314
+
315
+ expired_cookie = WEBrick::Cookie.new("expired", "doh")
316
+ expired_cookie.path = "/"
317
+ expired_cookie.expires = Time.now - 86400
318
+ res.cookies << expired_cookie
319
+
320
+ different_path_cookie = WEBrick::Cookie.new("a_path", "some_path")
321
+ different_path_cookie.path = "/some_path"
322
+ different_path_cookie.expires = Time.now + 86400
323
+ res.cookies << different_path_cookie
324
+
325
+ no_path_cookie = WEBrick::Cookie.new("no_path", "no_path")
326
+ no_path_cookie.expires = Time.now + 86400
327
+ res.cookies << no_path_cookie
328
+
329
+ no_exp_path_cookie = WEBrick::Cookie.new("no_expires", "nope")
330
+ no_exp_path_cookie.path = "/"
331
+ res.cookies << no_exp_path_cookie
332
+
333
+ res['Content-Type'] = "text/html"
334
+ res.body = "<html><body>hello</body></html>"
335
+ end
336
+ end
337
+
338
+ class ModifiedSinceServlet < WEBrick::HTTPServlet::AbstractServlet
339
+ def do_GET(req, res)
340
+ s_time = 'Fri, 04 May 2001 00:00:38 GMT'
341
+
342
+ my_time = Time.parse(s_time)
343
+
344
+ if req['If-Modified-Since']
345
+ your_time = Time.parse(req['If-Modified-Since'])
346
+ if my_time > your_time
347
+ res.body = 'This page was updated since you requested'
348
+ else
349
+ res.status = 304
350
+ end
351
+ else
352
+ res.body = 'You did not send an If-Modified-Since header'
353
+ end
354
+
355
+ res['Last-Modified'] = s_time
356
+ end
357
+ end
358
+
359
+ class NTLMServlet < WEBrick::HTTPServlet::AbstractServlet
360
+
361
+ def do_GET(req, res)
362
+ if req['Authorization'] =~ /^NTLM (.*)/ then
363
+ authorization = $1.unpack('m*').first
364
+
365
+ if authorization =~ /^NTLMSSP\000\001/ then
366
+ type_2 = 'TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mr' \
367
+ 'ze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4A' \
368
+ 'AgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUA' \
369
+ 'UgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIA' \
370
+ 'cwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMA' \
371
+ 'bwBtAAAAAAA='
372
+
373
+ res['WWW-Authenticate'] = "NTLM #{type_2}"
374
+ res.status = 401
375
+ elsif authorization =~ /^NTLMSSP\000\003/ then
376
+ res.body = 'ok'
377
+ else
378
+ res['WWW-Authenticate'] = 'NTLM'
379
+ res.status = 401
380
+ end
381
+ else
382
+ res['WWW-Authenticate'] = 'NTLM'
383
+ res.status = 401
384
+ end
385
+ end
386
+
387
+ end
388
+
389
+ class OneCookieNoSpacesServlet < WEBrick::HTTPServlet::AbstractServlet
390
+ def do_GET(req, res)
391
+ cookie = WEBrick::Cookie.new("foo", "bar")
392
+ cookie.path = "/"
393
+ cookie.expires = Time.now + 86400
394
+ res.cookies << cookie.to_s.gsub(/; /, ';')
395
+ res['Content-Type'] = "text/html"
396
+ res.body = "<html><body>hello</body></html>"
397
+ end
398
+ end
399
+
400
+ class OneCookieServlet < WEBrick::HTTPServlet::AbstractServlet
401
+ def do_GET(req, res)
402
+ cookie = WEBrick::Cookie.new("foo", "bar")
403
+ cookie.path = "/"
404
+ cookie.expires = Time.now + 86400
405
+ res.cookies << cookie
406
+ res['Content-Type'] = "text/html"
407
+ res.body = "<html><body>hello</body></html>"
408
+ end
409
+ end
410
+
411
+ class QuotedValueCookieServlet < WEBrick::HTTPServlet::AbstractServlet
412
+ def do_GET(req, res)
413
+ cookie = WEBrick::Cookie.new("quoted", "\"value\"")
414
+ cookie.path = "/"
415
+ cookie.expires = Time.now + 86400
416
+ res.cookies << cookie
417
+ res['Content-Type'] = "text/html"
418
+ res.body = "<html><body>hello</body></html>"
419
+ end
420
+ end
421
+
422
+ class RedirectServlet < WEBrick::HTTPServlet::AbstractServlet
423
+ def do_GET(req, res)
424
+ res['Content-Type'] = req.query['ct'] || "text/html"
425
+ res.status = req.query['code'] ? req.query['code'].to_i : '302'
426
+ res['Location'] = "/verb"
427
+ end
428
+
429
+ alias :do_POST :do_GET
430
+ alias :do_HEAD :do_GET
431
+ alias :do_PUT :do_GET
432
+ alias :do_DELETE :do_GET
433
+ end
434
+
435
+ class RefererServlet < WEBrick::HTTPServlet::AbstractServlet
436
+ def do_GET(req, res)
437
+ res['Content-Type'] = "text/html"
438
+ res.body = req['Referer'] || ''
439
+ end
440
+
441
+ def do_POST(req, res)
442
+ res['Content-Type'] = "text/html"
443
+ res.body = req['Referer'] || ''
444
+ end
445
+ end
446
+
447
+ class RefreshWithoutUrl < WEBrick::HTTPServlet::AbstractServlet
448
+ @@count = 0
449
+ def do_GET(req, res)
450
+ res['Content-Type'] = "text/html"
451
+ @@count += 1
452
+ if @@count > 1
453
+ res['Refresh'] = "0; url=http://localhost/index.html";
454
+ else
455
+ res['Refresh'] = "0";
456
+ end
457
+ end
458
+ end
459
+
460
+ class RefreshWithEmptyUrl < WEBrick::HTTPServlet::AbstractServlet
461
+ @@count = 0
462
+ def do_GET(req, res)
463
+ res['Content-Type'] = "text/html"
464
+ @@count += 1
465
+ if @@count > 1
466
+ res['Refresh'] = "0; url=http://localhost/index.html";
467
+ else
468
+ res['Refresh'] = "0; url=";
469
+ end
470
+ end
471
+ end
472
+
473
+ class ResponseCodeServlet < WEBrick::HTTPServlet::AbstractServlet
474
+ def do_GET(req, res)
475
+ res['Content-Type'] = req.query['ct'] || "text/html"
476
+ if req.query['code']
477
+ code = req.query['code'].to_i
478
+ case code
479
+ when 300, 301, 302, 303, 304, 305, 307
480
+ res['Location'] = "/index.html"
481
+ end
482
+ res.status = code
483
+ else
484
+ end
485
+ end
486
+ end
487
+
488
+ class SendCookiesServlet < WEBrick::HTTPServlet::AbstractServlet
489
+ def do_GET(req, res)
490
+ res['Content-Type'] = "text/html"
491
+ res.body = "<html><body>"
492
+ req.cookies.each { |c|
493
+ res.body << "<a href=\"#\">#{c.name}:#{c.value}</a>"
494
+ }
495
+ res.body << "</body></html>"
496
+ end
497
+ end
498
+
499
+ class VerbServlet < WEBrick::HTTPServlet::AbstractServlet
500
+ %w(HEAD GET POST PUT DELETE).each do |verb|
501
+ eval(<<-eomethod)
502
+ def do_#{verb}(req, res)
503
+ res.header['X-Request-Method'] = #{verb.dump}
504
+ end
505
+ eomethod
506
+ end
507
+ end
508
+
509
+ class Net::HTTP
510
+ alias :old_do_start :do_start
511
+
512
+ def do_start
513
+ @started = true
514
+ end
515
+
516
+ SERVLETS = {
517
+ '/gzip' => GzipServlet,
518
+ '/form_post' => FormServlet,
519
+ '/basic_auth' => BasicAuthServlet,
520
+ '/form post' => FormServlet,
521
+ '/response_code' => ResponseCodeServlet,
522
+ '/http_refresh' => HttpRefreshServlet,
523
+ '/content_type_test' => ContentTypeServlet,
524
+ '/referer' => RefererServlet,
525
+ '/file_upload' => FileUploadServlet,
526
+ '/one_cookie' => OneCookieServlet,
527
+ '/one_cookie_no_space' => OneCookieNoSpacesServlet,
528
+ '/many_cookies' => ManyCookiesServlet,
529
+ '/many_cookies_as_string' => ManyCookiesAsStringServlet,
530
+ '/ntlm' => NTLMServlet,
531
+ '/send_cookies' => SendCookiesServlet,
532
+ '/quoted_value_cookie' => QuotedValueCookieServlet,
533
+ '/if_modified_since' => ModifiedSinceServlet,
534
+ '/http_headers' => HeaderServlet,
535
+ '/infinite_redirect' => InfiniteRedirectServlet,
536
+ '/infinite_refresh' => InfiniteRefreshServlet,
537
+ '/redirect' => RedirectServlet,
538
+ '/refresh_without_url' => RefreshWithoutUrl,
539
+ '/refresh_with_empty_url' => RefreshWithEmptyUrl,
540
+ '/digest_auth' => DigestAuthServlet,
541
+ '/verb' => VerbServlet,
542
+ }
543
+
544
+ PAGE_CACHE = {}
545
+
546
+ alias :old_request :request
547
+
548
+ def request(req, *data, &block)
549
+ url = URI.parse(req.path)
550
+ path = WEBrick::HTTPUtils.unescape(url.path)
551
+
552
+ path = '/index.html' if path == '/'
553
+
554
+ res = ::Response.new
555
+ res.query_params = url.query
556
+
557
+ req.query = if 'POST' != req.method && url.query then
558
+ WEBrick::HTTPUtils.parse_query url.query
559
+ elsif req['content-type'] =~ /www-form-urlencoded/ then
560
+ WEBrick::HTTPUtils.parse_query req.body
561
+ elsif req['content-type'] =~ /boundary=(.+)/ then
562
+ boundary = WEBrick::HTTPUtils.dequote $1
563
+ WEBrick::HTTPUtils.parse_form_data req.body, boundary
564
+ else
565
+ {}
566
+ end
567
+
568
+ req.cookies = WEBrick::Cookie.parse(req['Cookie'])
569
+
570
+ Mechanize::TestCase::REQUESTS << req
571
+
572
+ if servlet_klass = SERVLETS[path]
573
+ servlet = servlet_klass.new({})
574
+ servlet.send "do_#{req.method}", req, res
575
+ else
576
+ filename = "htdocs#{path.gsub(/[^\/\\.\w\s]/, '_')}"
577
+ unless PAGE_CACHE[filename]
578
+ open("#{Mechanize::TestCase::TEST_DIR}/#{filename}", 'rb') { |io|
579
+ PAGE_CACHE[filename] = io.read
580
+ }
581
+ end
582
+
583
+ res.body = PAGE_CACHE[filename]
584
+ case filename
585
+ when /\.txt$/
586
+ res['Content-Type'] = 'text/plain'
587
+ when /\.jpg$/
588
+ res['Content-Type'] = 'image/jpeg'
589
+ end
590
+ end
591
+
592
+ res['Content-Type'] ||= 'text/html'
593
+ res.code ||= "200"
594
+
595
+ response_klass = Net::HTTPResponse::CODE_TO_OBJ[res.code.to_s]
596
+ response = response_klass.new res.http_version, res.code, res.message
597
+
598
+ res.header.each do |k,v|
599
+ v = v.first if v.length == 1
600
+ response[k] = v
601
+ end
602
+
603
+ res.cookies.each do |cookie|
604
+ response.add_field 'Set-Cookie', cookie.to_s
605
+ end
606
+
607
+ response['Content-Type'] ||= 'text/html'
608
+ response['Content-Length'] = res['Content-Length'] || res.body.length.to_s
609
+
610
+ io = StringIO.new(res.body)
611
+ response.instance_variable_set :@socket, io
612
+ def io.read clen, dest, _
613
+ dest << string[0, clen]
614
+ end
615
+
616
+ body_exist = req.response_body_permitted? &&
617
+ response_klass.body_permitted?
618
+
619
+ response.instance_variable_set :@body_exist, body_exist
620
+
621
+ yield response if block_given?
622
+
623
+ response
624
+ end
625
+ end
626
+
627
+ class Net::HTTPRequest
628
+ attr_accessor :query, :body, :cookies, :user
629
+ end
630
+
631
+ class Response
632
+ include Net::HTTPHeader
633
+
634
+ attr_reader :code
635
+ attr_accessor :body, :query, :cookies
636
+ attr_accessor :query_params, :http_version
637
+ attr_accessor :header
638
+
639
+ def code=(c)
640
+ @code = c.to_s
641
+ end
642
+
643
+ alias :status :code
644
+ alias :status= :code=
645
+
646
+ def initialize
647
+ @header = {}
648
+ @body = ''
649
+ @code = nil
650
+ @query = nil
651
+ @cookies = []
652
+ @http_version = '1.1'
653
+ end
654
+
655
+ def read_body
656
+ yield body
657
+ end
658
+
659
+ def message
660
+ ''
661
+ end
662
+ end
663
+