Skip to content
This repository
Browse code

Fix several known web encoding issues:

* Specify accept-charset on all forms. All recent browsers,
  as well as IE5+, will use the encoding specified for form
  parameters
* Unfortunately, IE5+ will not look at accept-charset unless
  at least one character in the form's values is not in the
  page's charset. Since the user can override the default
  charset (which Rails sets to UTF-8), we provide a hidden
  input containing a unicode character, forcing IE to look
  at the accept-charset.
* Now that the vast majority of web input is UTF-8, we set
  the inbound parameters to UTF-8. This will eliminate many
  cases of incompatible encodings between ASCII-8BIT and
  UTF-8.
* You can safely ignore params[:_snowman_]

TODO:

* Validate inbound text to confirm it is UTF-8
* Combine the whole_form implementations in form_helper_test
  and form_tag_helper_test
commit 25215d7285db10e2c04d903f251b791342e4dd6a 1 parent 06b0d6e
Yehuda Katz authored June 27, 2010
31  actionpack/lib/action_dispatch/http/parameters.rb
@@ -6,7 +6,11 @@ module Http
6 6
     module Parameters
7 7
       # Returns both GET and POST \parameters in a single hash.
8 8
       def parameters
9  
-        @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
  9
+        @env["action_dispatch.request.parameters"] ||= begin
  10
+          params = request_parameters.merge(query_parameters)
  11
+          params.merge!(path_parameters)
  12
+          encode_params(params).with_indifferent_access
  13
+        end
10 14
       end
11 15
       alias :params :parameters
12 16
 
@@ -32,6 +36,31 @@ def path_parameters
32 36
       end
33 37
 
34 38
     private
  39
+
  40
+      # TODO: Validate that the characters are UTF-8. If they aren't,
  41
+      # you'll get a weird error down the road, but our form handling
  42
+      # should really prevent that from happening
  43
+      def encode_params(params)
  44
+        return params unless "ruby".encoding_aware?
  45
+
  46
+        if params.is_a?(String)
  47
+          return params.force_encoding("UTF-8").encode!
  48
+        elsif !params.is_a?(Hash)
  49
+          return params
  50
+        end
  51
+
  52
+        params.each do |k, v|
  53
+          case v
  54
+          when Hash
  55
+            encode_params(v)
  56
+          when Array
  57
+            v.map! {|el| encode_params(el) }
  58
+          else
  59
+            encode_params(v)
  60
+          end
  61
+        end
  62
+      end
  63
+
35 64
       # Convert nested Hash to HashWithIndifferentAccess
36 65
       def normalize_parameters(value)
37 66
         case value
15  actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -530,22 +530,31 @@ def html_options_for_form(url_for_options, options, *parameters_for_url)
530 530
           returning options.stringify_keys do |html_options|
531 531
             html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
532 532
             html_options["action"]  = url_for(url_for_options, *parameters_for_url)
  533
+            html_options["accept-encoding"] = "UTF-8"
533 534
             html_options["data-remote"] = true if html_options.delete("remote")
534 535
           end
535 536
         end
536 537
 
537 538
         def extra_tags_for_form(html_options)
538  
-          case method = html_options.delete("method").to_s
  539
+          snowman_tag = tag(:input, :type => "hidden",
  540
+                            :name => "_snowman_", :value => "☃")
  541
+
  542
+          method = html_options.delete("method").to_s
  543
+
  544
+          method_tag = case method
539 545
             when /^get$/i # must be case-insensitive, but can't use downcase as might be nil
540 546
               html_options["method"] = "get"
541 547
               ''
542 548
             when /^post$/i, "", nil
543 549
               html_options["method"] = "post"
544  
-              protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0;display:inline') : ''
  550
+              token_tag
545 551
             else
546 552
               html_options["method"] = "post"
547  
-              content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0;display:inline')
  553
+              tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag
548 554
           end
  555
+
  556
+          tags = snowman_tag << method_tag
  557
+          content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
549 558
         end
550 559
 
551 560
         def form_tag_html(html_options)
23  actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -141,6 +141,29 @@ def assert_parses(expected, actual)
141 141
         post "/parse", actual
142 142
         assert_response :ok
143 143
         assert_equal(expected, TestController.last_request_parameters)
  144
+        assert_utf8(TestController.last_request_parameters)
  145
+      end
  146
+    end
  147
+
  148
+    def assert_utf8(object)
  149
+      return unless "ruby".encoding_aware?
  150
+
  151
+      correct_encoding = Encoding.default_internal
  152
+
  153
+      unless object.is_a?(Hash)
  154
+        assert_equal correct_encoding, object.encoding, "#{object.inspect} should have been UTF-8"
  155
+        return
  156
+      end
  157
+
  158
+      object.each do |k,v|
  159
+        case v
  160
+        when Hash
  161
+          assert_utf8(v)
  162
+        when Array
  163
+          v.each {|el| assert_utf8(el) }
  164
+        else
  165
+          assert_utf8(v)
  166
+        end
144 167
       end
145 168
     end
146 169
 end
2  actionpack/test/template/erb/form_for_test.rb
@@ -5,7 +5,7 @@ module ERBTest
5 5
   class TagHelperTest < BlockTestCase
6 6
     test "form_for works" do
7 7
       output = render_content "form_for(:staticpage, :url => {:controller => 'blah', :action => 'update'})", ""
8  
-      assert_equal "<form action=\"/blah/update\" method=\"post\"></form>", output
  8
+      assert_match %r{<form.*action="/blah/update".*method="post">.*</form>}, output
9 9
     end
