actionpack 1.13.6 → 2.0.0

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

Potentially problematic release.


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

Files changed (317) hide show
  1. data/CHANGELOG +1400 -20
  2. data/MIT-LICENSE +1 -1
  3. data/README +5 -5
  4. data/RUNNING_UNIT_TESTS +4 -5
  5. data/Rakefile +5 -6
  6. data/install.rb +2 -2
  7. data/lib/action_controller.rb +11 -15
  8. data/lib/action_controller/assertions.rb +12 -25
  9. data/lib/action_controller/assertions/dom_assertions.rb +18 -4
  10. data/lib/action_controller/assertions/model_assertions.rb +8 -1
  11. data/lib/action_controller/assertions/response_assertions.rb +35 -12
  12. data/lib/action_controller/assertions/routing_assertions.rb +56 -12
  13. data/lib/action_controller/assertions/selector_assertions.rb +105 -38
  14. data/lib/action_controller/assertions/tag_assertions.rb +28 -15
  15. data/lib/action_controller/base.rb +318 -250
  16. data/lib/action_controller/benchmarking.rb +33 -29
  17. data/lib/action_controller/caching.rb +130 -64
  18. data/lib/action_controller/cgi_ext.rb +16 -0
  19. data/lib/action_controller/cgi_ext/{cookie_performance_fix.rb → cookie.rb} +25 -40
  20. data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
  21. data/lib/action_controller/cgi_ext/session.rb +73 -0
  22. data/lib/action_controller/cgi_ext/stdinput.rb +23 -0
  23. data/lib/action_controller/cgi_process.rb +34 -57
  24. data/lib/action_controller/components.rb +19 -36
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/dispatcher.rb +195 -0
  27. data/lib/action_controller/filters.rb +35 -34
  28. data/lib/action_controller/flash.rb +30 -35
  29. data/lib/action_controller/helpers.rb +121 -47
  30. data/lib/action_controller/http_authentication.rb +126 -0
  31. data/lib/action_controller/integration.rb +105 -101
  32. data/lib/action_controller/layout.rb +59 -47
  33. data/lib/action_controller/mime_responds.rb +57 -68
  34. data/lib/action_controller/mime_type.rb +43 -80
  35. data/lib/action_controller/mime_types.rb +20 -0
  36. data/lib/action_controller/polymorphic_routes.rb +88 -0
  37. data/lib/action_controller/record_identifier.rb +91 -0
  38. data/lib/action_controller/request.rb +553 -88
  39. data/lib/action_controller/request_forgery_protection.rb +126 -0
  40. data/lib/action_controller/request_profiler.rb +138 -0
  41. data/lib/action_controller/rescue.rb +185 -69
  42. data/lib/action_controller/resources.rb +211 -172
  43. data/lib/action_controller/response.rb +49 -8
  44. data/lib/action_controller/routing.rb +359 -236
  45. data/lib/action_controller/routing_optimisation.rb +119 -0
  46. data/lib/action_controller/session/active_record_store.rb +3 -2
  47. data/lib/action_controller/session/cookie_store.rb +161 -0
  48. data/lib/action_controller/session/mem_cache_store.rb +9 -16
  49. data/lib/action_controller/session_management.rb +17 -8
  50. data/lib/action_controller/streaming.rb +6 -3
  51. data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
  52. data/lib/action_controller/templates/rescues/{_trace.rhtml → _trace.erb} +0 -0
  53. data/lib/action_controller/templates/rescues/{diagnostics.rhtml → diagnostics.erb} +2 -2
  54. data/lib/action_controller/templates/rescues/{layout.rhtml → layout.erb} +0 -0
  55. data/lib/action_controller/templates/rescues/{missing_template.rhtml → missing_template.erb} +0 -0
  56. data/lib/action_controller/templates/rescues/{routing_error.rhtml → routing_error.erb} +0 -0
  57. data/lib/action_controller/templates/rescues/{template_error.rhtml → template_error.erb} +2 -2
  58. data/lib/action_controller/templates/rescues/{unknown_action.rhtml → unknown_action.erb} +0 -0
  59. data/lib/action_controller/test_case.rb +53 -0
  60. data/lib/action_controller/test_process.rb +59 -46
  61. data/lib/action_controller/url_rewriter.rb +48 -24
  62. data/lib/action_controller/vendor/html-scanner/html/document.rb +7 -4
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +11 -6
  65. data/lib/action_controller/verification.rb +27 -21
  66. data/lib/action_pack.rb +1 -1
  67. data/lib/action_pack/version.rb +4 -4
  68. data/lib/action_view.rb +2 -3
  69. data/lib/action_view/base.rb +218 -63
  70. data/lib/action_view/compiled_templates.rb +1 -2
  71. data/lib/action_view/helpers/active_record_helper.rb +35 -17
  72. data/lib/action_view/helpers/asset_tag_helper.rb +395 -87
  73. data/lib/action_view/helpers/atom_feed_helper.rb +111 -0
  74. data/lib/action_view/helpers/benchmark_helper.rb +12 -5
  75. data/lib/action_view/helpers/cache_helper.rb +29 -0
  76. data/lib/action_view/helpers/capture_helper.rb +97 -63
  77. data/lib/action_view/helpers/date_helper.rb +295 -35
  78. data/lib/action_view/helpers/debug_helper.rb +6 -2
  79. data/lib/action_view/helpers/form_helper.rb +354 -111
  80. data/lib/action_view/helpers/form_options_helper.rb +171 -109
  81. data/lib/action_view/helpers/form_tag_helper.rb +332 -76
  82. data/lib/action_view/helpers/javascript_helper.rb +35 -11
  83. data/lib/action_view/helpers/javascripts/controls.js +484 -354
  84. data/lib/action_view/helpers/javascripts/dragdrop.js +88 -58
  85. data/lib/action_view/helpers/javascripts/effects.js +396 -364
  86. data/lib/action_view/helpers/javascripts/prototype.js +2817 -1107
  87. data/lib/action_view/helpers/number_helper.rb +84 -60
  88. data/lib/action_view/helpers/prototype_helper.rb +419 -43
  89. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  90. data/lib/action_view/helpers/record_tag_helper.rb +59 -0
  91. data/lib/action_view/helpers/sanitize_helper.rb +223 -0
  92. data/lib/action_view/helpers/scriptaculous_helper.rb +63 -4
  93. data/lib/action_view/helpers/tag_helper.rb +69 -39
  94. data/lib/action_view/helpers/text_helper.rb +221 -148
  95. data/lib/action_view/helpers/url_helper.rb +283 -165
  96. data/lib/action_view/partials.rb +134 -62
  97. data/lib/action_view/template_error.rb +4 -12
  98. data/lib/actionpack.rb +1 -0
  99. data/test/abstract_unit.rb +21 -1
  100. data/test/action_view_test.rb +26 -0
  101. data/test/active_record_unit.rb +12 -20
  102. data/test/activerecord/active_record_store_test.rb +2 -2
  103. data/test/activerecord/render_partial_with_record_identification_test.rb +74 -0
  104. data/test/controller/action_pack_assertions_test.rb +21 -152
  105. data/test/controller/addresses_render_test.rb +2 -7
  106. data/test/controller/assert_select_test.rb +120 -14
  107. data/test/controller/base_test.rb +11 -13
  108. data/test/controller/caching_test.rb +125 -5
  109. data/test/controller/capture_test.rb +23 -16
  110. data/test/controller/cgi_test.rb +66 -391
  111. data/test/controller/components_test.rb +31 -42
  112. data/test/controller/content_type_test.rb +1 -1
  113. data/test/controller/cookie_test.rb +42 -14
  114. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -42
  115. data/test/controller/dispatcher_test.rb +123 -0
  116. data/test/controller/fake_models.rb +5 -0
  117. data/test/controller/filters_test.rb +44 -7
  118. data/test/controller/flash_test.rb +46 -2
  119. data/test/controller/fragment_store_setting_test.rb +10 -8
  120. data/test/controller/helper_test.rb +19 -2
  121. data/test/controller/html-scanner/document_test.rb +124 -0
  122. data/test/controller/html-scanner/node_test.rb +69 -0
  123. data/test/controller/html-scanner/sanitizer_test.rb +250 -0
  124. data/test/controller/html-scanner/tag_node_test.rb +239 -0
  125. data/test/controller/html-scanner/text_node_test.rb +51 -0
  126. data/test/controller/html-scanner/tokenizer_test.rb +125 -0
  127. data/test/controller/http_authentication_test.rb +54 -0
  128. data/test/controller/integration_test.rb +12 -26
  129. data/test/controller/layout_test.rb +64 -12
  130. data/test/controller/mime_responds_test.rb +193 -38
  131. data/test/controller/mime_type_test.rb +30 -8
  132. data/test/controller/new_render_test.rb +104 -22
  133. data/test/controller/polymorphic_routes_test.rb +98 -0
  134. data/test/controller/record_identifier_test.rb +103 -0
  135. data/test/controller/redirect_test.rb +120 -18
  136. data/test/controller/render_test.rb +195 -45
  137. data/test/controller/request_forgery_protection_test.rb +217 -0
  138. data/test/controller/request_test.rb +545 -27
  139. data/test/controller/rescue_test.rb +501 -0
  140. data/test/controller/resources_test.rb +258 -132
  141. data/test/controller/routing_test.rb +502 -106
  142. data/test/controller/selector_test.rb +5 -5
  143. data/test/controller/send_file_test.rb +17 -7
  144. data/test/controller/session/cookie_store_test.rb +246 -0
  145. data/test/controller/session/mem_cache_store_test.rb +182 -0
  146. data/test/controller/session_fixation_test.rb +8 -11
  147. data/test/controller/session_management_test.rb +7 -7
  148. data/test/controller/test_test.rb +150 -38
  149. data/test/controller/url_rewriter_test.rb +87 -12
  150. data/test/controller/verification_test.rb +11 -0
  151. data/test/controller/view_paths_test.rb +137 -0
  152. data/test/controller/webservice_test.rb +11 -75
  153. data/test/fixtures/addresses/{list.rhtml → list.erb} +0 -0
  154. data/test/fixtures/db_definitions/sqlite.sql +2 -1
  155. data/test/fixtures/developer.rb +2 -0
  156. data/test/fixtures/fun/games/{hello_world.rhtml → hello_world.erb} +0 -0
  157. data/test/fixtures/helpers/fun/pdf_helper.rb +1 -1
  158. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  159. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  160. data/test/fixtures/layouts/{builder.rxml → builder.builder} +0 -0
  161. data/test/fixtures/layouts/{standard.rhtml → standard.erb} +0 -0
  162. data/test/fixtures/layouts/{talk_from_action.rhtml → talk_from_action.erb} +0 -0
  163. data/test/fixtures/layouts/{yield.rhtml → yield.erb} +0 -0
  164. data/test/fixtures/multipart/binary_file +0 -0
  165. data/test/fixtures/multipart/bracketed_param +5 -0
  166. data/test/fixtures/override/test/hello_world.erb +1 -0
  167. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  168. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  169. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  170. data/test/fixtures/post_test/post/index.html.erb +1 -0
  171. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  172. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  173. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  174. data/test/fixtures/public/404.html +1 -0
  175. data/test/fixtures/public/500.html +1 -0
  176. data/test/fixtures/public/javascripts/application.js +0 -1
  177. data/test/fixtures/public/javascripts/bank.js +1 -0
  178. data/test/fixtures/public/javascripts/robber.js +1 -0
  179. data/test/fixtures/public/stylesheets/bank.css +1 -0
  180. data/test/fixtures/public/stylesheets/robber.css +1 -0
  181. data/test/fixtures/replies.yml +2 -0
  182. data/test/fixtures/reply.rb +2 -1
  183. data/test/fixtures/respond_to/{all_types_with_layout.rhtml → all_types_with_layout.html.erb} +0 -0
  184. data/test/fixtures/respond_to/{all_types_with_layout.rjs → all_types_with_layout.js.rjs} +0 -0
  185. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  186. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  187. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  188. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  189. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  190. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  191. data/test/fixtures/respond_to/{using_defaults.rhtml → using_defaults.html.erb} +0 -0
  192. data/test/fixtures/respond_to/{using_defaults.rjs → using_defaults.js.rjs} +0 -0
  193. data/test/fixtures/respond_to/{using_defaults.rxml → using_defaults.xml.builder} +0 -0
  194. data/test/fixtures/respond_to/{using_defaults_with_type_list.rhtml → using_defaults_with_type_list.html.erb} +0 -0
  195. data/test/fixtures/respond_to/{using_defaults_with_type_list.rjs → using_defaults_with_type_list.js.rjs} +0 -0
  196. data/test/fixtures/respond_to/{using_defaults_with_type_list.rxml → using_defaults_with_type_list.xml.builder} +0 -0
  197. data/test/fixtures/scope/test/{modgreet.rhtml → modgreet.erb} +0 -0
  198. data/test/fixtures/test/{_customer.rhtml → _customer.erb} +0 -0
  199. data/test/fixtures/test/{_customer_greeting.rhtml → _customer_greeting.erb} +0 -0
  200. data/test/fixtures/test/_hash_greeting.erb +1 -0
  201. data/test/fixtures/test/_hash_object.erb +2 -0
  202. data/test/fixtures/test/{_hello.rxml → _hello.builder} +0 -0
  203. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  204. data/test/fixtures/test/_partial.erb +1 -0
  205. data/test/fixtures/test/_partial.html.erb +1 -0
  206. data/test/fixtures/test/_partial.js.erb +1 -0
  207. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  208. data/test/fixtures/test/{_partial_only.rhtml → _partial_only.erb} +0 -0
  209. data/test/fixtures/test/{_person.rhtml → _person.erb} +0 -0
  210. data/test/fixtures/test/{action_talk_to_layout.rhtml → action_talk_to_layout.erb} +0 -0
  211. data/test/fixtures/test/{block_content_for.rhtml → block_content_for.erb} +0 -0
  212. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  213. data/test/fixtures/test/{capturing.rhtml → capturing.erb} +0 -0
  214. data/test/fixtures/test/{content_for.rhtml → content_for.erb} +0 -0
  215. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  216. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  217. data/test/fixtures/test/dot.directory/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
  218. data/test/fixtures/test/{erb_content_for.rhtml → erb_content_for.erb} +0 -0
  219. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  220. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  221. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  222. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  223. data/test/fixtures/test/{greeting.rhtml → greeting.erb} +0 -0
  224. data/test/fixtures/test/{hello.rxml → hello.builder} +0 -0
  225. data/test/fixtures/test/{hello_world.rxml → hello_world.builder} +0 -0
  226. data/test/fixtures/test/{hello_world.rhtml → hello_world.erb} +0 -0
  227. data/test/fixtures/test/{hello_world_container.rxml → hello_world_container.builder} +0 -0
  228. data/test/fixtures/test/{hello_world_with_layout_false.rhtml → hello_world_with_layout_false.erb} +0 -0
  229. data/test/fixtures/test/{hello_xml_world.rxml → hello_xml_world.builder} +0 -0
  230. data/test/fixtures/test/list.erb +1 -0
  231. data/test/fixtures/test/{non_erb_block_content_for.rxml → non_erb_block_content_for.builder} +0 -0
  232. data/test/fixtures/test/{potential_conflicts.rhtml → potential_conflicts.erb} +0 -0
  233. data/test/fixtures/test/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
  234. data/test/fixtures/test/{render_file_with_locals.rhtml → render_file_with_locals.erb} +0 -0
  235. data/test/fixtures/test/{render_to_string_test.rhtml → render_to_string_test.erb} +0 -0
  236. data/test/fixtures/test/{update_element_with_capture.rhtml → update_element_with_capture.erb} +0 -0
  237. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  238. data/test/fixtures/topic.rb +1 -1
  239. data/test/template/active_record_helper_test.rb +67 -20
  240. data/test/template/asset_tag_helper_test.rb +222 -54
  241. data/test/template/atom_feed_helper_test.rb +101 -0
  242. data/test/template/benchmark_helper_test.rb +2 -2
  243. data/test/template/compiled_templates_test.rb +76 -32
  244. data/test/template/date_helper_test.rb +125 -9
  245. data/test/template/form_helper_test.rb +326 -33
  246. data/test/template/form_options_helper_test.rb +822 -15
  247. data/test/template/form_tag_helper_test.rb +96 -30
  248. data/test/template/javascript_helper_test.rb +61 -13
  249. data/test/template/number_helper_test.rb +12 -11
  250. data/test/template/prototype_helper_test.rb +185 -24
  251. data/test/template/sanitize_helper_test.rb +49 -0
  252. data/test/template/scriptaculous_helper_test.rb +9 -3
  253. data/test/template/tag_helper_test.rb +13 -2
  254. data/test/template/text_helper_test.rb +38 -52
  255. data/test/template/url_helper_test.rb +216 -46
  256. metadata +144 -116
  257. data/examples/.htaccess +0 -24
  258. data/examples/address_book/index.rhtml +0 -33
  259. data/examples/address_book/layout.rhtml +0 -8
  260. data/examples/address_book_controller.cgi +0 -9
  261. data/examples/address_book_controller.fcgi +0 -6
  262. data/examples/address_book_controller.rb +0 -52
  263. data/examples/address_book_controller.rbx +0 -4
  264. data/examples/benchmark.rb +0 -52
  265. data/examples/benchmark_with_ar.fcgi +0 -89
  266. data/examples/blog_controller.cgi +0 -53
  267. data/examples/debate/index.rhtml +0 -14
  268. data/examples/debate/new_topic.rhtml +0 -22
  269. data/examples/debate/topic.rhtml +0 -32
  270. data/examples/debate_controller.cgi +0 -57
  271. data/lib/action_controller/assertions/deprecated_assertions.rb +0 -228
  272. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -36
  273. data/lib/action_controller/cgi_ext/cgi_methods.rb +0 -211
  274. data/lib/action_controller/cgi_ext/pstore_performance_fix.rb +0 -30
  275. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +0 -95
  276. data/lib/action_controller/cgi_ext/session_performance_fix.rb +0 -30
  277. data/lib/action_controller/deprecated_dependencies.rb +0 -65
  278. data/lib/action_controller/deprecated_redirects.rb +0 -17
  279. data/lib/action_controller/deprecated_request_methods.rb +0 -34
  280. data/lib/action_controller/macros/auto_complete.rb +0 -53
  281. data/lib/action_controller/macros/in_place_editing.rb +0 -33
  282. data/lib/action_controller/pagination.rb +0 -408
  283. data/lib/action_controller/scaffolding.rb +0 -189
  284. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -44
  285. data/lib/action_controller/templates/scaffolds/edit.rhtml +0 -7
  286. data/lib/action_controller/templates/scaffolds/layout.rhtml +0 -69
  287. data/lib/action_controller/templates/scaffolds/list.rhtml +0 -27
  288. data/lib/action_controller/templates/scaffolds/new.rhtml +0 -6
  289. data/lib/action_controller/templates/scaffolds/show.rhtml +0 -9
  290. data/lib/action_controller/vendor/xml_node.rb +0 -97
  291. data/lib/action_view/helpers/deprecated_helper.rb +0 -37
  292. data/lib/action_view/helpers/java_script_macros_helper.rb +0 -233
  293. data/lib/action_view/helpers/pagination_helper.rb +0 -86
  294. data/test/activerecord/active_record_assertions_test.rb +0 -92
  295. data/test/activerecord/pagination_test.rb +0 -165
  296. data/test/controller/deprecated_instance_variables_test.rb +0 -48
  297. data/test/controller/raw_post_test.rb +0 -68
  298. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +0 -1
  299. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +0 -1
  300. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +0 -1
  301. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +0 -1
  302. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +0 -1
  303. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +0 -1
  304. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +0 -1
  305. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +0 -1
  306. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +0 -1
  307. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +0 -1
  308. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +0 -1
  309. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +0 -1
  310. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +0 -1
  311. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +0 -1
  312. data/test/fixtures/respond_to/layouts/standard.rhtml +0 -1
  313. data/test/fixtures/test/_hash_object.rhtml +0 -1
  314. data/test/fixtures/test/list.rhtml +0 -1
  315. data/test/template/deprecated_helper_test.rb +0 -36
  316. data/test/template/deprecated_instance_variables_test.rb +0 -43
  317. data/test/template/java_script_macros_helper_test.rb +0 -109
