require 'mechanize' require 'logger' require 'tempfile' require 'tmpdir' require 'webrick' require 'zlib' require 'rubygems' begin gem 'minitest' rescue Gem::LoadError end require 'minitest/autorun' class Mechanize::TestCase < MiniTest::Unit::TestCase TEST_DIR = File.expand_path '../../../test', __FILE__ REQUESTS = [] def setup super REQUESTS.clear @mech = Mechanize.new @ssl_private_key = nil @ssl_certificate = nil end def fake_page agent = @mech uri = URI 'http://fake.example/' html = <<-END
END response = { 'content-type' => 'text/html' } Mechanize::Page.new uri, response, html, 200, agent end def have_encoding? Object.const_defined? :Encoding end def html_page body uri = URI 'http://example/' Mechanize::Page.new uri, { 'content-type' => 'text/html' }, body, 200, @mech end def in_tmpdir Dir.mktmpdir do |dir| Dir.chdir dir do yield end end end def node element, attributes = {} doc = Nokogiri::HTML::Document.new node = Nokogiri::XML::Node.new element, doc attributes.each do |name, value| node[name] = value end node end def page uri, content_type = 'text/html', body = '', code = 200 uri = URI uri unless URI::Generic === uri Mechanize::Page.new(uri, { 'content-type' => content_type }, body, code, @mech) end def requests REQUESTS end def ssl_private_key @ssl_private_key ||= OpenSSL::PKey::RSA.new <<-KEY -----BEGIN RSA PRIVATE KEY----- MIG7AgEAAkEA8pmEfmP0Ibir91x6pbts4JmmsVZd3xvD5p347EFvBCbhBW1nv1Gs bCBEFlSiT1q2qvxGb5IlbrfdhdgyqdTXUQIBAQIBAQIhAPumXslvf6YasXa1hni3 p80joKOug2UUgqOLD2GUSO//AiEA9ssY6AFxjHWuwo/+/rkLmkfO2s1Lz3OeUEWq 6DiHOK8CAQECAQECIQDt8bc4vS6wh9VXApNSKIpVygtxSFe/IwLeX26n77j6Qg== -----END RSA PRIVATE KEY----- KEY end def ssl_certificate @ssl_certificate ||= OpenSSL::X509::Certificate.new <<-CERT -----BEGIN CERTIFICATE----- MIIBQjCB7aADAgECAgEAMA0GCSqGSIb3DQEBBQUAMCoxDzANBgNVBAMMBm5vYm9k eTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUwIBcNMTExMTAzMjEwODU5WhgPOTk5 OTEyMzExMjU5NTlaMCoxDzANBgNVBAMMBm5vYm9keTEXMBUGCgmSJomT8ixkARkW B2V4YW1wbGUwWjANBgkqhkiG9w0BAQEFAANJADBGAkEA8pmEfmP0Ibir91x6pbts 4JmmsVZd3xvD5p347EFvBCbhBW1nv1GsbCBEFlSiT1q2qvxGb5IlbrfdhdgyqdTX UQIBATANBgkqhkiG9w0BAQUFAANBAAAB//////////////////////////////// //8AMCEwCQYFKw4DAhoFAAQUePiv+QrJxyjtEJNnH5pB9OTWIqA= -----END CERTIFICATE----- CERT end end class BasicAuthServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req,res) htpd = WEBrick::HTTPAuth::Htpasswd.new('dot.htpasswd') htpd.set_passwd('Blah', 'user', 'pass') authenticator = WEBrick::HTTPAuth::BasicAuth.new({ :UserDB => htpd, :Realm => 'Blah', :Logger => Logger.new(nil) } ) begin authenticator.authenticate(req,res) res.body = 'You are authenticated' rescue WEBrick::HTTPStatus::Unauthorized res.status = 401 end FileUtils.rm('dot.htpasswd') end alias :do_POST :do_GET end class ContentTypeServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) ct = req.query['ct'] || "text/html; charset=utf-8" res['Content-Type'] = ct res.body = "Hello World" end end class DigestAuthServlet < WEBrick::HTTPServlet::AbstractServlet htpd = WEBrick::HTTPAuth::Htdigest.new('digest.htpasswd') htpd.set_passwd('Blah', 'user', 'pass') @@authenticator = WEBrick::HTTPAuth::DigestAuth.new({ :UserDB => htpd, :Realm => 'Blah', :Algorithm => 'MD5', :Logger => Logger.new(nil) } ) def do_GET(req,res) def req.request_time; Time.now; end def req.request_uri; '/digest_auth'; end def req.request_method; "GET"; end begin @@authenticator.authenticate(req,res) res.body = 'You are authenticated' rescue WEBrick::HTTPStatus::Unauthorized res.status = 401 end FileUtils.rm('digest.htpasswd') if File.exists?('digest.htpasswd') end alias :do_POST :do_GET end class FileUploadServlet < WEBrick::HTTPServlet::AbstractServlet def do_POST(req, res) res.body = req.body end end class FormServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res.body = "" req.query.each_key { |k| req.query[k].each_data { |data| res.body << "#{WEBrick::HTTPUtils.unescape(k)}:#{WEBrick::HTTPUtils.unescape(data)}
" } } res.body << "
#{res.query}
" res['Content-Type'] = "text/html" end def do_POST(req, res) res.body = "" req.query.each_key { |k| req.query[k].each_data { |data| res.body << "#{k}:#{data}
" } } res.body << "
#{req.body}
" res['Content-Type'] = "text/html" end end class GzipServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) if req['Accept-Encoding'] =~ /gzip/ if name = req.query['file'] then open("#{Mechanize::TestCase::TEST_DIR}/htdocs/#{name}", 'r') do |io| string = "" zipped = StringIO.new string, 'w' Zlib::GzipWriter.wrap zipped do |gz| gz.write io.read end res.body = string end else res.body = '' end res['Content-Encoding'] = req['X-ResponseContentEncoding'] || 'gzip' res['Content-Type'] = "text/html" else res.code = 400 res.body = 'no gzip' end end end class HeaderServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = "text/html" req.query.each do |x,y| res[x] = y end body = '' req.each_header do |k,v| body << "#{k}|#{v}\n" end res.body = body end end class HttpRefreshServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = req.query['ct'] || "text/html" refresh_time = req.query['refresh_time'] || 0 refresh_url = req.query['refresh_url'] || '/index.html' res['Refresh'] = " #{refresh_time};url=#{refresh_url}\r\n"; end end class InfiniteRedirectServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = req.query['ct'] || "text/html" res.status = req.query['code'] ? req.query['code'].to_i : '302' number = req.query['q'] ? req.query['q'].to_i : 0 res['Location'] = "/infinite_redirect?q=#{number + 1}" end alias :do_POST :do_GET end class InfiniteRefreshServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = req.query['ct'] || "text/html" res.status = req.query['code'] ? req.query['code'].to_i : '302' number = req.query['q'] ? req.query['q'].to_i : 0 res['Refresh'] = " 0;url=http://localhost/infinite_refresh?q=#{number + 1}\r\n"; end end class ManyCookiesAsStringServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) cookies = [] name_cookie = WEBrick::Cookie.new("name", "Aaron") name_cookie.path = "/" name_cookie.expires = Time.now + 86400 name_cookie.domain = 'localhost' cookies << name_cookie cookies << name_cookie cookies << name_cookie cookies << "#{name_cookie}; HttpOnly" expired_cookie = WEBrick::Cookie.new("expired", "doh") expired_cookie.path = "/" expired_cookie.expires = Time.now - 86400 cookies << expired_cookie different_path_cookie = WEBrick::Cookie.new("a_path", "some_path") different_path_cookie.path = "/some_path" different_path_cookie.expires = Time.now + 86400 cookies << different_path_cookie no_path_cookie = WEBrick::Cookie.new("no_path", "no_path") no_path_cookie.expires = Time.now + 86400 cookies << no_path_cookie no_exp_path_cookie = WEBrick::Cookie.new("no_expires", "nope") no_exp_path_cookie.path = "/" cookies << no_exp_path_cookie res['Set-Cookie'] = cookies.join(', ') res['Content-Type'] = "text/html" res.body = "hello" end end class ManyCookiesServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) name_cookie = WEBrick::Cookie.new("name", "Aaron") name_cookie.path = "/" name_cookie.expires = Time.now + 86400 res.cookies << name_cookie res.cookies << name_cookie res.cookies << name_cookie res.cookies << name_cookie expired_cookie = WEBrick::Cookie.new("expired", "doh") expired_cookie.path = "/" expired_cookie.expires = Time.now - 86400 res.cookies << expired_cookie different_path_cookie = WEBrick::Cookie.new("a_path", "some_path") different_path_cookie.path = "/some_path" different_path_cookie.expires = Time.now + 86400 res.cookies << different_path_cookie no_path_cookie = WEBrick::Cookie.new("no_path", "no_path") no_path_cookie.expires = Time.now + 86400 res.cookies << no_path_cookie no_exp_path_cookie = WEBrick::Cookie.new("no_expires", "nope") no_exp_path_cookie.path = "/" res.cookies << no_exp_path_cookie res['Content-Type'] = "text/html" res.body = "hello" end end class ModifiedSinceServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) s_time = 'Fri, 04 May 2001 00:00:38 GMT' my_time = Time.parse(s_time) if req['If-Modified-Since'] your_time = Time.parse(req['If-Modified-Since']) if my_time > your_time res.body = 'This page was updated since you requested' else res.status = 304 end else res.body = 'You did not send an If-Modified-Since header' end res['Last-Modified'] = s_time end end class NTLMServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) if req['Authorization'] =~ /^NTLM (.*)/ then authorization = $1.unpack('m*').first if authorization =~ /^NTLMSSP\000\001/ then type_2 = 'TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mr' \ 'ze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4A' \ 'AgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUA' \ 'UgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIA' \ 'cwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMA' \ 'bwBtAAAAAAA=' res['WWW-Authenticate'] = "NTLM #{type_2}" res.status = 401 elsif authorization =~ /^NTLMSSP\000\003/ then res.body = 'ok' else res['WWW-Authenticate'] = 'NTLM' res.status = 401 end else res['WWW-Authenticate'] = 'NTLM' res.status = 401 end end end class OneCookieNoSpacesServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) cookie = WEBrick::Cookie.new("foo", "bar") cookie.path = "/" cookie.expires = Time.now + 86400 res.cookies << cookie.to_s.gsub(/; /, ';') res['Content-Type'] = "text/html" res.body = "hello" end end class OneCookieServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) cookie = WEBrick::Cookie.new("foo", "bar") cookie.path = "/" cookie.expires = Time.now + 86400 res.cookies << cookie res['Content-Type'] = "text/html" res.body = "hello" end end class QuotedValueCookieServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) cookie = WEBrick::Cookie.new("quoted", "\"value\"") cookie.path = "/" cookie.expires = Time.now + 86400 res.cookies << cookie res['Content-Type'] = "text/html" res.body = "hello" end end class RedirectServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = req.query['ct'] || "text/html" res.status = req.query['code'] ? req.query['code'].to_i : '302' res['Location'] = "/verb" end alias :do_POST :do_GET alias :do_HEAD :do_GET alias :do_PUT :do_GET alias :do_DELETE :do_GET end class RefererServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = "text/html" res.body = req['Referer'] || '' end def do_POST(req, res) res['Content-Type'] = "text/html" res.body = req['Referer'] || '' end end class RefreshWithoutUrl < WEBrick::HTTPServlet::AbstractServlet @@count = 0 def do_GET(req, res) res['Content-Type'] = "text/html" @@count += 1 if @@count > 1 res['Refresh'] = "0; url=http://localhost/index.html"; else res['Refresh'] = "0"; end end end class RefreshWithEmptyUrl < WEBrick::HTTPServlet::AbstractServlet @@count = 0 def do_GET(req, res) res['Content-Type'] = "text/html" @@count += 1 if @@count > 1 res['Refresh'] = "0; url=http://localhost/index.html"; else res['Refresh'] = "0; url="; end end end class ResponseCodeServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = req.query['ct'] || "text/html" if req.query['code'] code = req.query['code'].to_i case code when 300, 301, 302, 303, 304, 305, 307 res['Location'] = "/index.html" end res.status = code else end end end class SendCookiesServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) res['Content-Type'] = "text/html" res.body = "" req.cookies.each { |c| res.body << "#{c.name}:#{c.value}" } res.body << "" end end class VerbServlet < WEBrick::HTTPServlet::AbstractServlet %w(HEAD GET POST PUT DELETE).each do |verb| eval(<<-eomethod) def do_#{verb}(req, res) res.header['X-Request-Method'] = #{verb.dump} end eomethod end end class Net::HTTP alias :old_do_start :do_start def do_start @started = true end SERVLETS = { '/gzip' => GzipServlet, '/form_post' => FormServlet, '/basic_auth' => BasicAuthServlet, '/form post' => FormServlet, '/response_code' => ResponseCodeServlet, '/http_refresh' => HttpRefreshServlet, '/content_type_test' => ContentTypeServlet, '/referer' => RefererServlet, '/file_upload' => FileUploadServlet, '/one_cookie' => OneCookieServlet, '/one_cookie_no_space' => OneCookieNoSpacesServlet, '/many_cookies' => ManyCookiesServlet, '/many_cookies_as_string' => ManyCookiesAsStringServlet, '/ntlm' => NTLMServlet, '/send_cookies' => SendCookiesServlet, '/quoted_value_cookie' => QuotedValueCookieServlet, '/if_modified_since' => ModifiedSinceServlet, '/http_headers' => HeaderServlet, '/infinite_redirect' => InfiniteRedirectServlet, '/infinite_refresh' => InfiniteRefreshServlet, '/redirect' => RedirectServlet, '/refresh_without_url' => RefreshWithoutUrl, '/refresh_with_empty_url' => RefreshWithEmptyUrl, '/digest_auth' => DigestAuthServlet, '/verb' => VerbServlet, } PAGE_CACHE = {} alias :old_request :request def request(req, *data, &block) url = URI.parse(req.path) path = WEBrick::HTTPUtils.unescape(url.path) path = '/index.html' if path == '/' res = ::Response.new res.query_params = url.query req.query = if 'POST' != req.method && url.query then WEBrick::HTTPUtils.parse_query url.query elsif req['content-type'] =~ /www-form-urlencoded/ then WEBrick::HTTPUtils.parse_query req.body elsif req['content-type'] =~ /boundary=(.+)/ then boundary = WEBrick::HTTPUtils.dequote $1 WEBrick::HTTPUtils.parse_form_data req.body, boundary else {} end req.cookies = WEBrick::Cookie.parse(req['Cookie']) Mechanize::TestCase::REQUESTS << req if servlet_klass = SERVLETS[path] servlet = servlet_klass.new({}) servlet.send "do_#{req.method}", req, res else filename = "htdocs#{path.gsub(/[^\/\\.\w\s]/, '_')}" unless PAGE_CACHE[filename] open("#{Mechanize::TestCase::TEST_DIR}/#{filename}", 'rb') { |io| PAGE_CACHE[filename] = io.read } end res.body = PAGE_CACHE[filename] case filename when /\.txt$/ res['Content-Type'] = 'text/plain' when /\.jpg$/ res['Content-Type'] = 'image/jpeg' end end res['Content-Type'] ||= 'text/html' res.code ||= "200" response_klass = Net::HTTPResponse::CODE_TO_OBJ[res.code.to_s] response = response_klass.new res.http_version, res.code, res.message res.header.each do |k,v| v = v.first if v.length == 1 response[k] = v end res.cookies.each do |cookie| response.add_field 'Set-Cookie', cookie.to_s end response['Content-Type'] ||= 'text/html' response['Content-Length'] = res['Content-Length'] || res.body.length.to_s io = StringIO.new(res.body) response.instance_variable_set :@socket, io def io.read clen, dest, _ dest << string[0, clen] end body_exist = req.response_body_permitted? && response_klass.body_permitted? response.instance_variable_set :@body_exist, body_exist yield response if block_given? response end end class Net::HTTPRequest attr_accessor :query, :body, :cookies, :user end class Response include Net::HTTPHeader attr_reader :code attr_accessor :body, :query, :cookies attr_accessor :query_params, :http_version attr_accessor :header def code=(c) @code = c.to_s end alias :status :code alias :status= :code= def initialize @header = {} @body = '' @code = nil @query = nil @cookies = [] @http_version = '1.1' end def read_body yield body end def message '' end end