10 10
   end
11 11
 end
4  actionpack/test/template/erb/tag_helper_test.rb
@@ -28,8 +28,8 @@ def maybe_deprecated
28 28
     end
29 29
 
30 30
     test "percent equals works with form tags" do
31  
-      expected_output = "<form action=\"foo\" method=\"post\">hello</form>"
32  
-      maybe_deprecated { assert_equal expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") }
  31
+      expected_output = %r{<form.*action="foo".*method="post">.*hello*</form>}
  32
+      maybe_deprecated { assert_match expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") }
33 33
     end
34 34
 
35 35
     test "percent equals works with fieldset tags" do
385  actionpack/test/template/form_helper_test.rb
@@ -583,7 +583,8 @@ def test_form_for
583 583
     end
584 584
 
585 585
     expected =
586  
-      "<form action='http://www.example.com' id='create-post' method='post'>" +
  586
+      "<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" +
  587
+      snowman +
587 588
       "<label for='post_title'>The Title</label>" +
588 589
       "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
589 590
       "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
@@ -604,15 +605,14 @@ def test_form_for_with_symbol_object_name
604 605
       concat f.submit('Create post')
605 606
     end
606 607
 
607  
-    expected =
608  
-      "<form class='other_name_edit' method='post' action='/posts/123' id='create-post'>" +
609  
-      "<div style='margin:0;padding:0;display:inline'><input name='_method' value='put' type='hidden' /></div>" +
  608
+    expected =  whole_form("/posts/123", "create-post", "other_name_edit", :method => "put") do
610 609
       "<label for='other_name_title'>Title</label>" +
611 610
       "<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" +
612 611
       "<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
613 612
       "<input name='other_name[secret]' value='0' type='hidden' />" +
614 613
       "<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" +
615  
-      "<input name='commit' id='other_name_submit' value='Create post' type='submit' /></form>"
  614
+      "<input name='commit' id='other_name_submit' value='Create post' type='submit' />"
  615
+    end
616 616
 
617 617
     assert_dom_equal expected, output_buffer
618 618
   end
@@ -626,14 +626,12 @@ def test_form_for_with_method
626 626
       end
627 627
     end
628 628
 
629  
-    expected =
630  
-      "<form action='http://www.example.com' id='create-post' method='post'>" +
631  
-      "<div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div>" +
  629
+    expected =  whole_form("http://www.example.com", "create-post", nil, "put") do
632 630
       "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
633 631
       "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
634 632
       "<input name='post[secret]' type='hidden' value='0' />" +
635  
-      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
636  
-      "</form>"
  633
+      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
  634
+    end
637 635
 
638 636
     assert_dom_equal expected, output_buffer
639 637
   end
@@ -647,14 +645,12 @@ def test_form_for_with_remote
647 645
       end
648 646
     end
649 647
 
650  
-    expected =
651  
-      "<form action='http://www.example.com' id='create-post' method='post' data-remote='true'>" +
652  
-      "<div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div>" +
  648
+    expected =  whole_form("http://www.example.com", "create-post", nil, :method => "put", :remote => true) do
653 649
       "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
654 650
       "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
655 651
       "<input name='post[secret]' type='hidden' value='0' />" +
656  
-      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
657  
-      "</form>"
  652
+      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
  653
+    end
658 654
 
659 655
     assert_dom_equal expected, output_buffer
660 656
   end
@@ -668,13 +664,12 @@ def test_form_for_with_remote_without_html
668 664
       end
669 665
     end
670 666
 
671  
-    expected =
672  
-      "<form action='http://www.example.com' method='post' data-remote='true'>" +
  667
+    expected =  whole_form("http://www.example.com", nil, nil, :remote => true) do
673 668
       "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
674 669
       "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
675 670
       "<input name='post[secret]' type='hidden' value='0' />" +
676  
-      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
677  
-      "</form>"
  671
+      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
  672
+    end
678 673
 
679 674
     assert_dom_equal expected, output_buffer
680 675
   end
@@ -686,13 +681,12 @@ def test_form_for_without_object
686 681
       concat f.check_box(:secret)
687 682
     end
688 683
 
689  
-    expected =
690  
-      "<form action='http://www.example.com' id='create-post' method='post'>" +
  684
+    expected =  whole_form("http://www.example.com", "create-post") do
691 685
       "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
692 686
       "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
693 687
       "<input name='post[secret]' type='hidden' value='0' />" +
694  
-      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
695  
-      "</form>"
  688
+      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
  689
+    end
696 690
 
697 691
     assert_dom_equal expected, output_buffer
698 692
   end
@@ -707,14 +701,13 @@ def test_form_for_with_index
707 701
       end
708 702
     end
709 703
 
710  
-    expected =
711  
-      "<form action='http://www.example.com' method='post'>" +
  704
+    expected = whole_form do
712 705
       "<label for='post_123_title'>Title</label>" +
713 706
       "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
714 707
       "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
715 708
       "<input name='post[123][secret]' type='hidden' value='0' />" +
716  
-      "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" +
717  
-      "</form>"
  709
+      "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />"
  710
+    end
718 711
 
719 712
     assert_dom_equal expected, output_buffer
720 713
   end
@@ -728,13 +721,12 @@ def test_form_for_with_nil_index_option_override
728 721
       end
729 722
     end
730 723
 
731  
-    expected =
732  
-      "<form action='http://www.example.com' method='post'>" +
  724
+    expected = whole_form do
733 725
       "<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" +
734 726
       "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
735 727
       "<input name='post[][secret]' type='hidden' value='0' />" +
736  
-      "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" +
737  
-      "</form>"
  728
+      "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />"
  729
+    end
738 730
 
739 731
     assert_dom_equal expected, output_buffer
740 732
   end
@@ -749,9 +741,10 @@ def test_submit_with_object_as_new_record_and_locale_strings
749 741
       end
750 742
     end
751 743
 
752  
-    expected = "<form action='http://www.example.com' method='post'>" +
753  
-               "<input name='commit' id='post_submit' type='submit' value='Create Post' />" +
754  
-               "</form>"
  744
+    expected =  whole_form do
  745
+                  "<input name='commit' id='post_submit' type='submit' value='Create Post' />"
  746
+                end
  747
+
755 748
     assert_dom_equal expected, output_buffer
756 749
   ensure
757 750
     I18n.locale = old_locale
@@ -766,9 +759,10 @@ def test_submit_with_object_as_existing_record_and_locale_strings
766 759
       end
767 760
     end
768 761
 
769  
-    expected = "<form action='http://www.example.com' method='post'>" +
770  
-               "<input name='commit' id='post_submit' type='submit' value='Confirm Post changes' />" +
771  
-               "</form>"
  762
+    expected =  whole_form do
  763
+                  "<input name='commit' id='post_submit' type='submit' value='Confirm Post changes' />"
  764
+                end
  765
+
772 766
     assert_dom_equal expected, output_buffer
773 767
   ensure
774 768
     I18n.locale = old_locale
@@ -781,9 +775,10 @@ def test_submit_without_object_and_locale_strings
781 775
       concat f.submit :class => "extra"
782 776
     end
783 777
 
784  
-    expected = "<form action='http://www.example.com' method='post'>" +
785  
-               "<input name='commit' class='extra' id='post_submit' type='submit' value='Save changes' />" +
786  
-               "</form>"
  778
+    expected =  whole_form do
  779
+                  "<input name='commit' class='extra' id='post_submit' type='submit' value='Save changes' />"
  780
+                end
  781
+
787 782
     assert_dom_equal expected, output_buffer
788 783
   ensure
789 784
     I18n.locale = old_locale
@@ -798,9 +793,10 @@ def test_submit_with_object_and_nested_lookup
798 793
       end
799 794
     end
800 795
 
801  
-    expected = "<form action='http://www.example.com' method='post'>" +
802  
-               "<input name='commit' id='another_post_submit' type='submit' value='Update your Post' />" +
803  
-               "</form>"
  796
+    expected =  whole_form do
  797
+                  "<input name='commit' id='another_post_submit' type='submit' value='Update your Post' />"
  798
+                end
  799
+
804 800
     assert_dom_equal expected, output_buffer
805 801
   ensure
806 802
     I18n.locale = old_locale
@@ -815,9 +811,9 @@ def test_nested_fields_for
815 811
       end
816 812
     end
817 813
 
818  
-    expected = "<form action='http://www.example.com' method='post'>" +
819  
-               "<input name='post[comment][title]' size='30' type='text' id='post_comment_title' value='Hello World' />" +
820  
-               "</form>"
  814
+    expected =  whole_form do
  815
+                  "<input name='post[comment][title]' size='30' type='text' id='post_comment_title' value='Hello World' />"
  816
+                end
821 817
 
822 818
     assert_dom_equal expected, output_buffer
823 819
   end
@@ -832,10 +828,10 @@ def test_nested_fields_for_with_nested_collections
832 828
       end
833 829
     end
834 830
 
835  
-    expected = "<form action='http://www.example.com' method='post'>" +
836  
-               "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
837  
-               "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />" +
838  
-               "</form>"
  831
+    expected =  whole_form do
  832
+                  "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
  833
+                  "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />"
  834
+                end
839 835
 
840 836
     assert_dom_equal expected, output_buffer
841 837
   end
@@ -850,10 +846,10 @@ def test_nested_fields_for_with_index_and_parent_fields
850 846
       end
851 847
     end
852 848
 
853  
-    expected = "<form action='http://www.example.com' method='post'>" +
854  
-               "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" +
855  
-               "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />" +
856  
-               "</form>"
  849
+    expected =  whole_form do
  850
+                  "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" +
  851
+                  "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />"
  852
+                end
857 853
 
858 854
     assert_dom_equal expected, output_buffer
859 855
   end
@@ -867,9 +863,9 @@ def test_form_for_with_index_and_nested_fields_for
867 863
       end
868 864
     end
869 865
 
870  
-    expected = "<form action='http://www.example.com' method='post'>" +
871  
-               "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />" +
872  
-               "</form>"
  866
+    expected =  whole_form do
  867
+                  "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />"
  868
+                end
873 869
 
874 870
     assert_dom_equal expected, output_buffer
875 871
   end
@@ -883,9 +879,9 @@ def test_nested_fields_for_with_index_on_both
883 879
       end
884 880
     end
885 881
 
886  
-    expected = "<form action='http://www.example.com' method='post'>" +
887  
-               "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />" +
888  
-               "</form>"
  882
+    expected =  whole_form do
  883
+                  "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />"
  884
+                end
889 885
 
890 886
     assert_dom_equal expected, output_buffer
891 887
   end
@@ -899,9 +895,9 @@ def test_nested_fields_for_with_auto_index
899 895
       end
900 896
     end
901 897
 
902  
-    expected = "<form action='http://www.example.com' method='post'>" +
903  
-               "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />" +
904  
-               "</form>"
  898
+    expected =  whole_form do
  899
+                  "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />"
  900
+                end
905 901
 
906 902
     assert_dom_equal expected, output_buffer
907 903
   end
@@ -915,9 +911,9 @@ def test_nested_fields_for_with_index_radio_button
915 911
       end
916 912
     end
917 913
 
918  
-    expected = "<form action='http://www.example.com' method='post'>" +
919  
-               "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" +
920  
-               "</form>"
  914
+    expected =  whole_form do
  915
+                  "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />"
  916
+                end
921 917
 
922 918
     assert_dom_equal expected, output_buffer
923 919
   end
@@ -931,9 +927,9 @@ def test_nested_fields_for_with_auto_index_on_both
931 927
       end
932 928
     end
933 929
 
934  
-    expected = "<form action='http://www.example.com' method='post'>" +
935  
-               "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />" +
936  
-               "</form>"
  930
+    expected =  whole_form do
  931
+                  "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />"
  932
+                end
937 933
 
938 934
     assert_dom_equal expected, output_buffer
939 935
   end
@@ -952,12 +948,11 @@ def test_nested_fields_for_with_index_and_auto_index
952 948
         }
953 949
       end
954 950
 
955  
-      expected = "<form action='http://www.example.com' method='post'>" +
956  
-                 "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />" +
957  
-                 "</form>" +
958  
-                 "<form action='http://www.example.com' method='post'>" +
959  
-                 "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />" +
960  
-                 "</form>"
  951
+      expected =  whole_form do
  952
+                    "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />"
  953
+                  end + whole_form do
  954
+                    "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />"
  955
+                  end
961 956
 
962 957
       assert_dom_equal expected, output_buffer
963 958
     end
@@ -975,10 +970,10 @@ def test_nested_fields_for_with_a_new_record_on_a_nested_attributes_one_to_one_a
975 970
       end
976 971
     end
977 972
 
978  
-    expected = '<form action="http://www.example.com" method="post">' +
979  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
980  
-               '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />' +
981  
-               '</form>'
  973
+    expected =  whole_form do
  974
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  975
+                  '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />'
  976
+                end
982 977
 
983 978
     assert_dom_equal expected, output_buffer
984 979
   end
@@ -1006,11 +1001,11 @@ def test_nested_fields_for_with_an_existing_record_on_a_nested_attributes_one_to
1006 1001
       end
1007 1002
     end
1008 1003
 
1009  
-    expected = '<form action="http://www.example.com" method="post">' +
1010  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1011  
-               '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
1012  
-               '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
1013  
-               '</form>'
  1004
+    expected =  whole_form do
  1005
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1006
+                  '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
  1007
+                  '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
  1008
+                end
1014 1009
 
1015 1010
     assert_dom_equal expected, output_buffer
1016 1011
   end
@@ -1028,11 +1023,11 @@ def test_nested_fields_for_with_existing_records_on_a_nested_attributes_one_to_o
1028 1023
       end
1029 1024
     end
1030 1025
 
1031  
-    expected = '<form action="http://www.example.com" method="post">' +
1032  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1033  
-               '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
1034  
-               '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
1035  
-               '</form>'
  1026
+    expected =  whole_form do
  1027
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1028
+                  '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
  1029
+                  '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />'
  1030
+                end
1036 1031
 
1037 1032
     assert_dom_equal expected, output_buffer
1038 1033
   end
@@ -1051,13 +1046,13 @@ def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collecti
1051 1046
       end
1052 1047
     end
1053 1048
 
1054  
-    expected = '<form action="http://www.example.com" method="post">' +
1055  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1056  
-               '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
1057  
-               '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
1058  
-               '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
1059  
-               '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
1060  
-               '</form>'
  1049
+    expected =  whole_form do
  1050
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1051
+                  '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
  1052
+                  '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
  1053
+                  '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
  1054
+                  '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
  1055
+                end
1061 1056
 
1062 1057
     assert_dom_equal expected, output_buffer
1063 1058
   end
@@ -1077,13 +1072,13 @@ def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collecti
1077 1072
       end
1078 1073
     end
1079 1074
 
1080  
-    expected = '<form action="http://www.example.com" method="post">' +
1081  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1082  
-               '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
1083  
-               '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
1084  
-               '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
1085  
-               '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
1086  
-               '</form>'
  1075
+    expected =  whole_form do
  1076
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1077
+                  '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
  1078
+                  '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
  1079
+                  '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
  1080
+                  '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />'
  1081
+                end
1087 1082
 
1088 1083
     assert_dom_equal expected, output_buffer
1089 1084
   end
@@ -1102,11 +1097,11 @@ def test_nested_fields_for_with_new_records_on_a_nested_attributes_collection_as
1102 1097
       end
1103 1098
     end
1104 1099
 
1105  
-    expected = '<form action="http://www.example.com" method="post">' +
1106  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1107  
-               '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' +
1108  
-               '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' +
1109  
-               '</form>'
  1100
+    expected =  whole_form do
  1101
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1102
+                  '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' +
  1103
+                  '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
  1104
+                end
1110 1105
 
1111 1106
     assert_dom_equal expected, output_buffer
1112 1107
   end
@@ -1125,12 +1120,12 @@ def test_nested_fields_for_with_existing_and_new_records_on_a_nested_attributes_
1125 1120
       end
1126 1121
     end
1127 1122
 
1128  
-    expected = '<form action="http://www.example.com" method="post">' +
1129  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1130  
-               '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
1131  
-               '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
1132  
-               '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' +
1133  
-               '</form>'
  1123
+    expected =  whole_form do
  1124
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1125
+                  '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
  1126
+                  '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
  1127
+                  '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
  1128
+                end
1134 1129
 
1135 1130
     assert_dom_equal expected, output_buffer
1136 1131
   end
@@ -1145,9 +1140,9 @@ def test_nested_fields_for_with_an_empty_supplied_attributes_collection
1145 1140
       end
1146 1141
     end
1147 1142
 
1148  
-    expected = '<form action="http://www.example.com" method="post">' +
1149  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1150  
-               '</form>'
  1143
+    expected =  whole_form do
  1144
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />'
  1145
+                end
1151 1146
 
1152 1147
     assert_dom_equal expected, output_buffer
1153 1148
   end
@@ -1164,13 +1159,13 @@ def test_nested_fields_for_with_existing_records_on_a_supplied_nested_attributes
1164 1159
       end
1165 1160
     end
1166 1161
 
1167  
-    expected = '<form action="http://www.example.com" method="post">' +
1168  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1169  
-               '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
1170  
-               '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
1171  
-               '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
1172  
-               '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
1173  
-               '</form>'
  1162
+    expected =  whole_form do
  1163
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1164
+                  '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
  1165
+                  '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
  1166
+                  '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
  1167
+                  '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
  1168
+                end
1174 1169
 
1175 1170
     assert_dom_equal expected, output_buffer
1176 1171
   end
@@ -1188,13 +1183,13 @@ def test_nested_fields_for_with_existing_records_on_a_supplied_nested_attributes
1188 1183
       end
1189 1184
     end
1190 1185
 
1191  
-    expected = '<form action="http://www.example.com" method="post">' +
1192  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1193  
-               '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
1194  
-               '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
1195  
-               '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
1196  
-               '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
1197  
-               '</form>'
  1186
+    expected =  whole_form do
  1187
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1188
+                  '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
  1189
+                  '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
  1190
+                  '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
  1191
+                  '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
  1192
+                end
1198 1193
 
1199 1194
     assert_dom_equal expected, output_buffer
1200 1195
   end
@@ -1213,12 +1208,12 @@ def test_nested_fields_for_on_a_nested_attributes_collection_association_yields_
1213 1208
       end
1214 1209
     end
1215 1210
 
1216  
-    expected = '<form action="http://www.example.com" method="post">' +
1217  
-               '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
1218  
-               '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
1219  
-               '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
1220  
-               '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' +
1221  
-               '</form>'
  1211
+    expected =  whole_form do
  1212
+                  '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
  1213
+                  '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
  1214
+                  '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
  1215
+                  '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
  1216
+                end
1222 1217
 
1223 1218
     assert_dom_equal expected, output_buffer
1224 1219
     assert_equal yielded_comments, @post.comments
@@ -1235,10 +1230,10 @@ def test_nested_fields_for_with_child_index_option_override_on_a_nested_attribut
1235 1230
       end
1236 1231
     end
1237 1232
 
1238  
-    expected = '<form action="http://www.example.com" method="post">' +
1239  
-               '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' +
1240  
-               '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' +
1241  
-               '</form>'
  1233
+    expected =  whole_form do
  1234
+                  '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' +
  1235
+                  '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
  1236
+                end
1242 1237
 
1243 1238
     assert_dom_equal expected, output_buffer
1244 1239
   end
@@ -1273,20 +1268,20 @@ def test_nested_fields_uses_unique_indices_for_different_collection_associations
1273 1268
       end
1274 1269
     end
1275 1270
 
1276  
-    expected = '<form action="http://www.example.com" method="post">' +
1277  
-               '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
1278  
-               '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' +
1279  
-               '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
1280  
-               '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
1281  
-               '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' +
1282  
-               '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' +
1283  
-               '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
1284  
-               '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
1285  
-               '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' +
1286  
-               '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' +
1287  
-               '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
1288  
-               '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' +
1289  
-               '</form>'
  1271
+    expected =  whole_form do
  1272
+                  '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
  1273
+                  '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' +
  1274
+                  '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
  1275
+                  '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
  1276
+                  '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' +
  1277
+                  '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' +
  1278
+                  '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
  1279
+                  '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
  1280
+                  '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' +
  1281
+                  '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' +
  1282
+                  '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
  1283
+                  '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />'
  1284
+                end
1290 1285
 
1291 1286
     assert_dom_equal expected, output_buffer
1292 1287
   end
@@ -1426,7 +1421,8 @@ def test_form_for_and_fields_for
1426 1421
     end
1427 1422
 
1428 1423
     expected =
1429  
-      "<form action='http://www.example.com' id='create-post' method='post'>" +
  1424
+      "<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" +
  1425
+      snowman +
1430 1426
       "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
1431 1427
       "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
1432 1428
       "<input name='parent_post[secret]' type='hidden' value='0' />" +
@@ -1449,11 +1445,11 @@ def test_form_for_and_fields_for_with_object
1449 1445
     end
1450 1446
 
1451 1447
     expected =
1452  
-      "<form action='http://www.example.com' id='create-post' method='post'>" +
1453  
-      "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
1454  
-      "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
1455  
-      "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />" +
1456  
-      "</form>"
  1448
+      whole_form("http://www.example.com", "create-post") do
  1449
+        "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
  1450
+        "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
  1451
+        "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />"
  1452
+      end
1457 1453
 
1458 1454
     assert_dom_equal expected, output_buffer
1459 1455
   end
@@ -1477,16 +1473,42 @@ def test_form_for_with_labelled_builder
1477 1473
       end
1478 1474
     end
1479 1475
 
1480  
-    expected =
1481  
-      "<form action='http://www.example.com' method='post'>" +
1482  
-      "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
1483  
-      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
1484  
-      "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" +
1485  
-      "</form>"
  1476
+    expected =  whole_form do
  1477
+        "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
  1478
+        "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
  1479
+        "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
  1480
+      end
1486 1481
 
1487 1482
     assert_dom_equal expected, output_buffer
1488 1483
   end
1489 1484
 
  1485
+  def snowman(method = nil)
  1486
+    txt =  %{<div style="margin:0;padding:0;display:inline">}
  1487
+    txt << %{<input name="_snowman_" type="hidden" value="&#9731;" />}
  1488
+    txt << %{<input name="_method" type="hidden" value="#{method}" />} if method
  1489
+    txt << %{</div>}
  1490
+  end
  1491
+
  1492
+  def form_text(action = "http://www.example.com", id = nil, html_class = nil, remote = nil)
  1493
+    txt =  %{<form accept-charset="UTF-8" action="#{action}"}
  1494
+    txt << %{ data-remote="true"} if remote
  1495
+    txt << %{ class="#{html_class}"} if html_class
  1496
+    txt << %{ id="#{id}"} if id
  1497
+    txt << %{ method="post">}
  1498
+  end
  1499
+
  1500
+  def whole_form(action = "http://www.example.com", id = nil, html_class = nil, options = nil)
  1501
+    contents = block_given? ? yield : ""
  1502
+
  1503
+    if options.is_a?(Hash)
  1504
+      method, remote = options.values_at(:method, :remote)
  1505
+    else
  1506
+      method = options
  1507
+    end
  1508
+
  1509
+    form_text(action, id, html_class, remote) + snowman(method) + contents + "</form>"
  1510
+  end
  1511
+
1490 1512
   def test_default_form_builder
1491 1513
     old_default_form_builder, ActionView::Base.default_form_builder =
1492 1514
       ActionView::Base.default_form_builder, LabelledFormBuilder
@@ -1499,12 +1521,11 @@ def test_default_form_builder
1499 1521
       end
1500 1522
     end
1501 1523
 
1502  
-    expected =
1503  
-      "<form action='http://www.example.com' method='post'>" +
  1524
+    expected =  whole_form do
1504 1525
       "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
1505 1526
       "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
1506  
-      "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" +
1507  
-      "</form>"
  1527
+      "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
  1528
+    end
1508 1529
 
1509 1530
     assert_dom_equal expected, output_buffer
1510 1531
   ensure
@@ -1577,7 +1598,7 @@ def test_form_for_with_html_options_adds_options_to_form_tag
1577 1598
     assert_deprecated do
1578 1599
       form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
1579 1600
     end
1580  
-    expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>"
  1601
+    expected = whole_form("http://www.example.com", "some_form", "some_class")
1581 1602
 
1582 1603
     assert_dom_equal expected, output_buffer
1583 1604
   end
@@ -1587,7 +1608,8 @@ def test_form_for_with_string_url_option
1587 1608
       form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end
1588 1609
     end
1589 1610
 
1590  
-    assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', output_buffer
  1611
+    assert_equal whole_form("http://www.otherdomain.com"), output_buffer
  1612
+    # assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', output_buffer
1591 1613
   end
1592 1614
 
1593 1615
   def test_form_for_with_hash_url_option
@@ -1604,14 +1626,15 @@ def test_form_for_with_record_url_option
1604 1626
       form_for(:post, @post, :url => @post) do |f| end
1605 1627
     end
1606 1628
 
1607  
-    expected = "<form action=\"/posts/123\" method=\"post\"></form>"
  1629
+    expected = whole_form("/posts/123")
  1630
+    # expected = "<form action=\"/posts/123\" method=\"post\"></form>"
1608 1631
     assert_equal expected, output_buffer
1609 1632
   end
1610 1633
 
1611 1634
   def test_form_for_with_existing_object
1612 1635
     form_for(@post) do |f| end
1613 1636
 
1614  
-    expected = "<form action=\"/posts/123\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>"
  1637
+    expected = whole_form("/posts/123", "edit_post_123", "edit_post", "put")
1615 1638
     assert_equal expected, output_buffer
1616 1639
   end
1617 1640
 
@@ -1622,7 +1645,7 @@ def post.id() nil end
1622 1645
 
1623 1646
     form_for(post) do |f| end
1624 1647
 
1625  
-    expected = "<form action=\"/posts\" class=\"new_post\" id=\"new_post\" method=\"post\"></form>"
  1648
+    expected = whole_form("/posts", "new_post", "new_post")
1626 1649
     assert_equal expected, output_buffer
1627 1650
   end
1628 1651
 
@@ -1630,14 +1653,14 @@ def test_form_for_with_existing_object_in_list
1630 1653
     @comment.save
1631 1654
     form_for([@post, @comment]) {}
1632 1655
 
1633  
-    expected = %(<form action="#{comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>)
  1656
+    expected = whole_form(comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put")
1634 1657
     assert_dom_equal expected, output_buffer
1635 1658
   end
1636 1659
 
1637 1660
   def test_form_for_with_new_object_in_list
1638 1661
     form_for([@post, @comment]) {}
1639 1662
 
1640  
-    expected = %(<form action="#{comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>)
  1663
+    expected = whole_form(comments_path(@post), "new_comment", "new_comment")
1641 1664
     assert_dom_equal expected, output_buffer
1642 1665
   end
1643 1666
 
@@ -1645,21 +1668,21 @@ def test_form_for_with_existing_object_and_namespace_in_list
1645 1668
     @comment.save
1646 1669
     form_for([:admin, @post, @comment]) {}
1647 1670
 
1648  
-    expected = %(<form action="#{admin_comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>)
  1671
+    expected = whole_form(admin_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put")
1649 1672
     assert_dom_equal expected, output_buffer
1650 1673
   end
1651 1674
 
1652 1675
   def test_form_for_with_new_object_and_namespace_in_list
1653 1676
     form_for([:admin, @post, @comment]) {}
1654 1677
 
1655  
-    expected = %(<form action="#{admin_comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>)
  1678
+    expected = whole_form(admin_comments_path(@post), "new_comment", "new_comment")
1656 1679
     assert_dom_equal expected, output_buffer
1657 1680
   end
1658 1681
 
1659 1682
   def test_form_for_with_existing_object_and_custom_url
1660 1683
     form_for(@post, :url => "/super_posts") do |f| end
1661 1684
 
1662  
-    expected = "<form action=\"/super_posts\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>"
  1685
+    expected = whole_form("/super_posts", "edit_post_123", "edit_post", "put")
1663 1686
     assert_equal expected, output_buffer
1664 1687
   end
1665 1688
 
56  actionpack/test/template/form_tag_helper_test.rb
@@ -8,6 +8,36 @@ def setup
8 8
     @controller = BasicController.new
9 9
   end
10 10
 
  11
+  def snowman(options = {})
  12
+    method = options[:method]
  13
+
  14
+    txt =  %{<div style="margin:0;padding:0;display:inline">}
  15
+    txt << %{<input name="_snowman_" type="hidden" value="&#9731;" />}
  16
+    txt << %{<input name="_method" type="hidden" value="#{method}" />} if method
  17
+    txt << %{</div>}
  18
+  end
  19
+
  20
+  def form_text(action = "http://www.example.com", options = {})
  21
+    remote, enctype, html_class, id = options.values_at(:remote, :enctype, :html_class, :id)
  22
+
  23
+    txt =  %{<form accept-charset="UTF-8" action="#{action}"}
  24
+    txt << %{ enctype="multipart/form-data"} if enctype
  25
+    txt << %{ data-remote="true"} if remote
  26
+    txt << %{ class="#{html_class}"} if html_class
  27
+    txt << %{ id="#{id}"} if id
  28
+    txt << %{ method="post">}
  29
+  end
  30
+
  31
+  def whole_form(action = "http://www.example.com", options = {})
  32
+    out = form_text(action, options) + snowman(options)
  33
+
  34
+    if block_given?
  35
+      out << yield << "</form>"
  36
+    end
  37
+
  38
+    out
  39
+  end
  40
+
11 41
   def url_for(options)
12 42
     if options.is_a?(Hash)
13 43
       "http://www.example.com"
@@ -31,51 +61,57 @@ def test_check_box_tag_id_sanitized
31 61
 
32 62
   def test_form_tag
33 63
     actual = form_tag
34  
-    expected = %(<form action="http://www.example.com" method="post">)
  64
+    expected = whole_form
35 65
     assert_dom_equal expected, actual
36 66
   end
37 67
 
38 68
   def test_form_tag_multipart
39 69
     actual = form_tag({}, { 'multipart' => true })
40  
-    expected = %(<form action="http://www.example.com" enctype="multipart/form-data" method="post">)
  70
+    expected = whole_form("http://www.example.com", :enctype => true)
41 71
     assert_dom_equal expected, actual
42 72
   end
43 73
 
44 74
   def test_form_tag_with_method_put
45 75
     actual = form_tag({}, { :method => :put })
46  
-    expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="put" /></div>)
  76
+    expected = whole_form("http://www.example.com", :method => :put)
47 77
     assert_dom_equal expected, actual
48 78
   end
49 79
 
50 80
   def test_form_tag_with_method_delete
51 81
     actual = form_tag({}, { :method => :delete })
52  
-    expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="delete" /></div>)
  82
+
  83
+    expected = whole_form("http://www.example.com", :method => :delete)
53 84
     assert_dom_equal expected, actual
54 85
   end
55 86
 
56 87
   def test_form_tag_with_remote
57 88
     actual = form_tag({}, :remote => true)
58  
-    expected = %(<form action="http://www.example.com" method="post" data-remote="true">)
  89
+
  90
+    expected = whole_form("http://www.example.com", :remote => true)
59 91
     assert_dom_equal expected, actual
60 92
   end
61 93
 
62 94
   def test_form_tag_with_remote_false
63 95
     actual = form_tag({}, :remote => false)
64  
-    expected = %(<form action="http://www.example.com" method="post">)
  96
+
  97
+    expected = whole_form
65 98
     assert_dom_equal expected, actual
66 99
   end
67 100
 
68 101
   def test_form_tag_with_block_in_erb
69  
-    output_buffer = form_tag("http://example.com") { concat "Hello world!" }
  102
+    output_buffer = form_tag("http://www.example.com") { concat "Hello world!" }
70 103
 
71  
-    expected = %(<form action="http://example.com" method="post">Hello world!</form>)
  104
+    expected = whole_form { "Hello world!" }
72 105
     assert_dom_equal expected, output_buffer
73 106
   end
74 107
 
75 108
   def test_form_tag_with_block_and_method_in_erb
76  
-    output_buffer = form_tag("http://example.com", :method => :put) { concat "Hello world!" }
  109
+    output_buffer = form_tag("http://www.example.com", :method => :put) { concat "Hello world!" }
  110
+
  111
+    expected = whole_form("http://www.example.com", :method => "put") do
  112
+      "Hello world!"
  113
+    end
77 114
 
78  
-    expected = %(<form action="http://example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="put" /></div>Hello world!</form>)
79 115
     assert_dom_equal expected, output_buffer
80 116
   end
81 117
 

31 notes on commit 25215d7

Jonas Grimfelt

Hmm...what's the motivation behind supporting IE5? Curious as I haven't seen IE5 showing up in any browser stats in years.

José Valim
Owner

IE5+ (emphasis in the +) ;)

Jeremy Walker
iHiD commented on 25215d7 June 28, 2010

Can I suggest documenting the snowman tag? I just saw this in a HTTP trace and panicked somewhat in case my server had been compromised. I just think sending a new variable with every form request is something that people should know about.

Thanks,
iHiD

Jonas Grimfelt

Aha. :)

Alex MacCaw

Woah - was pretty surprised when I saw snowman in my log today.

Pete Nicholls

Paul Campbell

It would actually help to do a (high profile) blog post about this ... I went searching for it when I started seeing snowmen and it was quite hard to track down.

It is the kind of thing that would freak people out, and, dare I say it, make people think of Rails less seriously. I love it, but on the surface, it feels like an immature Easter egg, rather than a cute hack for forcing unicode.

Mark A. Richman

Why haven't we heard about this hackery before?

Yehuda Katz
Owner

This bug exists in IE5, IE6, IE7, and IE8. If the user switches the browser's encoding to Latin-1 (to understand why a user would decide to do something seemingly so crazy, check out this google search: http://www.google.com/search?sourceid=chrome&ie=UTF-8&q=diamond+with+a+question+mark+in+it), any form submission will be sent in Latin-1.

This means that if a user searches for "Ché Guevara", it will come through incorrectly on the server-side. In Ruby 1.9, this will result in an encoding error when the text inevitably makes its way into the regular expression engine. In Ruby 1.8, it will result in broken results for the user.

By creating a parameter that can only be understood by IE as a unicode character, we are forcing IE to look at the accept-charset attribute, which then tells it to encode all of the characters as UTF-8, even ones that can be encoded in Latin-1.

Keep in mind that in Ruby 1.8, it is extremely trivial to get Latin-1 data into your UTF-8 database (since NOTHING in the entire stack checks that the bytes that the user sent at any point are valid UTF-8 characters). As a result, it's extremely common for Ruby applications (and PHP applications, etc. etc.) to exhibit this user-facing bug, and therefore extremely common for users to try to change the encoding as a palliative measure.

All that said, when I wrote this patch, I didn't realize that the name of the parameter would ever appear in a user-facing place (it does with forms that use the GET action, such as search forms). Since it does, we will rename this parameter to _e, and use a more innocuous-looking unicode character.

Mark A. Richman

Oh come on, why not just name it to _ie ;)

Alex MacCaw

Would IE user agent sniffing be a bad idea?

Nicolás Sanguinetti
foca commented on 25215d7 July 27, 2010

I mentioned this in twitter, but why not just set up a middleware that does params.delete(:_snowman_) when it gets to rack? That way end-users will never see this. That, and documenting why you get it on your logs (or maybe removing it even before it hits the logs…) should be enough to keep everyone happy.

Thomas Ingram

Personally I love the silliness of _snowman. _ie is a wonderful choice as well, but _e is strikes me as enterprise-y and boring.

Xavier Noria
Owner
fxn commented on 25215d7 July 27, 2010

@foca renaming is considered because if you send a form with GET, eg a search form, the query string has the snowman. We do not want end-users to have such a prominent strange parameter right there. The fact that it appears in the params hash is not an issue, as "_method" does.

It could be the case that _e is actually called the snowman parameter though :).

aaronchi

I vote for changing snowman to lollipop because it will be more silly and even less corporate-y. That'll teach those suits!

Gotta love open source ;)

Norman Clarke

I think "Frosty" should be the new official Rails mascot.

Pete Nicholls

@paulca Completely agree. Here's one option: I've set up a simple page with information about the Rails snowman people can easily search for. The repo is at http://github.com/Aupajo/rails-snowman-info and the website will be at http://railssnowman.info, once the DNS updates and the CNAME kicks in.

Łukasz Strzałkowski
Collaborator

@Aupajo great idea. But it's no longer snowman but _snowman - checkout this commit: http://github.com/rails/rails/commit/caab17611668ff18a3c8642b2d45b353be5d9691 (with no underscore on the end). I've send you patch via email for it. If you didn't get it, here it is: http://gist.github.com/493743

Jeremy Walker
iHiD commented on 25215d7 July 28, 2010

Would it not make sense just to add a config option to change the name of the parameter. There is always a small risk that whatever is chosen will conflict with an existing app, may be unacceptable in that particular organisation etc. By keeping a default that is well documented (nice work @Aupajo), new users will understand what is going on, but still have control to change it if necessary.

In terms of logs, I think that it should be filtered by default for new apps (in config.filter_parameters), as per :password.

Pete Nicholls

@strzalek Thanks! I've applied your patch.

@ihid I think a company is much more likely to be tripped up by _method than _snowman. If one is acceptable, so should the other be.

Damien Mathieu
Collaborator