mechanize 2.0.1 → 2.1.pre.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mechanize might be problematic. Click here for more details.

Files changed (148) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG.rdoc +82 -0
  3. data/EXAMPLES.rdoc +1 -1
  4. data/FAQ.rdoc +9 -9
  5. data/Manifest.txt +35 -48
  6. data/README.rdoc +2 -1
  7. data/Rakefile +16 -3
  8. data/lib/mechanize.rb +809 -392
  9. data/lib/mechanize/content_type_error.rb +10 -11
  10. data/lib/mechanize/cookie.rb +193 -60
  11. data/lib/mechanize/cookie_jar.rb +39 -86
  12. data/lib/mechanize/download.rb +59 -0
  13. data/lib/mechanize/element_matcher.rb +1 -0
  14. data/lib/mechanize/file.rb +61 -76
  15. data/lib/mechanize/file_saver.rb +37 -35
  16. data/lib/mechanize/form.rb +475 -410
  17. data/lib/mechanize/form/button.rb +4 -7
  18. data/lib/mechanize/form/check_box.rb +10 -9
  19. data/lib/mechanize/form/field.rb +52 -42
  20. data/lib/mechanize/form/file_upload.rb +17 -19
  21. data/lib/mechanize/form/hidden.rb +3 -0
  22. data/lib/mechanize/form/image_button.rb +15 -16
  23. data/lib/mechanize/form/keygen.rb +34 -0
  24. data/lib/mechanize/form/multi_select_list.rb +20 -9
  25. data/lib/mechanize/form/option.rb +48 -47
  26. data/lib/mechanize/form/radio_button.rb +52 -45
  27. data/lib/mechanize/form/reset.rb +3 -0
  28. data/lib/mechanize/form/select_list.rb +10 -6
  29. data/lib/mechanize/form/submit.rb +3 -0
  30. data/lib/mechanize/form/text.rb +3 -0
  31. data/lib/mechanize/form/textarea.rb +3 -0
  32. data/lib/mechanize/headers.rb +17 -19
  33. data/lib/mechanize/history.rb +60 -61
  34. data/lib/mechanize/http.rb +5 -0
  35. data/lib/mechanize/http/agent.rb +485 -218
  36. data/lib/mechanize/http/auth_challenge.rb +59 -0
  37. data/lib/mechanize/http/auth_realm.rb +31 -0
  38. data/lib/mechanize/http/content_disposition_parser.rb +188 -0
  39. data/lib/mechanize/http/www_authenticate_parser.rb +155 -0
  40. data/lib/mechanize/monkey_patch.rb +14 -35
  41. data/lib/mechanize/page.rb +34 -2
  42. data/lib/mechanize/page/base.rb +6 -7
  43. data/lib/mechanize/page/frame.rb +5 -5
  44. data/lib/mechanize/page/image.rb +23 -23
  45. data/lib/mechanize/page/label.rb +16 -16
  46. data/lib/mechanize/page/link.rb +16 -0
  47. data/lib/mechanize/page/meta_refresh.rb +19 -7
  48. data/lib/mechanize/parser.rb +173 -0
  49. data/lib/mechanize/pluggable_parsers.rb +126 -83
  50. data/lib/mechanize/redirect_limit_reached_error.rb +16 -13
  51. data/lib/mechanize/redirect_not_get_or_head_error.rb +18 -16
  52. data/lib/mechanize/response_code_error.rb +16 -17
  53. data/lib/mechanize/robots_disallowed_error.rb +22 -23
  54. data/lib/mechanize/test_case.rb +659 -0
  55. data/lib/mechanize/unauthorized_error.rb +3 -0
  56. data/lib/mechanize/unsupported_scheme_error.rb +4 -6
  57. data/lib/mechanize/util.rb +0 -12
  58. data/test/htdocs/form_order_test.html +11 -0
  59. data/test/htdocs/form_test.html +2 -2
  60. data/test/htdocs/tc_links.html +1 -0
  61. data/test/test_mechanize.rb +367 -59
  62. data/test/test_mechanize_cookie.rb +69 -4
  63. data/test/test_mechanize_cookie_jar.rb +200 -124
  64. data/test/test_mechanize_download.rb +43 -0
  65. data/test/test_mechanize_file.rb +53 -45
  66. data/test/{test_mechanize_file_response.rb → test_mechanize_file_connection.rb} +2 -2
  67. data/test/test_mechanize_file_request.rb +2 -2
  68. data/test/test_mechanize_file_saver.rb +21 -0
  69. data/test/test_mechanize_form.rb +345 -46
  70. data/test/test_mechanize_form_check_box.rb +5 -4
  71. data/test/test_mechanize_form_encoding.rb +10 -16
  72. data/test/test_mechanize_form_field.rb +45 -3
  73. data/test/test_mechanize_form_file_upload.rb +20 -0
  74. data/test/test_mechanize_form_image_button.rb +2 -2
  75. data/test/test_mechanize_form_keygen.rb +32 -0
  76. data/test/test_mechanize_form_multi_select_list.rb +84 -0
  77. data/test/test_mechanize_form_option.rb +55 -0
  78. data/test/test_mechanize_form_radio_button.rb +78 -0
  79. data/test/test_mechanize_form_select_list.rb +76 -0
  80. data/test/test_mechanize_form_textarea.rb +8 -7
  81. data/test/{test_headers.rb → test_mechanize_headers.rb} +4 -2
  82. data/test/test_mechanize_history.rb +103 -0
  83. data/test/test_mechanize_http_agent.rb +525 -17
  84. data/test/test_mechanize_http_auth_challenge.rb +39 -0
  85. data/test/test_mechanize_http_auth_realm.rb +49 -0
  86. data/test/test_mechanize_http_content_disposition_parser.rb +118 -0
  87. data/test/test_mechanize_http_www_authenticate_parser.rb +146 -0
  88. data/test/test_mechanize_link.rb +10 -14
  89. data/test/test_mechanize_page.rb +118 -0
  90. data/test/test_mechanize_page_encoding.rb +48 -13
  91. data/test/test_mechanize_page_frame.rb +16 -0
  92. data/test/test_mechanize_page_link.rb +27 -19
  93. data/test/test_mechanize_page_meta_refresh.rb +26 -14
  94. data/test/test_mechanize_parser.rb +289 -0
  95. data/test/test_mechanize_pluggable_parser.rb +52 -0
  96. data/test/test_mechanize_redirect_limit_reached_error.rb +24 -0
  97. data/test/test_mechanize_redirect_not_get_or_head_error.rb +3 -7
  98. data/test/test_mechanize_subclass.rb +2 -2
  99. data/test/test_mechanize_util.rb +24 -13
  100. data/test/test_multi_select.rb +23 -22
  101. metadata +145 -114
  102. metadata.gz.sig +0 -0
  103. data/lib/mechanize/inspect.rb +0 -88
  104. data/test/helper.rb +0 -175
  105. data/test/htdocs/form_select_all.html +0 -16
  106. data/test/htdocs/form_select_none.html +0 -17
  107. data/test/htdocs/form_select_noopts.html +0 -10
  108. data/test/htdocs/iframe_test.html +0 -16
  109. data/test/htdocs/nofollow.html +0 -9
  110. data/test/htdocs/norobots.html +0 -8
  111. data/test/htdocs/rel_nofollow.html +0 -8
  112. data/test/htdocs/tc_base_images.html +0 -10
  113. data/test/htdocs/tc_images.html +0 -8
  114. data/test/htdocs/tc_no_attributes.html +0 -16
  115. data/test/htdocs/tc_radiobuttons.html +0 -17
  116. data/test/htdocs/test_bad_encoding.html +0 -52
  117. data/test/servlets.rb +0 -402
  118. data/test/ssl_server.rb +0 -48
  119. data/test/test_cookies.rb +0 -129
  120. data/test/test_form_action.rb +0 -52
  121. data/test/test_form_as_hash.rb +0 -59
  122. data/test/test_form_button.rb +0 -46
  123. data/test/test_frames.rb +0 -34
  124. data/test/test_history.rb +0 -118
  125. data/test/test_history_added.rb +0 -16
  126. data/test/test_html_unscape_forms.rb +0 -46
  127. data/test/test_if_modified_since.rb +0 -20
  128. data/test/test_images.rb +0 -19
  129. data/test/test_no_attributes.rb +0 -13
  130. data/test/test_option.rb +0 -18
  131. data/test/test_pluggable_parser.rb +0 -136
  132. data/test/test_post_form.rb +0 -37
  133. data/test/test_pretty_print.rb +0 -22
  134. data/test/test_radiobutton.rb +0 -75
  135. data/test/test_redirect_limit_reached.rb +0 -39
  136. data/test/test_referer.rb +0 -81
  137. data/test/test_relative_links.rb +0 -40
  138. data/test/test_request.rb +0 -13
  139. data/test/test_response_code.rb +0 -53
  140. data/test/test_robots.rb +0 -72
  141. data/test/test_save_file.rb +0 -48
  142. data/test/test_scheme.rb +0 -48
  143. data/test/test_select.rb +0 -119
  144. data/test/test_select_all.rb +0 -15
  145. data/test/test_select_none.rb +0 -15
  146. data/test/test_select_noopts.rb +0 -18
  147. data/test/test_set_fields.rb +0 -44
  148. data/test/test_ssl_server.rb +0 -20