@@ -0,0 +1,250 @@
1
+ require File.dirname(__FILE__) + '/../../abstract_unit'
2
+ require 'test/unit'
3
+
4
+ class SanitizerTest < Test::Unit::TestCase
5
+ def setup
6
+ @sanitizer = nil # used by assert_sanitizer
7
+ end
8
+
9
+ def test_strip_tags
10
+ sanitizer = HTML::FullSanitizer.new
11
+ assert_equal("<<<bad html", sanitizer.sanitize("<<<bad html"))
12
+ assert_equal("<<", sanitizer.sanitize("<<<bad html>"))
13
+ assert_equal("Dont touch me", sanitizer.sanitize("Dont touch me"))
14
+ assert_equal("This is a test.", sanitizer.sanitize("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>"))
15
+ assert_equal("Weirdos", sanitizer.sanitize("Wei<<a>a onclick='alert(document.cookie);'</a>/>rdos"))
16
+ assert_equal("This is a test.", sanitizer.sanitize("This is a test."))
17
+ assert_equal(
18
+ %{This is a test.\n\n\nIt no longer contains any HTML.\n}, sanitizer.sanitize(
19
+ %{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}))
20
+ assert_equal "This has a here.", sanitizer.sanitize("This has a <!-- comment --> here.")
21
+ [nil, '', ' '].each { |blank| assert_equal blank, sanitizer.sanitize(blank) }
22
+ end
23
+
24
+ def test_strip_links
25
+ sanitizer = HTML::LinkSanitizer.new
26
+ assert_equal "Dont touch me", sanitizer.sanitize("Dont touch me")
27
+ assert_equal "on my mind\nall day long", sanitizer.sanitize("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>")
28
+ assert_equal "0wn3d", sanitizer.sanitize("<a href='http://www.rubyonrails.com/'><a href='http://www.rubyonrails.com/' onlclick='steal()'>0wn3d</a></a>")
29
+ assert_equal "Magic", sanitizer.sanitize("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic")
30
+ assert_equal "FrrFox", sanitizer.sanitize("<href onlclick='steal()'>FrrFox</a></href>")
31
+ assert_equal "My mind\nall <b>day</b> long", sanitizer.sanitize("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>")
32
+ assert_equal "all <b>day</b> long", sanitizer.sanitize("<<a>a href='hello'>all <b>day</b> long<</A>/a>")
33
+
34
+ assert_equal "<a<a", sanitizer.sanitize("<a<a")
35
+ end
36
+
37
+ def test_sanitize_form
38
+ assert_sanitized "<form action=\"/foo/bar\" method=\"post\"><input></form>", ''
39
+ end
40
+
41
+ def test_sanitize_plaintext
42
+ raw = "<plaintext><span>foo</span></plaintext>"
43
+ assert_sanitized raw, "<span>foo</span>"
44
+ end
45
+
46
+ def test_sanitize_script
47
+ assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cd e f"
48
+ end
49
+
50
+ # fucked
51
+ def test_sanitize_js_handlers
52
+ raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>}
53
+ assert_sanitized raw, %{onthis="do that" <a name="foo" href="#">hello</a>}
54
+ end
55
+
56
+ def test_sanitize_javascript_href
57
+ raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>}
58
+ assert_sanitized raw, %{href="javascript:bang" <a name="hello">foo</a>, <span>bar</span>}
59
+ end
60
+
61
+ def test_sanitize_image_src
62
+ raw = %{src="javascript:bang" <img src="javascript:bang" width="5">foo</img>, <span src="javascript:bang">bar</span>}
63
+ assert_sanitized raw, %{src="javascript:bang" <img width="5">foo</img>, <span>bar</span>}
64
+ end
65
+
66
+ HTML::WhiteListSanitizer.allowed_tags.each do |tag_name|
67
+ define_method "test_should_allow_#{tag_name}_tag" do
68
+ assert_sanitized "start <#{tag_name} title=\"1\" onclick=\"foo\">foo <bad>bar</bad> baz</#{tag_name}> end", %(start <#{tag_name} title="1">foo bar baz</#{tag_name}> end)
69
+ end
70
+ end
71
+
72
+ def test_should_allow_anchors
73
+ assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href="foo"></a>)
74
+ end
75
+
76
+ # RFC 3986, sec 4.2
77
+ def test_allow_colons_in_path_component
78
+ assert_sanitized("<a href=\"./this:that\">foo</a>")
79
+ end
80
+
81
+ %w(src width height alt).each do |img_attr|
82
+ define_method "test_should_allow_image_#{img_attr}_attribute" do
83
+ assert_sanitized %(<img #{img_attr}="foo" onclick="bar" />), %(<img #{img_attr}="foo" />)
84
+ end
85
+ end
86
+
87
+ def test_should_handle_non_html
88
+ assert_sanitized 'abc'
89
+ end
90
+
91
+ def test_should_handle_blank_text
92
+ assert_sanitized nil
93
+ assert_sanitized ''
94
+ end
95
+
96
+ def test_should_allow_custom_tags
97
+ text = "<u>foo</u>"
98
+ sanitizer = HTML::WhiteListSanitizer.new
99
+ assert_equal(text, sanitizer.sanitize(text, :tags => %w(u)))
100
+ end
101
+
102
+ def test_should_allow_only_custom_tags
103
+ text = "<u>foo</u> with <i>bar</i>"
104
+ sanitizer = HTML::WhiteListSanitizer.new
105
+ assert_equal("<u>foo</u> with bar", sanitizer.sanitize(text, :tags => %w(u)))
106
+ end
107
+
108
+ def test_should_allow_custom_tags_with_attributes
109
+ text = %(<blockquote cite="http://example.com/">foo</blockquote>)
110
+ sanitizer = HTML::WhiteListSanitizer.new
111
+ assert_equal(text, sanitizer.sanitize(text))
112
+ end
113
+
114
+ def test_should_allow_custom_tags_with_custom_attributes
115
+ text = %(<blockquote foo="bar">Lorem ipsum</blockquote>)
116
+ sanitizer = HTML::WhiteListSanitizer.new
117
+ assert_equal(text, sanitizer.sanitize(text, :attributes => ['foo']))
118
+ end
119
+
120
+ [%w(img src), %w(a href)].each do |(tag, attr)|
121
+ define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do
122
+ assert_sanitized %(<#{tag} #{attr}="javascript:bang" title="1">boo</#{tag}>), %(<#{tag} title="1">boo</#{tag}>)
123
+ end
124
+ end
125
+
126
+ def test_should_flag_bad_protocols
127
+ sanitizer = HTML::WhiteListSanitizer.new
128
+ %w(about chrome data disk hcp help javascript livescript lynxcgi lynxexec ms-help ms-its mhtml mocha opera res resource shell vbscript view-source vnd.ms.radio wysiwyg).each do |proto|
129
+ assert sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://bad")
130
+ end
131
+ end
132
+
133
+ def test_should_accept_good_protocols
134
+ sanitizer = HTML::WhiteListSanitizer.new
135
+ HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
136
+ assert !sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://good")
137
+ end
138
+ end
139
+
140
+ def test_should_reject_hex_codes_in_protocol
141
+ assert_sanitized %(<a href="&#37;6A&#37;61&#37;76&#37;61&#37;73&#37;63&#37;72&#37;69&#37;70&#37;74&#37;3A&#37;61&#37;6C&#37;65&#37;72&#37;74&#37;28&#37;22&#37;58&#37;53&#37;53&#37;22&#37;29">1</a>), "<a>1</a>"
142
+ assert @sanitizer.send(:contains_bad_protocols?, 'src', "%6A%61%76%61%73%63%72%69%70%74%3A%61%6C%65%72%74%28%22%58%53%53%22%29")
143
+ end
144
+
145
+ def test_should_block_script_tag
146
+ assert_sanitized %(<SCRIPT\nSRC=http://ha.ckers.org/xss.js></SCRIPT>), ""
147
+ end
148
+
149
+ [%(<IMG SRC="javascript:alert('XSS');">),
150
+ %(<IMG SRC=javascript:alert('XSS')>),
151
+ %(<IMG SRC=JaVaScRiPt:alert('XSS')>),
152
+ %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">),
153
+ %(<IMG SRC=javascript:alert(&quot;XSS&quot;)>),
154
+ %(<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>),
155
+ %(<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>),
156
+ %(<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>),
157
+ %(<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>),
158
+ %(<IMG SRC="jav\tascript:alert('XSS');">),
159
+ %(<IMG SRC="jav&#x09;ascript:alert('XSS');">),
160
+ %(<IMG SRC="jav&#x0A;ascript:alert('XSS');">),
161
+ %(<IMG SRC="jav&#x0D;ascript:alert('XSS');">),
162
+ %(<IMG SRC=" &#14; javascript:alert('XSS');">),
163
+ %(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each_with_index do |img_hack, i|
164
+ define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do
165
+ assert_sanitized img_hack, "<img>"
166
+ end
167
+ end
168
+
169
+ def test_should_sanitize_tag_broken_up_by_null
170
+ assert_sanitized %(<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>), "alert(\"XSS\")"
171
+ end
172
+
173
+ def test_should_sanitize_invalid_script_tag
174
+ assert_sanitized %(<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>), ""
175
+ end
176
+
177
+ def test_should_sanitize_script_tag_with_multiple_open_brackets
178
+ assert_sanitized %(<<SCRIPT>alert("XSS");//<</SCRIPT>), "&lt;"
179
+ assert_sanitized %(<iframe src=http://ha.ckers.org/scriptlet.html\n<a), %(&lt;a)
180
+ end
181
+
182
+ def test_should_sanitize_unclosed_script
183
+ assert_sanitized %(<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>), "<b>"
184
+ end
185
+
186
+ def test_should_sanitize_half_open_scripts
187
+ assert_sanitized %(<IMG SRC="javascript:alert('XSS')"), "<img>"
188
+ end
189
+
190
+ def test_should_not_fall_for_ridiculous_hack
191
+ img_hack = %(<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt\n(\n'\nX\nS\nS\n'\n)\n"\n>)
192
+ assert_sanitized img_hack, "<img>"
193
+ end
194
+
195
+ # fucked
196
+ def test_should_sanitize_attributes
197
+ assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="'&gt;&lt;script&gt;alert()&lt;/script&gt;">blah</span>)
198
+ end
199
+
200
+ def test_should_sanitize_illegal_style_properties
201
+ raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;)
202
+ expected = %(display: block; width: 100%; height: 100%; background-color: black; background-image: ; background-x: center; background-y: center;)
203
+ assert_equal expected, sanitize_css(raw)
204
+ end
205
+
206
+ def test_should_sanitize_xul_style_attributes
207
+ raw = %(-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss'))
208
+ assert_equal '', sanitize_css(raw)
209
+ end
210
+
211
+ def test_should_sanitize_invalid_tag_names
212
+ assert_sanitized(%(a b c<script/XSS src="http://ha.ckers.org/xss.js"></script>d e f), "a b cd e f")
213
+ end
214
+
215
+ def test_should_sanitize_non_alpha_and_non_digit_characters_in_tags
216
+ assert_sanitized('<a onclick!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>foo</a>', "<a>foo</a>")
217
+ end
218
+
219
+ def test_should_sanitize_invalid_tag_names_in_single_tags
220
+ assert_sanitized('<img/src="http://ha.ckers.org/xss.js"/>', "<img />")
221
+ end
222
+
223
+ def test_should_sanitize_img_dynsrc_lowsrc
224
+ assert_sanitized(%(<img lowsrc="javascript:alert('XSS')" />), "<img />")
225
+ end
226
+
227
+ def test_should_sanitize_div_background_image_unicode_encoded
228
+ raw = %(background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029)
229
+ assert_equal '', sanitize_css(raw)
230
+ end
231
+
232
+ def test_should_sanitize_div_style_expression
233
+ raw = %(width: expression(alert('XSS'));)
234
+ assert_equal '', sanitize_css(raw)
235
+ end
236
+
237
+ def test_should_sanitize_img_vbscript
238
+ assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), '<img />'
239
+ end
240
+
241
+ protected
242
+ def assert_sanitized(input, expected = nil)
243
+ @sanitizer ||= HTML::WhiteListSanitizer.new
244
+ assert_equal expected || input, @sanitizer.sanitize(input)
245
+ end
246
+
247
+ def sanitize_css(input)
248
+ (@sanitizer ||= HTML::WhiteListSanitizer.new).sanitize_css(input)
249
+ end
250
+ end
@@ -0,0 +1,239 @@
1
+ require File.dirname(__FILE__) + '/../../abstract_unit'
2
+ require 'test/unit'
3
+
4
+ class TagNodeTest < Test::Unit::TestCase
5
+ def test_open_without_attributes
6
+ node = tag("<tag>")
7
+ assert_equal "tag", node.name
8
+ assert_equal Hash.new, node.attributes
9
+ assert_nil node.closing
10
+ end
11
+
12
+ def test_open_with_attributes
13
+ node = tag("<TAG1 foo=hey_ho x:bar=\"blah blah\" BAZ='blah blah blah' >")
14
+ assert_equal "tag1", node.name
15
+ assert_equal "hey_ho", node["foo"]
16
+ assert_equal "blah blah", node["x:bar"]
17
+ assert_equal "blah blah blah", node["baz"]
18
+ end
19
+
20
+ def test_self_closing_without_attributes
21
+ node = tag("<tag/>")
22
+ assert_equal "tag", node.name
23
+ assert_equal Hash.new, node.attributes
24
+ assert_equal :self, node.closing
25
+ end
26
+
27
+ def test_self_closing_with_attributes
28
+ node = tag("<tag a=b/>")
29
+ assert_equal "tag", node.name
30
+ assert_equal( { "a" => "b" }, node.attributes )
31
+ assert_equal :self, node.closing
32
+ end
33
+
34
+ def test_closing_without_attributes
35
+ node = tag("</tag>")
36
+ assert_equal "tag", node.name
37
+ assert_nil node.attributes
38
+ assert_equal :close, node.closing
39
+ end
40
+
41
+ def test_bracket_op_when_no_attributes
42
+ node = tag("</tag>")
43
+ assert_nil node["foo"]
44
+ end
45
+
46
+ def test_bracket_op_when_attributes
47
+ node = tag("<tag a=b/>")
48
+ assert_equal "b", node["a"]
49
+ end
50
+
51
+ def test_attributes_with_escaped_quotes
52
+ node = tag("<tag a='b\\'c' b=\"bob \\\"float\\\"\">")
53
+ assert_equal "b\\'c", node["a"]
54
+ assert_equal "bob \\\"float\\\"", node["b"]
55
+ end
56
+
57
+ def test_to_s
58
+ node = tag("<a b=c d='f' g=\"h 'i'\" />")
59
+ assert_equal %(<a b='c' d='f' g='h \\'i\\'' />), node.to_s
60
+ end
61
+
62
+ def test_tag
63
+ assert tag("<tag>").tag?
64
+ end
65
+
66
+ def test_match_tag_as_string
67
+ assert tag("<tag>").match(:tag => "tag")
68
+ assert !tag("<tag>").match(:tag => "b")
69
+ end
70
+
71
+ def test_match_tag_as_regexp
72
+ assert tag("<tag>").match(:tag => /t.g/)
73
+ assert !tag("<tag>").match(:tag => /t[bqs]g/)
74
+ end
75
+
76
+ def test_match_attributes_as_string
77
+ t = tag("<tag a=something b=else />")
78
+ assert t.match(:attributes => {"a" => "something"})
79
+ assert t.match(:attributes => {"b" => "else"})
80
+ end
81
+
82
+ def test_match_attributes_as_regexp
83
+ t = tag("<tag a=something b=else />")
84
+ assert t.match(:attributes => {"a" => /^something$/})
85
+ assert t.match(:attributes => {"b" => /e.*e/})
86
+ assert t.match(:attributes => {"a" => /me..i/, "b" => /.ls.$/})
87
+ end
88
+
89
+ def test_match_attributes_as_number
90
+ t = tag("<tag a=15 b=3.1415 />")
91
+ assert t.match(:attributes => {"a" => 15})
92
+ assert t.match(:attributes => {"b" => 3.1415})
93
+ assert t.match(:attributes => {"a" => 15, "b" => 3.1415})
94
+ end
95
+
96
+ def test_match_attributes_exist
97
+ t = tag("<tag a=15 b=3.1415 />")
98
+ assert t.match(:attributes => {"a" => true})
99
+ assert t.match(:attributes => {"b" => true})
100
+ assert t.match(:attributes => {"a" => true, "b" => true})
101
+ end
102
+
103
+ def test_match_attributes_not_exist
104
+ t = tag("<tag a=15 b=3.1415 />")
105
+ assert t.match(:attributes => {"c" => false})
106
+ assert t.match(:attributes => {"c" => nil})
107
+ assert t.match(:attributes => {"a" => true, "c" => false})
108
+ end
109
+
110
+ def test_match_parent_success
111
+ t = tag("<tag a=15 b='hello'>", tag("<foo k='value'>"))
112
+ assert t.match(:parent => {:tag => "foo", :attributes => {"k" => /v.l/, "j" => false}})
113
+ end
114
+
115
+ def test_match_parent_fail
116
+ t = tag("<tag a=15 b='hello'>", tag("<foo k='value'>"))
117
+ assert !t.match(:parent => {:tag => /kafka/})
118
+ end
119
+
120
+ def test_match_child_success
121
+ t = tag("<tag x:k='something'>")
122
+ tag("<child v=john a=kelly>", t)
123
+ tag("<sib m=vaughn v=james>", t)
124
+ assert t.match(:child => { :tag => "sib", :attributes => {"v" => /j/}})
125
+ assert t.match(:child => { :attributes => {"a" => "kelly"}})
126
+ end
127
+
128
+ def test_match_child_fail
129
+ t = tag("<tag x:k='something'>")
130
+ tag("<child v=john a=kelly>", t)
131
+ tag("<sib m=vaughn v=james>", t)
132
+ assert !t.match(:child => { :tag => "sib", :attributes => {"v" => /r/}})
133
+ assert !t.match(:child => { :attributes => {"v" => false}})
134
+ end
135
+
136
+ def test_match_ancestor_success
137
+ t = tag("<tag x:k='something'>", tag("<parent v=john a=kelly>", tag("<grandparent m=vaughn v=james>")))
138
+ assert t.match(:ancestor => {:tag => "parent", :attributes => {"a" => /ll/}})
139
+ assert t.match(:ancestor => {:attributes => {"m" => "vaughn"}})
140
+ end
141
+
142
+ def test_match_ancestor_fail
143
+ t = tag("<tag x:k='something'>", tag("<parent v=john a=kelly>", tag("<grandparent m=vaughn v=james>")))
144
+ assert !t.match(:ancestor => {:tag => /^parent/, :attributes => {"v" => /m/}})
145
+ assert !t.match(:ancestor => {:attributes => {"v" => false}})
146
+ end
147
+
148
+ def test_match_descendant_success
149
+ tag("<grandchild m=vaughn v=james>", tag("<child v=john a=kelly>", t = tag("<tag x:k='something'>")))
150
+ assert t.match(:descendant => {:tag => "child", :attributes => {"a" => /ll/}})
151
+ assert t.match(:descendant => {:attributes => {"m" => "vaughn"}})
152
+ end
153
+
154
+ def test_match_descendant_fail
155
+ tag("<grandchild m=vaughn v=james>", tag("<child v=john a=kelly>", t = tag("<tag x:k='something'>")))
156
+ assert !t.match(:descendant => {:tag => /^child/, :attributes => {"v" => /m/}})
157
+ assert !t.match(:descendant => {:attributes => {"v" => false}})
158
+ end
159
+
160
+ def test_match_child_count
161
+ t = tag("<tag x:k='something'>")
162
+ tag("hello", t)
163
+ tag("<child v=john a=kelly>", t)
164
+ tag("<sib m=vaughn v=james>", t)
165
+ assert t.match(:children => { :count => 2 })
166
+ assert t.match(:children => { :count => 2..4 })
167
+ assert t.match(:children => { :less_than => 4 })
168
+ assert t.match(:children => { :greater_than => 1 })
169
+ assert !t.match(:children => { :count => 3 })
170
+ end
171
+
172
+ def test_conditions_as_strings
173
+ t = tag("<tag x:k='something'>")
174
+ assert t.match("tag" => "tag")
175
+ assert t.match("attributes" => { "x:k" => "something" })
176
+ assert !t.match("tag" => "gat")
177
+ assert !t.match("attributes" => { "x:j" => "something" })
178
+ end
179
+
180
+ def test_attributes_as_symbols
181
+ t = tag("<child v=john a=kelly>")
182
+ assert t.match(:attributes => { :v => /oh/ })
183
+ assert t.match(:attributes => { :a => /ll/ })
184
+ end
185
+
186
+ def test_match_sibling
187
+ t = tag("<tag x:k='something'>")
188
+ tag("hello", t)
189
+ tag("<span a=b>", t)
190
+ tag("world", t)
191
+ m = tag("<span k=r>", t)
192
+ tag("<span m=l>", t)
193
+
194
+ assert m.match(:sibling => {:tag => "span", :attributes => {:a => true}})
195
+ assert m.match(:sibling => {:tag => "span", :attributes => {:m => true}})
196
+ assert !m.match(:sibling => {:tag => "span", :attributes => {:k => true}})
197
+ end
198
+
199
+ def test_match_sibling_before
200
+ t = tag("<tag x:k='something'>")
201
+ tag("hello", t)
202
+ tag("<span a=b>", t)
203
+ tag("world", t)
204
+ m = tag("<span k=r>", t)
205
+ tag("<span m=l>", t)
206
+
207
+ assert m.match(:before => {:tag => "span", :attributes => {:m => true}})
208
+ assert !m.match(:before => {:tag => "span", :attributes => {:a => true}})
209
+ assert !m.match(:before => {:tag => "span", :attributes => {:k => true}})
210
+ end
211
+
212
+ def test_match_sibling_after
213
+ t = tag("<tag x:k='something'>")
214
+ tag("hello", t)
215
+ tag("<span a=b>", t)
216
+ tag("world", t)
217
+ m = tag("<span k=r>", t)
218
+ tag("<span m=l>", t)
219
+
220
+ assert m.match(:after => {:tag => "span", :attributes => {:a => true}})
221
+ assert !m.match(:after => {:tag => "span", :attributes => {:m => true}})
222
+ assert !m.match(:after => {:tag => "span", :attributes => {:k => true}})
223
+ end
224
+
225
+ def test_to_s
226
+ t = tag("<b x='foo'>")
227
+ tag("hello", t)
228
+ tag("<hr />", t)
229
+ assert_equal %(<b x="foo">hello<hr /></b>), t.to_s
230
+ end
231
+
232
+ private
233
+
234
+ def tag(content, parent=nil)
235
+ node = HTML::Node.parse(parent,0,0,content)
236
+ parent.children << node if parent
237
+ node
238
+ end
239
+ end