@@ -0,0 +1,59 @@
1
+ ##
2
+ # Download is a pluggable parser for downloading files without loading them
3
+ # into memory first. You may subclass this class to handle content types you
4
+ # do not wish to load into memory first.
5
+ #
6
+ # See Mechanize::PluggableParser for instructions on using this class.
7
+
8
+ class Mechanize::Download
9
+
10
+ include Mechanize::Parser
11
+
12
+ ##
13
+ # Accessor for the IO-like that contains the body
14
+
15
+ attr_reader :body_io
16
+
17
+ alias content body_io
18
+
19
+ ##
20
+ # Creates a new download retrieved from the given +uri+ and +response+
21
+ # object. The +body_io+ is an IO-like containing the HTTP response body and
22
+ # +code+ is the HTTP status.
23
+
24
+ def initialize uri = nil, response = nil, body_io = nil, code = nil
25
+ @uri = uri
26
+ @body_io = body_io
27
+ @code = code
28
+
29
+ @full_path = false unless defined? @full_path
30
+
31
+ fill_header response
32
+ extract_filename
33
+
34
+ yield self if block_given?
35
+ end
36
+
37
+ ##
38
+ # Saves a copy of the body_io to +filename+
39
+
40
+ def save filename = nil
41
+ filename = find_free_name filename
42
+
43
+ dirname = File.dirname filename
44
+ FileUtils.mkdir_p dirname
45
+
46
+ # Ruby 1.8.7 implements StringIO#path, can't use respond_to? :path
47
+ if StringIO === @body_io then
48
+ open filename, 'wb' do |io|
49
+ until @body_io.eof? do
50
+ io.write @body_io.read 16384
51
+ end
52
+ end
53
+ else
54
+ FileUtils.cp @body_io.path, filename
55
+ end
56
+ end
57
+
58
+ end
59
+
@@ -8,6 +8,7 @@ module Mechanize::ElementMatcher
8
8
  else
9
9
  criteria.map do |k, v|
10
10
  k = :dom_id if k.to_sym == :id
11
+ k = :dom_class if k.to_sym == :class
11
12
  [k, v]
12
13
  end
13
14
  end
@@ -1,80 +1,65 @@
1
- class Mechanize
2
- # = Synopsis
3
- # This is the default (and base) class for the Pluggable Parsers. If
4
- # Mechanize cannot find an appropriate class to use for the content type,
5
- # this class will be used. For example, if you download a JPG, Mechanize
6
- # will not know how to parse it, so this class will be instantiated.
7
- #
8
- # This is a good class to use as the base class for building your own
9
- # pluggable parsers.
10
- #
11
- # == Example
12
- # require 'rubygems'
13
- # require 'mechanize'
14
- #
15
- # agent = Mechanize.new
16
- # agent.get('http://example.com/foo.jpg').class #=> Mechanize::File
17
- #
18
- class File
19
- extend Forwardable
20
-
21
- attr_accessor :uri, :response, :body, :code, :filename
22
- alias :header :response
23
- def_delegator :header, :[], :[]
24
- def_delegator :header, :[]=, :[]=
25
- def_delegator :header, :key?, :key?
26
- def_delegator :header, :each, :each
27
- def_delegator :header, :canonical_each, :canonical_each
28
-
29
- alias :content :body
30
-
31
- def initialize(uri=nil, response=nil, body=nil, code=nil)
32
- @uri = uri
33
- @body = body
34
- @code = code
35
- @response = Headers.new
36
-
37
- # Copy the headers in to a hash to prevent memory leaks
38
- if response
39
- response.each { |k,v|
40
- @response[k] = v
41
- }
42
- end
43
-
44
- @filename = 'index.html'
45
-
46
- # Set the filename
47
- if disposition = @response['content-disposition']
48
- disposition.split(/;\s*/).each do |pair|
49
- k,v = pair.split(/=/, 2)
50
- @filename = v if k && k.downcase == 'filename'
51
- end
52
- else
53
- if @uri
54
- @filename = @uri.path.split(/\//).last || 'index.html'
55
- @filename << ".html" unless @filename =~ /\./
56
- end
57
- end
58
-
59
- yield self if block_given?
60
- end
1
+ ##
2
+ # This is the base class for the Pluggable Parsers. If Mechanize cannot find
3
+ # an appropriate class to use for the content type, this class will be used.
4
+ # For example, if you download an image/jpeg, Mechanize will not know how to
5
+ # parse it, so this class will be instantiated.
6
+ #
7
+ # This is a good class to use as the base class for building your own
8
+ # pluggable parsers.
9
+ #
10
+ # == Example
11
+ #
12
+ # require 'mechanize'
13
+ #
14
+ # agent = Mechanize.new
15
+ # agent.get('http://example.com/foo.jpg').class #=> Mechanize::File
61
16
 
62
- # Use this method to save the content of this object to filename
63
- def save_as(filename = nil)
64
- if filename.nil?
65
- filename = @filename
66
- number = 1
67
- while(::File.exists?(filename))
68
- filename = "#{@filename}.#{number}"
69
- number += 1
70
- end
71
- end
72
-
73
- ::File::open(filename, "wb") { |f|
74
- f.write body
75
- }
76
- end
17
+ class Mechanize::File
18
+
19
+ include Mechanize::Parser
20
+
21
+ ##
22
+ # The HTTP response body, the raw file contents
23
+
24
+ attr_accessor :body
25
+
26
+ ##
27
+ # The filename for this file based on the content-disposition of the
28
+ # response or the basename of the URL
29
+
30
+ attr_accessor :filename
31
+
32
+ alias content body
33
+
34
+ ##
35
+ # Creates a new file retrieved from the given +uri+ and +response+ object.
36
+ # The +body+ is the HTTP response body and +code+ is the HTTP status.
37
+
38
+ def initialize uri = nil, response = nil, body = nil, code = nil
39
+ @uri = uri
40
+ @body = body
41
+ @code = code
77
42
 
78
- alias :save :save_as
43
+ @full_path = false unless defined? @full_path
44
+
45
+ fill_header response
46
+ extract_filename
47
+
48
+ yield self if block_given?
49
+ end
50
+
51
+ ##
52
+ # Use this method to save the content of this object to +filename+
53
+
54
+ def save filename = nil
55
+ filename = find_free_name filename
56
+
57
+ open filename, 'wb' do |f|
58
+ f.write body
59
+ end
79
60
  end
61
+
62
+ alias save_as save
63
+
80
64
  end
65
+
@@ -1,37 +1,39 @@
1
- class Mechanize
2
- # = Synopsis
3
- # This is a pluggable parser that automatically saves every file
4
- # it encounters. It saves the files as a tree, reflecting the
5
- # host and file path.
6
- #
7
- # == Example to save all PDF's
8
- # require 'rubygems'
9
- # require 'mechanize'
10
- #
11
- # agent = Mechanize.new
12
- # agent.pluggable_parser.pdf = Mechanize::FileSaver
13
- # agent.get('http://example.com/foo.pdf')
14
- #
15
- class FileSaver < File
16
- attr_reader :filename
17
-
18
- def initialize(uri=nil, response=nil, body=nil, code=nil)
19
- super(uri, response, body, code)
20
- path = uri.path.empty? ? 'index.html' : uri.path.gsub(/^[\/]*/, '')
21
- path += 'index.html' if path =~ /\/$/
22
-
23
- split_path = path.split(/\//)
24
- filename = split_path.length > 0 ? split_path.pop : 'index.html'
25
- joined_path = split_path.join(::File::SEPARATOR)
26
- path = if joined_path.empty?
27
- uri.host
28
- else
29
- "#{uri.host}#{::File::SEPARATOR}#{joined_path}"
30
- end
31
-
32
- @filename = "#{path}#{::File::SEPARATOR}#{filename}"
33
- FileUtils.mkdir_p(path)
34
- save_as(@filename)
35
- end
1
+ ##
2
+ # This is a pluggable parser that automatically saves every file it
3
+ # encounters. It saves the files as a tree, reflecting the host and file
4
+ # path.
5
+ #
6
+ # == Example
7
+ #
8
+ # This example saves all .pdf files
9
+ #
10
+ # require 'mechanize'
11
+ #
12
+ # agent = Mechanize.new
13
+ # agent.pluggable_parser.pdf = Mechanize::FileSaver
14
+ # agent.get('http://example.com/foo.pdf')
15
+ #
16
+ # Dir['example.com/*'] # => foo.pdf
17
+
18
+ class Mechanize::FileSaver < Mechanize::Download
19
+
20
+ attr_reader :filename
21
+
22
+ def initialize uri = nil, response = nil, body_io = nil, code = nil
23
+ @full_path = true
24
+
25
+ super
26
+
27
+ save @filename
36
28
  end
29
+
30
+ ##
31
+ # The save_as alias is provided for backwards compatibility with mechanize
32
+ # 2.0. It will be removed in mechanize 3.
33
+ #--
34
+ # TODO remove in mechanize 3
35
+
36
+ alias save_as save
37
+
37
38
  end
39
+
@@ -1,478 +1,543 @@
1
1
  require 'mechanize/element_matcher'
2
- require 'mechanize/form/field'
3
- require 'mechanize/form/button'
4
- require 'mechanize/form/file_upload'
5
- require 'mechanize/form/image_button'
6
- require 'mechanize/form/multi_select_list'
7
- require 'mechanize/form/option'
8
- require 'mechanize/form/radio_button'
9
- require 'mechanize/form/check_box'
10
- require 'mechanize/form/select_list'
11
2
 
12
- class Mechanize
13
- # =Synopsis
14
- # This class encapsulates a form parsed out of an HTML page. Each type
15
- # of input fields available in a form can be accessed through this object.
16
- # See GlobalForm for more methods.
17
- #
18
- # ==Example
19
- # Find a form and print out its fields
20
- # form = page.forms.first # => Mechanize::Form
21
- # form.fields.each { |f| puts f.name }
22
- # Set the input field 'name' to "Aaron"
23
- # form['name'] = 'Aaron'
24
- # puts form['name']
25
- class Form
26
-
27
- extend Mechanize::ElementMatcher
3
+ # This class encapsulates a form parsed out of an HTML page. Each type of
4
+ # input fields available in a form can be accessed through this object.
5
+ #
6
+ # == Examples
7
+ #
8
+ # Find a form and print out its fields
9
+ #
10
+ # form = page.forms.first # => Mechanize::Form
11
+ # form.fields.each { |f| puts f.name }
12
+ #
13
+ # Set the input field 'name' to "Aaron"
14
+ #
15
+ # form['name'] = 'Aaron'
16
+ # puts form['name']
17
+
18
+ class Mechanize::Form
19
+
20
+ extend Mechanize::ElementMatcher
21
+
22
+ attr_accessor :method, :action, :name
23
+
24
+ attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
25
+
26
+ # Content-Type for form data (i.e. application/x-www-form-urlencoded)
27
+ attr_accessor :enctype
28
+
29
+ # Character encoding of form data (i.e. UTF-8)
30
+ attr_accessor :encoding
31
+
32
+ # When true, character encoding errors will never be never raised on form
33
+ # submission. Default is false
34
+ attr_accessor :ignore_encoding_error
35
+
36
+ alias :elements :fields
37
+
38
+ attr_reader :form_node
39
+ attr_reader :page
40
+
41
+ def initialize(node, mech=nil, page=nil)
42
+ @enctype = node['enctype'] || 'application/x-www-form-urlencoded'
43
+ @form_node = node
44
+ @action = Mechanize::Util.html_unescape(node['action'])
45
+ @method = (node['method'] || 'GET').upcase
46
+ @name = node['name']
47
+ @clicked_buttons = []
48
+ @page = page
49
+ @mech = mech
50
+
51
+ @encoding = node['accept-charset'] || (page && page.encoding) || nil
52
+ @ignore_encoding_error = false
53
+ parse
54
+ end
28
55
 
29
- attr_accessor :method, :action, :name
56
+ # Returns whether or not the form contains a field with +field_name+
57
+ def has_field?(field_name)
58
+ fields.find { |f| f.name == field_name }
59
+ end
30
60
 
31
- attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
61
+ alias :has_key? :has_field?
32
62
 
33
- # Content-Type for form data (i.e. application/x-www-form-urlencoded)
34
- attr_accessor :enctype
63
+ def has_value?(value)
64
+ fields.find { |f| f.value == value }
65
+ end
35
66
 
36
- # Character encoding of form data (i.e. UTF-8)
37
- attr_accessor :encoding
67
+ def keys; fields.map { |f| f.name }; end
68
+
69
+ def values; fields.map { |f| f.value }; end
70
+
71
+ def submits ; @submits ||= buttons.select { |f| f.class == Submit }; end
72
+ def resets ; @resets ||= buttons.select { |f| f.class == Reset }; end
73
+ def texts ; @texts ||= fields.select { |f| f.class == Text }; end
74
+ def hiddens ; @hiddens ||= fields.select { |f| f.class == Hidden }; end
75
+ def textareas; @textareas ||= fields.select { |f| f.class == Textarea }; end
76
+ def keygens ; @keygens ||= fields.select { |f| f.class == Keygen }; end
77
+
78
+ def submit_button?(button_name) submits.find{|f| f.name == button_name}; end
79
+ def reset_button?(button_name) resets.find{|f| f.name == button_name}; end
80
+ def text_field?(field_name) texts.find{|f| f.name == field_name}; end
81
+ def hidden_field?(field_name) hiddens.find{|f| f.name == field_name}; end
82
+ def textarea_field?(field_name) textareas.find{|f| f.name == field_name}; end
83
+
84
+ # This method is a shortcut to get form's DOM id.
85
+ # Common usage:
86
+ # page.form_with(:dom_id => "foorm")
87
+ # Note that you can also use +:id+ to get to this method:
88
+ # page.form_with(:id => "foorm")
89
+ def dom_id
90
+ form_node['id']
91
+ end
38
92
 
39
- # When true, character encoding errors will never be never raised on form
40
- # submission. Default is false
41
- attr_accessor :ignore_encoding_error
93
+ # This method is a shortcut to get form's DOM class.
94
+ # Common usage:
95
+ # page.form_with(:dom_class => "foorm")
96
+ # Note that you can also use +:class+ to get to this method:
97
+ # page.form_with(:class => "foorm")
98
+ def dom_class
99
+ form_node['class']
100
+ end
42
101
 
43
- alias :elements :fields
102
+ # Add a field with +field_name+ and +value+
103
+ def add_field!(field_name, value = nil)
104
+ fields << Field.new({'name' => field_name}, value)
105
+ end
44
106
 
45
- attr_reader :form_node
46
- attr_reader :page
107
+ ##
108
+ # This method sets multiple fields on the form. It takes a list of +fields+
109
+ # which are name, value pairs.
110
+ #
111
+ # If there is more than one field found with the same name, this method will
112
+ # set the first one found. If you want to set the value of a duplicate
113
+ # field, use a value which is a Hash with the key as the index in to the
114
+ # form. The index is zero based.
115
+ #
116
+ # For example, to set the second field named 'foo', you could do the
117
+ # following:
118
+ #
119
+ # form.set_fields :foo => { 1 => 'bar' }
120
+
121
+ def set_fields fields = {}
122
+ fields.each do |name, v|
123
+ case v
124
+ when Hash
125
+ v.each do |index, value|
126
+ self.fields_with(:name => name.to_s)[index].value = value
127
+ end
128
+ else
129
+ value = nil
130
+ index = 0
47
131
 
48
- def initialize(node, mech=nil, page=nil)
49
- @enctype = node['enctype'] || 'application/x-www-form-urlencoded'
50
- @form_node = node
51
- @action = Util.html_unescape(node['action'])
52
- @method = (node['method'] || 'GET').upcase
53
- @name = node['name']
54
- @clicked_buttons = []
55
- @page = page
56
- @mech = mech
132
+ [v].flatten.each do |val|
133
+ index = val.to_i if value
134
+ value = val unless value
135
+ end
57
136
 
58
- @encoding = node['accept-charset'] || (page && page.encoding) || nil
59
- @ignore_encoding_error = false
60
- parse
137
+ self.fields_with(:name => name.to_s)[index].value = value
138
+ end
61
139
  end
140
+ end
62
141
 
63
- # Returns whether or not the form contains a field with +field_name+
64
- def has_field?(field_name)
65
- fields.find { |f| f.name == field_name }
142
+ # Fetch the value of the first input field with the name passed in
143
+ # ==Example
144
+ # Fetch the value set in the input field 'name'
145
+ # puts form['name']
146
+ def [](field_name)
147
+ f = field(field_name)
148
+ f && f.value
149
+ end
150
+
151
+ # Set the value of the first input field with the name passed in
152
+ # ==Example
153
+ # Set the value in the input field 'name' to "Aaron"
154
+ # form['name'] = 'Aaron'
155
+ def []=(field_name, value)
156
+ f = field(field_name)
157
+ if f
158
+ f.value = value
159
+ else
160
+ add_field!(field_name, value)
66
161
  end
162
+ end
67
163
 
68
- alias :has_key? :has_field?
164
+ # Treat form fields like accessors.
165
+ def method_missing(meth, *args)
166
+ method = meth.to_s.gsub(/=$/, '')
69
167
 
70
- def has_value?(value)
71
- fields.find { |f| f.value == value }
168
+ if field(method)
169
+ return field(method).value if args.empty?
170
+ return field(method).value = args[0]
72
171
  end
73
172
 
74
- def keys; fields.map { |f| f.name }; end
75
-
76
- def values; fields.map { |f| f.value }; end
77
-
78
- def submits ; @submits ||= buttons.select { |f| f.class == Submit }; end
79
- def resets ; @resets ||= buttons.select { |f| f.class == Reset }; end
80
- def texts ; @texts ||= fields.select { |f| f.class == Text }; end
81
- def hiddens ; @hiddens ||= fields.select { |f| f.class == Hidden }; end
82
- def textareas; @textareas ||= fields.select { |f| f.class == Textarea }; end
83
-
84
- def submit_button?(button_name) submits.find{|f| f.name == button_name}; end
85
- def reset_button?(button_name) resets.find{|f| f.name == button_name}; end
86
- def text_field?(field_name) texts.find{|f| f.name == field_name}; end
87
- def hidden_field?(field_name) hiddens.find{|f| f.name == field_name}; end
88
- def textarea_field?(field_name) textareas.find{|f| f.name == field_name}; end
89
-
90
- # This method is a shortcut to get form's DOM id.
91
- # Common usage:
92
- # page.form_with(:dom_id => "foorm")
93
- # Note that you can also use +:id+ to get to this method:
94
- # page.form_with(:id => "foorm")
95
- def dom_id
96
- form_node['id']
97
- end
173
+ super
174
+ end
98
175
 
99
- # Add a field with +field_name+ and +value+
100
- def add_field!(field_name, value = nil)
101
- fields << Field.new({'name' => field_name}, value)
102
- end
176
+ # Submit this form with the button passed in
177
+ def submit button=nil, headers = {}
178
+ @mech.submit(self, button, headers)
179
+ end
180
+
181
+ # Submit form using +button+. Defaults
182
+ # to the first button.
183
+ def click_button(button = buttons.first)
184
+ submit(button)
185
+ end
103
186
 
104
- # This method sets multiple fields on the form. It takes a list of field
105
- # name, value pairs. If there is more than one field found with the
106
- # same name, this method will set the first one found. If you want to
107
- # set the value of a duplicate field, use a value which is a Hash with
108
- # the key as the index in to the form. The index
109
- # is zero based. For example, to set the second field named 'foo', you
110
- # could do the following:
111
- # form.set_fields( :foo => { 1 => 'bar' } )
112
- def set_fields(fields = {})
113
- fields.each do |k,v|
114
- case v
115
- when Hash
116
- v.each do |index, value|
117
- self.fields_with(:name => k.to_s).[](index).value = value
118
- end
119
- else
120
- value = nil
121
- index = 0
122
- [v].flatten.each do |val|
123
- index = val.to_i if value
124
- value = val unless value
125
- end
126
- self.fields_with(:name => k.to_s).[](index).value = value
187
+ # This method is sub-method of build_query.
188
+ # It converts charset of query value of fields into expected one.
189
+ def proc_query(field)
190
+ return unless field.query_value
191
+ field.query_value.map{|(name, val)|
192
+ [from_native_charset(name), from_native_charset(val.to_s)]
193
+ }
194
+ end
195
+ private :proc_query
196
+
197
+ def from_native_charset str
198
+ Mechanize::Util.from_native_charset(str, encoding, @ignore_encoding_error,
199
+ @mech && @mech.log)
200
+ end
201
+ private :from_native_charset
202
+
203
+ # This method builds an array of arrays that represent the query
204
+ # parameters to be used with this form. The return value can then
205
+ # be used to create a query string for this form.
206
+ def build_query(buttons = [])
207
+ query = []
208
+ @mech.log.info("form encoding: #{encoding}") if @mech && @mech.log
209
+
210
+ successful_controls = []
211
+
212
+ (fields + checkboxes).sort.each do |f|
213
+ case f
214
+ when Mechanize::Form::CheckBox
215
+ if f.checked
216
+ successful_controls << f
127
217
  end
218
+ when Mechanize::Form::Field
219
+ successful_controls << f
128
220
  end
129
221
  end
130
222
 
131
- # Fetch the value of the first input field with the name passed in
132
- # ==Example
133
- # Fetch the value set in the input field 'name'
134
- # puts form['name']
135
- def [](field_name)
136
- f = field(field_name)
137
- f && f.value
223
+ radio_groups = {}
224
+ radiobuttons.each do |f|
225
+ fname = from_native_charset(f.name)
226
+ radio_groups[fname] ||= []
227
+ radio_groups[fname] << f
138
228
  end
139
229
 
140
- # Set the value of the first input field with the name passed in
141
- # ==Example
142
- # Set the value in the input field 'name' to "Aaron"
143
- # form['name'] = 'Aaron'
144
- def []=(field_name, value)
145
- f = field(field_name)
146
- if f
147
- f.value = value
148
- else
149
- add_field!(field_name, value)
230
+ # take one radio button from each group
231
+ radio_groups.each_value do |g|
232
+ checked = g.select {|f| f.checked}
233
+
234
+ if checked.size == 1
235
+ f = checked.first
236
+ successful_controls << f
237
+ elsif checked.size > 1
238
+ raise Mechanize::Error,
239
+ "multiple radiobuttons are checked in the same group!"
150
240
  end
151
241
  end
152
242
 
153
- # Treat form fields like accessors.
154
- def method_missing(meth, *args)
155
- method = meth.to_s.gsub(/=$/, '')
156
-
157
- if field(method)
158
- return field(method).value if args.empty?
159
- return field(method).value = args[0]
160
- end
243
+ @clicked_buttons.each { |b|
244
+ successful_controls << b
245
+ }
161
246
 
162
- super
247
+ successful_controls.sort.each do |ctrl| # DOM order
248
+ qval = proc_query(ctrl)
249
+ query.push(*qval)
163
250
  end
164
251
 
165
- # Submit this form with the button passed in
166
- def submit button=nil, headers = {}
167
- @mech.submit(self, button, headers)
168
- end
252
+ query
253
+ end
169
254
 
170
- # Submit form using +button+. Defaults
171
- # to the first button.
172
- def click_button(button = buttons.first)
173
- submit(button)
174
- end
255
+ # This method adds a button to the query. If the form needs to be
256
+ # submitted with multiple buttons, pass each button to this method.
257
+ def add_button_to_query(button)
258
+ @clicked_buttons << button
259
+ end
175
260
 
176
- # This method is sub-method of build_query.
177
- # It converts charset of query value of fields into expected one.
178
- def proc_query(field)
179
- return unless field.query_value
180
- field.query_value.map{|(name, val)|
181
- [from_native_charset(name), from_native_charset(val.to_s)]
182
- }
261
+ # This method calculates the request data to be sent back to the server
262
+ # for this form, depending on if this is a regular post, get, or a
263
+ # multi-part post,
264
+ def request_data
265
+ query_params = build_query()
266
+
267
+ case @enctype.downcase
268
+ when /^multipart\/form-data/
269
+ boundary = rand_string(20)
270
+ @enctype = "multipart/form-data; boundary=#{boundary}"
271
+
272
+ params = query_params.map do |k,v|
273
+ param_to_multipart(k, v) if k
274
+ end.compact
275
+
276
+ params.concat @file_uploads.map { |f| file_to_multipart(f) }
277
+
278
+ params.map do |part|
279
+ part.force_encoding('ASCII-8BIT') if part.respond_to? :force_encoding
280
+ "--#{boundary}\r\n#{part}"
281
+ end.join('') +
282
+ "--#{boundary}--\r\n"
283
+ else
284
+ Mechanize::Util.build_query_string(query_params)
183
285
  end
184
- private :proc_query
286
+ end
185
287
 
186
- def from_native_charset str
187
- Util.from_native_charset(str, encoding, @ignore_encoding_error, @mech && @mech.log)
188
- end
189
- private :from_native_charset
190
-
191
- # This method builds an array of arrays that represent the query
192
- # parameters to be used with this form. The return value can then
193
- # be used to create a query string for this form.
194
- def build_query(buttons = [])
195
- query = []
196
- @mech.log.info("form encoding: #{encoding}") if @mech && @mech.log
197
-
198
- (fields + checkboxes).sort.each do |f|
199
- case f
200
- when Form::CheckBox
201
- if f.checked
202
- qval = proc_query(f)
203
- query.push(*qval)
204
- end
205
- when Form::Field
206
- qval = proc_query(f)
207
- query.push(*qval)
208
- end
209
- end
288
+ # Removes all fields with name +field_name+.
289
+ def delete_field!(field_name)
290
+ @fields.delete_if{ |f| f.name == field_name}
291
+ end
210
292
 
211
- radio_groups = {}
212
- radiobuttons.each do |f|
213
- fname = from_native_charset(f.name)
214
- radio_groups[fname] ||= []
215
- radio_groups[fname] << f
216
- end
293
+ ##
294
+ # :method: field_with(criteria)
295
+ #
296
+ # Find one field that matches +criteria+
297
+ # Example:
298
+ # form.field_with(:id => "exact_field_id").value = 'hello'
217
299
 
218
- # take one radio button from each group
219
- radio_groups.each_value do |g|
220
- checked = g.select {|f| f.checked}
221
-
222
- if checked.size == 1
223
- f = checked.first
224
- qval = proc_query(f)
225
- query.push(*qval)
226
- elsif checked.size > 1
227
- raise Mechanize::Error,
228
- "multiple radiobuttons are checked in the same group!"
229
- end
230
- end
300
+ ##
301
+ # :method: fields_with(criteria)
302
+ #
303
+ # Find all fields that match +criteria+
304
+ # Example:
305
+ # form.fields_with(:value => /foo/).each do |field|
306
+ # field.value = 'hello!'
307
+ # end
231
308
 
232
- @clicked_buttons.each { |b|
233
- qval = proc_query(b)
234
- query.push(*qval)
235
- }
236
- query
237
- end
309
+ elements_with :field
238
310
 
239
- # This method adds a button to the query. If the form needs to be
240
- # submitted with multiple buttons, pass each button to this method.
241
- def add_button_to_query(button)
242
- @clicked_buttons << button
243
- end
311
+ ##
312
+ # :method: button_with(criteria)
313
+ #
314
+ # Find one button that matches +criteria+
315
+ # Example:
316
+ # form.button_with(:value => /submit/).value = 'hello'
244
317
 
245
- # This method calculates the request data to be sent back to the server
246
- # for this form, depending on if this is a regular post, get, or a
247
- # multi-part post,
248
- def request_data
249
- query_params = build_query()
318
+ ##
319
+ # :method: buttons_with(criteria)
320
+ #
321
+ # Find all buttons that match +criteria+
322
+ # Example:
323
+ # form.buttons_with(:value => /submit/).each do |button|
324
+ # button.value = 'hello!'
325
+ # end
250
326
 
251
- case @enctype.downcase
252
- when /^multipart\/form-data/
253
- boundary = rand_string(20)
254
- @enctype = "multipart/form-data; boundary=#{boundary}"
327
+ elements_with :button
255
328
 
256
- params = query_params.map do |k,v|
257
- param_to_multipart(k, v) if k
258
- end.compact
329
+ ##
330
+ # :method: file_upload_with(criteria)
331
+ #
332
+ # Find one file upload field that matches +criteria+
333
+ # Example:
334
+ # form.file_upload_with(:file_name => /picture/).value = 'foo'
259
335
 
260
- params.concat @file_uploads.map { |f| file_to_multipart(f) }
336
+ ##
337
+ # :method: file_uploads_with(criteria)
338
+ #
339
+ # Find all file upload fields that match +criteria+
340
+ # Example:
341
+ # form.file_uploads_with(:file_name => /picutre/).each do |field|
342
+ # field.value = 'foo!'
343
+ # end
261
344
 
262
- params.map do |part|
263
- part.force_encoding('ASCII-8BIT') if part.respond_to? :force_encoding
264
- "--#{boundary}\r\n#{part}"
265
- end.join('') +
266
- "--#{boundary}--\r\n"
267
- else
268
- Mechanize::Util.build_query_string(query_params)
269
- end
270
- end
345
+ elements_with :file_upload
271
346
 
272
- # Removes all fields with name +field_name+.
273
- def delete_field!(field_name)
274
- @fields.delete_if{ |f| f.name == field_name}
275
- end
347
+ ##
348
+ # :method: radiobutton_with(criteria)
349
+ #
350
+ # Find one radio button that matches +criteria+
351
+ # Example:
352
+ # form.radiobutton_with(:name => /woo/).check
276
353
 
277
- ##
278
- # :method: field_with(criteria)
279
- #
280
- # Find one field that matches +criteria+
281
- # Example:
282
- # form.field_with(:id => "exact_field_id").value = 'hello'
283
-
284
- ##
285
- # :method: fields_with(criteria)
286
- #
287
- # Find all fields that match +criteria+
288
- # Example:
289
- # form.fields_with(:value => /foo/).each do |field|
290
- # field.value = 'hello!'
291
- # end
292
-
293
- elements_with :field
294
-
295
- ##
296
- # :method: button_with(criteria)
297
- #
298
- # Find one button that matches +criteria+
299
- # Example:
300
- # form.button_with(:value => /submit/).value = 'hello'
301
-
302
- ##
303
- # :method: buttons_with(criteria)
304
- #
305
- # Find all buttons that match +criteria+
306
- # Example:
307
- # form.buttons_with(:value => /submit/).each do |button|
308
- # button.value = 'hello!'
309
- # end
310
-
311
- elements_with :button
312
-
313
- ##
314
- # :method: file_upload_with(criteria)
315
- #
316
- # Find one file upload field that matches +criteria+
317
- # Example:
318
- # form.file_upload_with(:file_name => /picture/).value = 'foo'
319
-
320
- ##
321
- # :method: file_uploads_with(criteria)
322
- #
323
- # Find all file upload fields that match +criteria+
324
- # Example:
325
- # form.file_uploads_with(:file_name => /picutre/).each do |field|
326
- # field.value = 'foo!'
327
- # end
328
-
329
- elements_with :file_upload
330
-
331
- ##
332
- # :method: radiobutton_with(criteria)
333
- #
334
- # Find one radio button that matches +criteria+
335
- # Example:
336
- # form.radiobutton_with(:name => /woo/).check
337
-
338
- ##
339
- # :method: radiobuttons_with(criteria)
340
- #
341
- # Find all radio buttons that match +criteria+
342
- # Example:
343
- # form.radiobuttons_with(:name => /woo/).each do |field|
344
- # field.check
345
- # end
346
-
347
- elements_with :radiobutton
348
-
349
- ##
350
- # :method: checkbox_with(criteria)
351
- #
352
- # Find one checkbox that matches +criteria+
353
- # Example:
354
- # form.checkbox_with(:name => /woo/).check
355
-
356
- ##
357
- # :method: checkboxes_with(criteria)
358
- #
359
- # Find all checkboxes that match +criteria+
360
- # Example:
361
- # form.checkboxes_with(:name => /woo/).each do |field|
362
- # field.check
363
- # end
364
-
365
- elements_with :checkbox, :checkboxes
366
-
367
- private
368
-
369
- def parse
370
- @fields = []
371
- @buttons = []
372
- @file_uploads = []
373
- @radiobuttons = []
374
- @checkboxes = []
375
-
376
- # Find all input tags
377
- form_node.search('input').each do |node|
378
- type = (node['type'] || 'text').downcase
379
- name = node['name']
380
- next if name.nil? && !%w[submit button image].include?(type)
381
- case type
382
- when 'radio'
383
- @radiobuttons << RadioButton.new(node, self)
384
- when 'checkbox'
385
- @checkboxes << CheckBox.new(node, self)
386
- when 'file'
387
- @file_uploads << FileUpload.new(node, nil)
388
- when 'submit'
389
- @buttons << Submit.new(node)
390
- when 'button'
391
- @buttons << Button.new(node)
392
- when 'reset'
393
- @buttons << Reset.new(node)
394
- when 'image'
395
- @buttons << ImageButton.new(node)
396
- when 'hidden'
397
- @fields << Hidden.new(node, node['value'] || '')
398
- when 'text'
399
- @fields << Text.new(node, node['value'] || '')
400
- when 'textarea'
401
- @fields << Textarea.new(node, node['value'] || '')
402
- else
403
- @fields << Field.new(node, node['value'] || '')
404
- end
405
- end
354
+ ##
355
+ # :method: radiobuttons_with(criteria)
356
+ #
357
+ # Find all radio buttons that match +criteria+
358
+ # Example:
359
+ # form.radiobuttons_with(:name => /woo/).each do |field|
360
+ # field.check
361
+ # end
406
362
 
407
- # Find all textarea tags
408
- form_node.search('textarea').each do |node|
409
- next unless node['name']
410
- @fields << Textarea.new(node, node.inner_text)
411
- end
363
+ elements_with :radiobutton
364
+
365
+ ##
366
+ # :method: checkbox_with(criteria)
367
+ #
368
+ # Find one checkbox that matches +criteria+
369
+ # Example:
370
+ # form.checkbox_with(:name => /woo/).check
412
371
 
413
- # Find all select tags
414
- form_node.search('select').each do |node|
415
- next unless node['name']
416
- if node.has_attribute? 'multiple'
417
- @fields << MultiSelectList.new(node)
418
- else
419
- @fields << SelectList.new(node)
372
+ ##
373
+ # :method: checkboxes_with(criteria)
374
+ #
375
+ # Find all checkboxes that match +criteria+
376
+ # Example:
377
+ # form.checkboxes_with(:name => /woo/).each do |field|
378
+ # field.check
379
+ # end
380
+
381
+ elements_with :checkbox, :checkboxes
382
+
383
+ def pretty_print(q) # :nodoc:
384
+ q.object_group(self) {
385
+ q.breakable; q.group(1, '{name', '}') { q.breakable; q.pp name }
386
+ q.breakable; q.group(1, '{method', '}') { q.breakable; q.pp method }
387
+ q.breakable; q.group(1, '{action', '}') { q.breakable; q.pp action }
388
+ q.breakable; q.group(1, '{fields', '}') {
389
+ fields.each do |field|
390
+ q.breakable
391
+ q.pp field
420
392
  end
421
- end
393
+ }
394
+ q.breakable; q.group(1, '{radiobuttons', '}') {
395
+ radiobuttons.each { |b| q.breakable; q.pp b }
396
+ }
397
+ q.breakable; q.group(1, '{checkboxes', '}') {
398
+ checkboxes.each { |b| q.breakable; q.pp b }
399
+ }
400
+ q.breakable; q.group(1, '{file_uploads', '}') {
401
+ file_uploads.each { |b| q.breakable; q.pp b }
402
+ }
403
+ q.breakable; q.group(1, '{buttons', '}') {
404
+ buttons.each { |b| q.breakable; q.pp b }
405
+ }
406
+ }
407
+ end
422
408
 
423
- # Find all submit button tags
424
- # FIXME: what can I do with the reset buttons?
425
- form_node.search('button').each do |node|
426
- type = (node['type'] || 'submit').downcase
427
- next if type == 'reset'
409
+ alias inspect pretty_inspect # :nodoc:
410
+
411
+ private
412
+
413
+ def parse
414
+ @fields = []
415
+ @buttons = []
416
+ @file_uploads = []
417
+ @radiobuttons = []
418
+ @checkboxes = []
419
+
420
+ # Find all input tags
421
+ form_node.search('input').each do |node|
422
+ type = (node['type'] || 'text').downcase
423
+ name = node['name']
424
+ next if name.nil? && !%w[submit button image].include?(type)
425
+ case type
426
+ when 'radio'
427
+ @radiobuttons << RadioButton.new(node, self)
428
+ when 'checkbox'
429
+ @checkboxes << CheckBox.new(node, self)
430
+ when 'file'
431
+ @file_uploads << FileUpload.new(node, nil)
432
+ when 'submit'
433
+ @buttons << Submit.new(node)
434
+ when 'button'
428
435
  @buttons << Button.new(node)
436
+ when 'reset'
437
+ @buttons << Reset.new(node)
438
+ when 'image'
439
+ @buttons << ImageButton.new(node)
440
+ when 'hidden'
441
+ @fields << Hidden.new(node, node['value'] || '')
442
+ when 'text'
443
+ @fields << Text.new(node, node['value'] || '')
444
+ when 'textarea'
445
+ @fields << Textarea.new(node, node['value'] || '')
446
+ else
447
+ @fields << Field.new(node, node['value'] || '')
429
448
  end
430
449
  end
431
450
 
432
- def rand_string(len = 10)
433
- chars = ("a".."z").to_a + ("A".."Z").to_a
434
- string = ""
435
- 1.upto(len) { |i| string << chars[rand(chars.size-1)] }
436
- string
451
+ # Find all textarea tags
452
+ form_node.search('textarea').each do |node|
453
+ next unless node['name']
454
+ @fields << Textarea.new(node, node.inner_text)
437
455
  end
438
456
 
439
- def mime_value_quote(str)
440
- str.gsub(/(["\r\\])/){|s| '\\' + s}
457
+ # Find all select tags
458
+ form_node.search('select').each do |node|
459
+ next unless node['name']
460
+ if node.has_attribute? 'multiple'
461
+ @fields << MultiSelectList.new(node)
462
+ else
463
+ @fields << SelectList.new(node)
464
+ end
441
465
  end
442
466
 
443
- def param_to_multipart(name, value)
444
- return "Content-Disposition: form-data; name=\"" +
445
- "#{mime_value_quote(name)}\"\r\n" +
446
- "\r\n#{value}\r\n"
467
+ # Find all submit button tags
468
+ # FIXME: what can I do with the reset buttons?
469
+ form_node.search('button').each do |node|
470
+ type = (node['type'] || 'submit').downcase
471
+ next if type == 'reset'
472
+ @buttons << Button.new(node)
447
473
  end
474
+
475
+ # Find all keygen tags
476
+ form_node.search('keygen').each do |node|
477
+ @fields << Keygen.new(node, node['value'] || '')
478
+ end
479
+ end
448
480
 
449
- def file_to_multipart(file)
450
- file_name = file.file_name ? ::File.basename(file.file_name) : ''
451
- body = "Content-Disposition: form-data; name=\"" +
452
- "#{mime_value_quote(file.name)}\"; " +
453
- "filename=\"#{mime_value_quote(file_name)}\"\r\n" +
454
- "Content-Transfer-Encoding: binary\r\n"
455
-
456
- if file.file_data.nil? and file.file_name
457
- file.file_data = open(file.file_name, "rb") { |f| f.read }
458
- file.mime_type =
459
- WEBrick::HTTPUtils.mime_type(file.file_name,
460
- WEBrick::HTTPUtils::DefaultMimeTypes)
461
- end
481
+ def rand_string(len = 10)
482
+ chars = ("a".."z").to_a + ("A".."Z").to_a
483
+ string = ""
484
+ 1.upto(len) { |i| string << chars[rand(chars.size-1)] }
485
+ string
486
+ end
462
487
 
463
- if file.mime_type
464
- body << "Content-Type: #{file.mime_type}\r\n"
465
- end
488
+ def mime_value_quote(str)
489
+ str.gsub(/(["\r\\])/){|s| '\\' + s}
490
+ end
466
491
 
467
- body <<
468
- if file.file_data.respond_to? :read
469
- "\r\n#{file.file_data.read}\r\n"
470
- else
471
- "\r\n#{file.file_data}\r\n"
472
- end
492
+ def param_to_multipart(name, value)
493
+ return "Content-Disposition: form-data; name=\"" +
494
+ "#{mime_value_quote(name)}\"\r\n" +
495
+ "\r\n#{value}\r\n"
496
+ end
473
497
 
474
- body
498
+ def file_to_multipart(file)
499
+ file_name = file.file_name ? ::File.basename(file.file_name) : ''
500
+ body = "Content-Disposition: form-data; name=\"" +
501
+ "#{mime_value_quote(file.name)}\"; " +
502
+ "filename=\"#{mime_value_quote(file_name)}\"\r\n" +
503
+ "Content-Transfer-Encoding: binary\r\n"
504
+
505
+ if file.file_data.nil? and file.file_name
506
+ file.file_data = open(file.file_name, "rb") { |f| f.read }
507
+ file.mime_type =
508
+ WEBrick::HTTPUtils.mime_type(file.file_name,
509
+ WEBrick::HTTPUtils::DefaultMimeTypes)
475
510
  end
476
511
 
512
+ if file.mime_type
513
+ body << "Content-Type: #{file.mime_type}\r\n"
514
+ end
515
+
516
+ body <<
517
+ if file.file_data.respond_to? :read
518
+ "\r\n#{file.file_data.read}\r\n"
519
+ else
520
+ "\r\n#{file.file_data}\r\n"
521
+ end
522
+
523
+ body
477
524
  end
525
+
478
526
  end
527
+
528
+ require 'mechanize/form/field'
529
+ require 'mechanize/form/button'
530
+ require 'mechanize/form/hidden'
531
+ require 'mechanize/form/text'
532
+ require 'mechanize/form/textarea'
533
+ require 'mechanize/form/submit'
534
+ require 'mechanize/form/reset'
535
+ require 'mechanize/form/file_upload'
536
+ require 'mechanize/form/keygen'
537
+ require 'mechanize/form/image_button'
538
+ require 'mechanize/form/multi_select_list'
539
+ require 'mechanize/form/option'
540
+ require 'mechanize/form/radio_button'
541
+ require 'mechanize/form/check_box'
542
+ require 'mechanize/form/select_list'
543
+