arachni 1.3.2 → 1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (727) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -0
  3. data/Gemfile +2 -6
  4. data/LICENSE.md +1 -1
  5. data/README.md +34 -16
  6. data/Rakefile +1 -1
  7. data/arachni.gemspec +28 -20
  8. data/bin/arachni +1 -1
  9. data/bin/arachni_console +1 -1
  10. data/bin/arachni_multi +1 -1
  11. data/bin/arachni_reporter +1 -1
  12. data/bin/arachni_rest_server +13 -0
  13. data/bin/arachni_restore +1 -1
  14. data/bin/arachni_rpc +1 -1
  15. data/bin/arachni_rpcd +1 -1
  16. data/bin/arachni_rpcd_monitor +1 -1
  17. data/bin/arachni_script +1 -1
  18. data/components/checks/active/code_injection.rb +8 -10
  19. data/components/checks/active/code_injection_php_input_wrapper.rb +5 -6
  20. data/components/checks/active/code_injection_timing.rb +1 -1
  21. data/components/checks/active/csrf.rb +1 -1
  22. data/components/checks/active/file_inclusion.rb +20 -26
  23. data/components/checks/active/ldap_injection.rb +4 -5
  24. data/components/checks/active/no_sql_injection.rb +11 -20
  25. data/components/checks/active/no_sql_injection/substrings/mongodb +1 -0
  26. data/components/checks/active/no_sql_injection_differential.rb +3 -4
  27. data/components/checks/active/os_cmd_injection.rb +5 -9
  28. data/components/checks/active/os_cmd_injection_timing.rb +1 -1
  29. data/components/checks/active/path_traversal.rb +4 -17
  30. data/components/checks/active/response_splitting.rb +8 -2
  31. data/components/checks/active/rfi.rb +4 -5
  32. data/components/checks/active/session_fixation.rb +9 -3
  33. data/components/checks/active/source_code_disclosure.rb +5 -20
  34. data/components/checks/active/sql_injection.rb +30 -18
  35. data/components/checks/active/sql_injection/{regexp_ignore.txt → ignore_substrings} +0 -0
  36. data/components/checks/active/sql_injection/regexps/db2.yaml +2 -0
  37. data/components/checks/active/sql_injection/regexps/frontbase.yaml +1 -0
  38. data/components/checks/active/sql_injection/regexps/informix.yaml +1 -0
  39. data/components/checks/active/sql_injection/regexps/ingres.yaml +2 -0
  40. data/components/checks/active/sql_injection/regexps/maxdb.yaml +2 -0
  41. data/components/checks/active/sql_injection/regexps/mssql.yaml +8 -0
  42. data/components/checks/active/sql_injection/regexps/mysql.yaml +4 -0
  43. data/components/checks/active/sql_injection/regexps/oracle.yaml +4 -0
  44. data/components/checks/active/sql_injection/regexps/pgsql.yaml +3 -0
  45. data/components/checks/active/sql_injection/regexps/sqlite.yaml +2 -0
  46. data/components/checks/active/sql_injection/regexps/sybase.yaml +2 -0
  47. data/components/checks/active/sql_injection/substrings/access +3 -0
  48. data/components/checks/active/sql_injection/substrings/db2 +2 -0
  49. data/components/checks/active/sql_injection/{patterns → substrings}/emc +1 -1
  50. data/components/checks/active/sql_injection/{patterns → substrings}/firebird +0 -1
  51. data/components/checks/active/sql_injection/substrings/hsqldb +1 -0
  52. data/components/checks/active/sql_injection/{patterns → substrings}/informix +1 -2
  53. data/components/checks/active/sql_injection/substrings/ingres +1 -0
  54. data/components/checks/active/sql_injection/{patterns → substrings}/interbase +0 -0
  55. data/components/checks/active/sql_injection/substrings/mssql +17 -0
  56. data/components/checks/active/sql_injection/{patterns → substrings}/mysql +3 -6
  57. data/components/checks/active/sql_injection/substrings/oracle +2 -0
  58. data/components/checks/active/sql_injection/{patterns → substrings}/pgsql +3 -6
  59. data/components/checks/active/sql_injection/substrings/sqlite +3 -0
  60. data/components/checks/active/sql_injection/substrings/sybase +1 -0
  61. data/components/checks/active/sql_injection_differential.rb +5 -7
  62. data/components/checks/active/sql_injection_differential/payloads.txt +1 -1
  63. data/components/checks/active/sql_injection_timing.rb +1 -1
  64. data/components/checks/active/trainer.rb +5 -4
  65. data/components/checks/active/unvalidated_redirect.rb +1 -1
  66. data/components/checks/active/unvalidated_redirect_dom.rb +1 -1
  67. data/components/checks/active/xpath_injection.rb +3 -4
  68. data/components/checks/active/xss.rb +33 -12
  69. data/components/checks/active/xss_dom.rb +7 -4
  70. data/components/checks/active/xss_dom_script_context.rb +1 -1
  71. data/components/checks/active/xss_event.rb +43 -20
  72. data/components/checks/active/xss_path.rb +5 -4
  73. data/components/checks/active/xss_script_context.rb +41 -11
  74. data/components/checks/active/xss_tag.rb +14 -15
  75. data/components/checks/active/xxe.rb +5 -16
  76. data/components/checks/passive/allowed_methods.rb +1 -1
  77. data/components/checks/passive/backdoors.rb +4 -2
  78. data/components/checks/passive/backup_directories.rb +4 -2
  79. data/components/checks/passive/backup_files.rb +4 -2
  80. data/components/checks/passive/common_admin_interfaces.rb +4 -3
  81. data/components/checks/passive/common_directories.rb +3 -1
  82. data/components/checks/passive/common_files.rb +3 -1
  83. data/components/checks/passive/directory_listing.rb +4 -4
  84. data/components/checks/passive/grep/captcha.rb +1 -1
  85. data/components/checks/passive/grep/cookie_set_for_parent_domain.rb +1 -1
  86. data/components/checks/passive/grep/credit_card.rb +5 -7
  87. data/components/checks/passive/grep/cvs_svn_users.rb +1 -1
  88. data/components/checks/passive/grep/emails.rb +135 -8
  89. data/components/checks/passive/grep/form_upload.rb +1 -1
  90. data/components/checks/passive/grep/hsts.rb +4 -3
  91. data/components/checks/passive/grep/html_objects.rb +1 -1
  92. data/components/checks/passive/grep/http_only_cookies.rb +5 -3
  93. data/components/checks/passive/grep/insecure_cookies.rb +5 -3
  94. data/components/checks/passive/grep/insecure_cors_policy.rb +1 -1
  95. data/components/checks/passive/grep/mixed_resource.rb +1 -1
  96. data/components/checks/passive/grep/password_autocomplete.rb +1 -1
  97. data/components/checks/passive/grep/private_ip.rb +1 -1
  98. data/components/checks/passive/grep/ssn.rb +6 -3
  99. data/components/checks/passive/grep/unencrypted_password_forms.rb +1 -1
  100. data/components/checks/passive/grep/x_frame_options.rb +4 -3
  101. data/components/checks/passive/htaccess_limit.rb +1 -1
  102. data/components/checks/passive/http_put.rb +1 -1
  103. data/components/checks/passive/insecure_client_access_policy.rb +2 -2
  104. data/components/checks/passive/insecure_cross_domain_policy_access.rb +2 -2
  105. data/components/checks/passive/insecure_cross_domain_policy_headers.rb +2 -2
  106. data/components/checks/passive/interesting_responses.rb +1 -1
  107. data/components/checks/passive/localstart_asp.rb +1 -1
  108. data/components/checks/passive/origin_spoof_access_restriction_bypass.rb +1 -1
  109. data/components/checks/passive/webdav.rb +1 -1
  110. data/components/checks/passive/xst.rb +1 -1
  111. data/components/fingerprinters/frameworks/aspx_mvc.rb +1 -1
  112. data/components/fingerprinters/frameworks/cakephp.rb +1 -1
  113. data/components/fingerprinters/frameworks/cherrypy.rb +1 -1
  114. data/components/fingerprinters/frameworks/django.rb +1 -1
  115. data/components/fingerprinters/frameworks/jsf.rb +1 -1
  116. data/components/fingerprinters/frameworks/nette.rb +1 -1
  117. data/components/fingerprinters/frameworks/rack.rb +1 -1
  118. data/components/fingerprinters/frameworks/rails.rb +1 -1
  119. data/components/fingerprinters/frameworks/symfony.rb +1 -1
  120. data/components/fingerprinters/languages/asp.rb +1 -1
  121. data/components/fingerprinters/languages/aspx.rb +1 -1
  122. data/components/fingerprinters/languages/java.rb +1 -1
  123. data/components/fingerprinters/languages/php.rb +1 -1
  124. data/components/fingerprinters/languages/python.rb +1 -1
  125. data/components/fingerprinters/languages/ruby.rb +1 -1
  126. data/components/fingerprinters/os/bsd.rb +1 -1
  127. data/components/fingerprinters/os/linux.rb +1 -1
  128. data/components/fingerprinters/os/solaris.rb +1 -1
  129. data/components/fingerprinters/os/unix.rb +1 -1
  130. data/components/fingerprinters/os/windows.rb +1 -1
  131. data/components/fingerprinters/servers/apache.rb +1 -1
  132. data/components/fingerprinters/servers/gunicorn.rb +1 -1
  133. data/components/fingerprinters/servers/iis.rb +1 -1
  134. data/components/fingerprinters/servers/jetty.rb +1 -1
  135. data/components/fingerprinters/servers/nginx.rb +1 -1
  136. data/components/fingerprinters/servers/tomcat.rb +1 -1
  137. data/components/path_extractors/anchors.rb +1 -1
  138. data/components/path_extractors/areas.rb +1 -1
  139. data/components/path_extractors/comments.rb +1 -1
  140. data/components/path_extractors/data_url.rb +1 -1
  141. data/components/path_extractors/forms.rb +1 -1
  142. data/components/path_extractors/frames.rb +1 -1
  143. data/components/path_extractors/generic.rb +1 -1
  144. data/components/path_extractors/links.rb +1 -1
  145. data/components/path_extractors/meta_refresh.rb +3 -3
  146. data/components/path_extractors/scripts.rb +1 -1
  147. data/components/plugins/autologin.rb +16 -24
  148. data/components/plugins/beep_notify.rb +1 -1
  149. data/components/plugins/content_types.rb +1 -1
  150. data/components/plugins/cookie_collector.rb +1 -1
  151. data/components/plugins/defaults/autothrottle.rb +1 -1
  152. data/components/plugins/defaults/healthmap.rb +1 -1
  153. data/components/plugins/defaults/meta/remedies/discovery.rb +10 -9
  154. data/components/plugins/defaults/meta/remedies/timing_attacks.rb +1 -1
  155. data/components/plugins/defaults/meta/uniformity.rb +1 -1
  156. data/components/plugins/email_notify.rb +3 -5
  157. data/components/plugins/exec.rb +1 -1
  158. data/components/plugins/form_dicattack.rb +1 -1
  159. data/components/plugins/headers_collector.rb +1 -1
  160. data/components/plugins/http_dicattack.rb +1 -1
  161. data/components/plugins/login_script.rb +47 -22
  162. data/components/plugins/metrics.rb +1 -1
  163. data/components/plugins/proxy.rb +69 -44
  164. data/components/plugins/proxy/panel/help.html.erb +1 -18
  165. data/components/plugins/proxy/panel/inspect.html.erb +4 -3
  166. data/components/plugins/proxy/panel/page_accordion.html.erb +78 -43
  167. data/components/plugins/proxy/panel/panel.html.erb +2 -7
  168. data/components/plugins/proxy/template_scope.rb +1 -1
  169. data/components/plugins/restrict_to_dom_state.rb +3 -15
  170. data/components/plugins/script.rb +1 -1
  171. data/components/plugins/uncommon_headers.rb +1 -1
  172. data/components/plugins/vector_collector.rb +1 -1
  173. data/components/plugins/vector_feed.rb +3 -11
  174. data/components/plugins/waf_detector.rb +1 -1
  175. data/components/reporters/ap.rb +1 -1
  176. data/components/reporters/html.rb +2 -2
  177. data/components/reporters/json.rb +1 -1
  178. data/components/reporters/marshal.rb +1 -1
  179. data/components/reporters/plugin_formatters/html/autologin.rb +1 -1
  180. data/components/reporters/plugin_formatters/html/content_types.rb +1 -1
  181. data/components/reporters/plugin_formatters/html/cookie_collector.rb +1 -1
  182. data/components/reporters/plugin_formatters/html/exec.rb +1 -1
  183. data/components/reporters/plugin_formatters/html/form_dicattack.rb +1 -1
  184. data/components/reporters/plugin_formatters/html/healthmap.rb +1 -1
  185. data/components/reporters/plugin_formatters/html/http_dicattack.rb +1 -1
  186. data/components/reporters/plugin_formatters/html/login_script.rb +1 -1
  187. data/components/reporters/plugin_formatters/html/metrics.rb +1 -1
  188. data/components/reporters/plugin_formatters/html/uncommon_headers.rb +1 -1
  189. data/components/reporters/plugin_formatters/html/uniformity.rb +1 -1
  190. data/components/reporters/plugin_formatters/html/vector_collector.rb +1 -1
  191. data/components/reporters/plugin_formatters/html/waf_detector.rb +1 -1
  192. data/components/reporters/plugin_formatters/stdout/autologin.rb +1 -1
  193. data/components/reporters/plugin_formatters/stdout/content_types.rb +1 -1
  194. data/components/reporters/plugin_formatters/stdout/cookie_collector.rb +1 -1
  195. data/components/reporters/plugin_formatters/stdout/exec.rb +1 -1
  196. data/components/reporters/plugin_formatters/stdout/form_dicattack.rb +1 -1
  197. data/components/reporters/plugin_formatters/stdout/healthmap.rb +1 -1
  198. data/components/reporters/plugin_formatters/stdout/http_dicattack.rb +1 -1
  199. data/components/reporters/plugin_formatters/stdout/login_script.rb +1 -1
  200. data/components/reporters/plugin_formatters/stdout/metrics.rb +1 -1
  201. data/components/reporters/plugin_formatters/stdout/uncommon_headers.rb +1 -1
  202. data/components/reporters/plugin_formatters/stdout/uniformity.rb +1 -1
  203. data/components/reporters/plugin_formatters/stdout/vector_collector.rb +1 -1
  204. data/components/reporters/plugin_formatters/stdout/waf_detector.rb +1 -1
  205. data/components/reporters/plugin_formatters/xml/autologin.rb +1 -1
  206. data/components/reporters/plugin_formatters/xml/content_types.rb +1 -1
  207. data/components/reporters/plugin_formatters/xml/cookie_collector.rb +1 -1
  208. data/components/reporters/plugin_formatters/xml/exec.rb +1 -1
  209. data/components/reporters/plugin_formatters/xml/form_dicattack.rb +1 -1
  210. data/components/reporters/plugin_formatters/xml/healthmap.rb +1 -1
  211. data/components/reporters/plugin_formatters/xml/http_dicattack.rb +1 -1
  212. data/components/reporters/plugin_formatters/xml/login_script.rb +1 -1
  213. data/components/reporters/plugin_formatters/xml/metrics.rb +1 -1
  214. data/components/reporters/plugin_formatters/xml/uncommon_headers.rb +1 -1
  215. data/components/reporters/plugin_formatters/xml/uniformity.rb +1 -1
  216. data/components/reporters/plugin_formatters/xml/vector_collector.rb +1 -1
  217. data/components/reporters/plugin_formatters/xml/waf_detector.rb +1 -1
  218. data/components/reporters/stdout.rb +1 -1
  219. data/components/reporters/txt.rb +1 -1
  220. data/components/reporters/xml.rb +29 -4
  221. data/components/reporters/yaml.rb +1 -1
  222. data/lib/arachni.rb +48 -3
  223. data/lib/arachni/banner.rb +1 -1
  224. data/lib/arachni/browser.rb +601 -358
  225. data/lib/arachni/browser/element_locator.rb +25 -6
  226. data/lib/arachni/browser/javascript.rb +103 -35
  227. data/lib/arachni/browser/javascript/dom_monitor.rb +1 -1
  228. data/lib/arachni/browser/javascript/proxy.rb +28 -16
  229. data/lib/arachni/browser/javascript/proxy/stub.rb +1 -1
  230. data/lib/arachni/browser/javascript/scripts/dom_monitor.js +138 -67
  231. data/lib/arachni/browser/javascript/scripts/polyfills.js +28 -0
  232. data/lib/arachni/browser/javascript/scripts/taint_tracer.js +27 -6
  233. data/lib/arachni/browser/javascript/taint_tracer.rb +1 -1
  234. data/lib/arachni/browser/javascript/taint_tracer/frame.rb +1 -1
  235. data/lib/arachni/browser/javascript/taint_tracer/frame/called_function.rb +1 -1
  236. data/lib/arachni/browser/javascript/taint_tracer/sink/base.rb +1 -1
  237. data/lib/arachni/browser/javascript/taint_tracer/sink/data_flow.rb +1 -1
  238. data/lib/arachni/browser/javascript/taint_tracer/sink/execution_flow.rb +1 -1
  239. data/lib/arachni/browser_cluster.rb +10 -14
  240. data/lib/arachni/browser_cluster/job.rb +1 -1
  241. data/lib/arachni/browser_cluster/job/result.rb +1 -1
  242. data/lib/arachni/browser_cluster/jobs/browser_provider.rb +1 -1
  243. data/lib/arachni/browser_cluster/jobs/{resource_exploration.rb → dom_exploration.rb} +5 -5
  244. data/lib/arachni/browser_cluster/jobs/{resource_exploration → dom_exploration}/event_trigger.rb +7 -4
  245. data/lib/arachni/browser_cluster/jobs/{resource_exploration → dom_exploration}/event_trigger/result.rb +3 -3
  246. data/lib/arachni/browser_cluster/jobs/{resource_exploration → dom_exploration}/result.rb +2 -2
  247. data/lib/arachni/browser_cluster/jobs/taint_trace.rb +3 -3
  248. data/lib/arachni/browser_cluster/jobs/taint_trace/event_trigger.rb +2 -2
  249. data/lib/arachni/browser_cluster/jobs/taint_trace/event_trigger/result.rb +2 -2
  250. data/lib/arachni/browser_cluster/jobs/taint_trace/result.rb +1 -1
  251. data/lib/arachni/browser_cluster/worker.rb +12 -40
  252. data/lib/arachni/check.rb +1 -1
  253. data/lib/arachni/check/auditor.rb +15 -1
  254. data/lib/arachni/check/base.rb +1 -1
  255. data/lib/arachni/check/manager.rb +1 -1
  256. data/lib/arachni/component.rb +1 -1
  257. data/lib/arachni/component/base.rb +5 -5
  258. data/lib/arachni/component/manager.rb +39 -13
  259. data/lib/arachni/component/options.rb +1 -1
  260. data/lib/arachni/component/options/address.rb +1 -1
  261. data/lib/arachni/component/options/base.rb +1 -1
  262. data/lib/arachni/component/options/bool.rb +1 -1
  263. data/lib/arachni/component/options/float.rb +1 -1
  264. data/lib/arachni/component/options/int.rb +1 -1
  265. data/lib/arachni/component/options/multiple_choice.rb +1 -1
  266. data/lib/arachni/component/options/object.rb +1 -1
  267. data/lib/arachni/component/options/path.rb +1 -1
  268. data/lib/arachni/component/options/port.rb +1 -1
  269. data/lib/arachni/component/options/string.rb +1 -1
  270. data/lib/arachni/component/options/url.rb +1 -1
  271. data/lib/arachni/component/output.rb +1 -1
  272. data/lib/arachni/component/utilities.rb +1 -1
  273. data/lib/arachni/data.rb +1 -1
  274. data/lib/arachni/data/framework.rb +1 -1
  275. data/lib/arachni/data/framework/rpc.rb +1 -1
  276. data/lib/arachni/data/issues.rb +1 -1
  277. data/lib/arachni/data/plugins.rb +1 -1
  278. data/lib/arachni/data/session.rb +1 -1
  279. data/lib/arachni/element/base.rb +19 -5
  280. data/lib/arachni/element/body.rb +1 -1
  281. data/lib/arachni/element/capabilities/analyzable.rb +1 -1
  282. data/lib/arachni/element/capabilities/analyzable/differential.rb +15 -5
  283. data/lib/arachni/element/capabilities/analyzable/signature.rb +147 -89
  284. data/lib/arachni/element/capabilities/analyzable/timeout.rb +43 -16
  285. data/lib/arachni/element/capabilities/auditable.rb +20 -15
  286. data/lib/arachni/element/capabilities/dom_only.rb +5 -4
  287. data/lib/arachni/element/capabilities/inputtable.rb +62 -12
  288. data/lib/arachni/element/capabilities/mutable.rb +74 -13
  289. data/lib/arachni/element/capabilities/refreshable.rb +1 -1
  290. data/lib/arachni/element/capabilities/submittable.rb +5 -2
  291. data/lib/arachni/element/capabilities/with_auditor.rb +1 -1
  292. data/lib/arachni/element/capabilities/with_auditor/output.rb +5 -5
  293. data/lib/arachni/element/capabilities/with_dom.rb +1 -1
  294. data/lib/arachni/element/capabilities/with_node.rb +2 -2
  295. data/lib/arachni/element/capabilities/with_scope.rb +1 -1
  296. data/lib/arachni/element/capabilities/with_scope/scope.rb +1 -1
  297. data/lib/arachni/element/capabilities/with_source.rb +4 -4
  298. data/lib/arachni/element/cookie.rb +57 -34
  299. data/lib/arachni/element/cookie/capabilities/inputtable.rb +1 -1
  300. data/lib/arachni/element/cookie/capabilities/mutable.rb +10 -1
  301. data/lib/arachni/element/cookie/capabilities/with_dom.rb +1 -1
  302. data/lib/arachni/element/cookie/dom.rb +1 -1
  303. data/lib/arachni/element/dom.rb +1 -15
  304. data/lib/arachni/element/dom/capabilities/auditable.rb +1 -1
  305. data/lib/arachni/element/dom/capabilities/inputtable.rb +1 -1
  306. data/lib/arachni/element/dom/capabilities/locatable.rb +29 -0
  307. data/lib/arachni/element/dom/capabilities/mutable.rb +11 -1
  308. data/lib/arachni/element/dom/capabilities/submittable.rb +2 -2
  309. data/lib/arachni/element/form.rb +33 -14
  310. data/lib/arachni/element/form/capabilities/auditable.rb +1 -1
  311. data/lib/arachni/element/form/capabilities/mutable.rb +18 -17
  312. data/lib/arachni/element/form/capabilities/submittable.rb +1 -1
  313. data/lib/arachni/element/form/capabilities/with_dom.rb +2 -1
  314. data/lib/arachni/element/form/dom.rb +3 -2
  315. data/lib/arachni/element/generic_dom.rb +1 -1
  316. data/lib/arachni/element/header.rb +16 -4
  317. data/lib/arachni/element/header/capabilities/inputtable.rb +1 -1
  318. data/lib/arachni/element/header/capabilities/mutable.rb +11 -1
  319. data/lib/arachni/element/json.rb +2 -2
  320. data/lib/arachni/element/json/capabilities/inputtable.rb +1 -1
  321. data/lib/arachni/element/json/capabilities/mutable.rb +8 -2
  322. data/lib/arachni/element/link.rb +14 -7
  323. data/lib/arachni/element/link/capabilities/auditable.rb +1 -1
  324. data/lib/arachni/element/link/capabilities/submittable.rb +1 -1
  325. data/lib/arachni/element/link/capabilities/with_dom.rb +8 -1
  326. data/lib/arachni/element/link/dom.rb +2 -1
  327. data/lib/arachni/element/link/dom/capabilities/submittable.rb +1 -1
  328. data/lib/arachni/element/link_template.rb +8 -3
  329. data/lib/arachni/element/link_template/capabilities/auditable.rb +1 -1
  330. data/lib/arachni/element/link_template/capabilities/inputtable.rb +1 -1
  331. data/lib/arachni/element/link_template/capabilities/with_dom.rb +1 -1
  332. data/lib/arachni/element/link_template/dom.rb +2 -1
  333. data/lib/arachni/element/link_template/dom/capabilities/submittable.rb +1 -1
  334. data/lib/arachni/element/path.rb +1 -1
  335. data/lib/arachni/element/server.rb +3 -3
  336. data/lib/arachni/element/ui_form.rb +24 -21
  337. data/lib/arachni/element/ui_form/dom.rb +12 -3
  338. data/lib/arachni/element/ui_input.rb +17 -11
  339. data/lib/arachni/element/{input → ui_input}/dom.rb +11 -2
  340. data/lib/arachni/element/xml.rb +3 -3
  341. data/lib/arachni/element/xml/capabilities/inputtable.rb +7 -1
  342. data/lib/arachni/element/xml/capabilities/mutable.rb +7 -13
  343. data/lib/arachni/element_filter.rb +1 -1
  344. data/lib/arachni/error.rb +1 -1
  345. data/lib/arachni/ethon/easy.rb +1 -1
  346. data/lib/arachni/framework.rb +2 -5
  347. data/lib/arachni/framework/parts/audit.rb +8 -2
  348. data/lib/arachni/framework/parts/browser.rb +8 -9
  349. data/lib/arachni/framework/parts/check.rb +2 -6
  350. data/lib/arachni/framework/parts/data.rb +23 -8
  351. data/lib/arachni/framework/parts/platform.rb +1 -1
  352. data/lib/arachni/framework/parts/plugin.rb +2 -8
  353. data/lib/arachni/framework/parts/report.rb +3 -9
  354. data/lib/arachni/framework/parts/scope.rb +1 -1
  355. data/lib/arachni/framework/parts/state.rb +8 -8
  356. data/lib/arachni/http.rb +1 -1
  357. data/lib/arachni/http/client.rb +72 -68
  358. data/lib/arachni/http/client/dynamic_404_handler.rb +85 -60
  359. data/lib/arachni/http/cookie_jar.rb +48 -27
  360. data/lib/arachni/http/headers.rb +4 -3
  361. data/lib/arachni/http/message.rb +17 -3
  362. data/lib/arachni/http/message/scope.rb +1 -1
  363. data/lib/arachni/http/proxy_server.rb +46 -344
  364. data/lib/arachni/http/proxy_server/connection.rb +316 -0
  365. data/lib/arachni/http/proxy_server/ssl_interceptor.rb +102 -0
  366. data/lib/arachni/http/proxy_server/tunnel.rb +54 -0
  367. data/lib/arachni/http/request.rb +126 -29
  368. data/lib/arachni/http/request/scope.rb +1 -1
  369. data/lib/arachni/http/response.rb +42 -12
  370. data/lib/arachni/http/response/scope.rb +1 -1
  371. data/lib/arachni/issue.rb +2 -2
  372. data/lib/arachni/issue/severity.rb +1 -1
  373. data/lib/arachni/issue/severity/base.rb +1 -1
  374. data/lib/arachni/option_group.rb +1 -1
  375. data/lib/arachni/option_groups.rb +1 -1
  376. data/lib/arachni/option_groups/audit.rb +20 -4
  377. data/lib/arachni/option_groups/browser_cluster.rb +8 -4
  378. data/lib/arachni/option_groups/datastore.rb +1 -1
  379. data/lib/arachni/option_groups/dispatcher.rb +1 -1
  380. data/lib/arachni/option_groups/http.rb +2 -2
  381. data/lib/arachni/option_groups/input.rb +6 -3
  382. data/lib/arachni/option_groups/output.rb +1 -1
  383. data/lib/arachni/option_groups/paths.rb +10 -3
  384. data/lib/arachni/option_groups/rpc.rb +1 -1
  385. data/lib/arachni/option_groups/scope.rb +35 -6
  386. data/lib/arachni/option_groups/session.rb +1 -1
  387. data/lib/arachni/option_groups/snapshot.rb +1 -1
  388. data/lib/arachni/options.rb +1 -1
  389. data/lib/arachni/page.rb +26 -12
  390. data/lib/arachni/page/dom.rb +29 -22
  391. data/lib/arachni/page/dom/transition.rb +2 -2
  392. data/lib/arachni/page/scope.rb +1 -1
  393. data/lib/arachni/parser.rb +42 -5
  394. data/lib/arachni/platform.rb +1 -1
  395. data/lib/arachni/platform/fingerprinter.rb +1 -1
  396. data/lib/arachni/platform/list.rb +1 -1
  397. data/lib/arachni/platform/manager.rb +2 -2
  398. data/lib/arachni/plugin.rb +1 -1
  399. data/lib/arachni/plugin/base.rb +1 -1
  400. data/lib/arachni/plugin/formatter.rb +1 -1
  401. data/lib/arachni/plugin/manager.rb +7 -13
  402. data/lib/arachni/processes.rb +1 -1
  403. data/lib/arachni/processes/dispatchers.rb +2 -2
  404. data/lib/arachni/processes/executables/base.rb +45 -4
  405. data/lib/arachni/processes/executables/browser.rb +91 -0
  406. data/lib/arachni/processes/executables/rest_service.rb +14 -0
  407. data/lib/arachni/processes/helpers.rb +1 -1
  408. data/lib/arachni/processes/helpers/dispatchers.rb +1 -1
  409. data/lib/arachni/processes/helpers/instances.rb +1 -1
  410. data/lib/arachni/processes/helpers/processes.rb +1 -1
  411. data/lib/arachni/processes/instances.rb +5 -5
  412. data/lib/arachni/processes/manager.rb +68 -9
  413. data/lib/arachni/report.rb +1 -1
  414. data/lib/arachni/reporter.rb +1 -1
  415. data/lib/arachni/reporter/base.rb +1 -1
  416. data/lib/arachni/reporter/formatter_manager.rb +4 -2
  417. data/lib/arachni/reporter/manager.rb +3 -2
  418. data/lib/arachni/reporter/options.rb +1 -1
  419. data/lib/arachni/rest/server.rb +231 -0
  420. data/lib/arachni/rest/server/instance_helpers.rb +37 -0
  421. data/lib/arachni/rpc/client/base.rb +1 -1
  422. data/lib/arachni/rpc/client/dispatcher.rb +1 -1
  423. data/lib/arachni/rpc/client/instance.rb +1 -1
  424. data/lib/arachni/rpc/client/instance/framework.rb +1 -1
  425. data/lib/arachni/rpc/client/instance/service.rb +1 -1
  426. data/lib/arachni/rpc/serializer.rb +1 -1
  427. data/lib/arachni/rpc/server/active_options.rb +20 -3
  428. data/lib/arachni/rpc/server/base.rb +1 -1
  429. data/lib/arachni/rpc/server/check/manager.rb +1 -1
  430. data/lib/arachni/rpc/server/dispatcher.rb +4 -4
  431. data/lib/arachni/rpc/server/dispatcher/node.rb +1 -1
  432. data/lib/arachni/rpc/server/dispatcher/service.rb +1 -1
  433. data/lib/arachni/rpc/server/framework.rb +3 -1
  434. data/lib/arachni/rpc/server/framework/distributor.rb +1 -1
  435. data/lib/arachni/rpc/server/framework/master.rb +1 -1
  436. data/lib/arachni/rpc/server/framework/multi_instance.rb +1 -1
  437. data/lib/arachni/rpc/server/framework/slave.rb +1 -1
  438. data/lib/arachni/rpc/server/instance.rb +1 -3
  439. data/lib/arachni/rpc/server/output.rb +1 -1
  440. data/lib/arachni/rpc/server/plugin/manager.rb +1 -1
  441. data/lib/arachni/ruby.rb +1 -2
  442. data/lib/arachni/ruby/array.rb +1 -1
  443. data/lib/arachni/ruby/hash.rb +1 -1
  444. data/lib/arachni/ruby/object.rb +15 -1
  445. data/lib/arachni/ruby/set.rb +1 -1
  446. data/lib/arachni/ruby/string.rb +23 -4
  447. data/lib/arachni/ruby/webrick.rb +1 -1
  448. data/lib/arachni/ruby/webrick/cookie.rb +1 -1
  449. data/lib/arachni/ruby/webrick/httprequest.rb +1 -1
  450. data/lib/arachni/scope.rb +1 -1
  451. data/lib/arachni/{watir → selenium/webdriver}/element.rb +12 -13
  452. data/lib/arachni/session.rb +19 -4
  453. data/lib/arachni/snapshot.rb +9 -5
  454. data/lib/arachni/state.rb +1 -1
  455. data/lib/arachni/state/audit.rb +1 -1
  456. data/lib/arachni/state/element_filter.rb +1 -1
  457. data/lib/arachni/state/framework.rb +1 -1
  458. data/lib/arachni/state/framework/rpc.rb +1 -1
  459. data/lib/arachni/state/http.rb +1 -1
  460. data/lib/arachni/state/options.rb +1 -1
  461. data/lib/arachni/state/plugins.rb +1 -1
  462. data/lib/arachni/support.rb +2 -1
  463. data/lib/arachni/support/buffer.rb +1 -1
  464. data/lib/arachni/support/buffer/autoflush.rb +1 -1
  465. data/lib/arachni/support/buffer/base.rb +1 -1
  466. data/lib/arachni/support/cache.rb +1 -1
  467. data/lib/arachni/support/cache/base.rb +20 -8
  468. data/lib/arachni/support/cache/least_cost_replacement.rb +1 -1
  469. data/lib/arachni/support/cache/least_recently_pushed.rb +1 -1
  470. data/lib/arachni/support/cache/least_recently_used.rb +8 -9
  471. data/lib/arachni/support/cache/preference.rb +7 -20
  472. data/lib/arachni/support/cache/random_replacement.rb +1 -1
  473. data/lib/arachni/support/crypto.rb +1 -1
  474. data/lib/arachni/support/crypto/rsa_aes_cbc.rb +1 -1
  475. data/lib/arachni/support/database.rb +1 -1
  476. data/lib/arachni/support/database/base.rb +2 -2
  477. data/lib/arachni/support/database/hash.rb +1 -1
  478. data/lib/arachni/support/database/queue.rb +1 -1
  479. data/lib/arachni/support/glob.rb +35 -0
  480. data/lib/arachni/support/lookup.rb +1 -1
  481. data/lib/arachni/support/lookup/base.rb +1 -1
  482. data/lib/arachni/support/lookup/hash_set.rb +1 -1
  483. data/lib/arachni/support/lookup/moolb.rb +1 -1
  484. data/lib/arachni/support/mixins.rb +1 -1
  485. data/lib/arachni/support/mixins/observable.rb +1 -1
  486. data/lib/arachni/support/mixins/terminal.rb +1 -1
  487. data/lib/arachni/support/profiler.rb +12 -10
  488. data/lib/arachni/support/signature.rb +12 -5
  489. data/lib/arachni/trainer.rb +18 -4
  490. data/lib/arachni/ui/foo/output.rb +17 -1
  491. data/lib/arachni/uri.rb +285 -203
  492. data/lib/arachni/uri/scope.rb +13 -2
  493. data/lib/arachni/utilities.rb +22 -5
  494. data/lib/arachni/version.rb +1 -1
  495. data/lib/version +1 -1
  496. data/spec/arachni/browser/element_locator_spec.rb +42 -14
  497. data/spec/arachni/browser/javascript/dom_monitor_spec.rb +34 -304
  498. data/spec/arachni/browser/javascript/polyfills_spec.rb +35 -0
  499. data/spec/arachni/browser/javascript/taint_tracer_spec.rb +24 -4
  500. data/spec/arachni/browser/javascript_spec.rb +92 -65
  501. data/spec/arachni/browser_cluster/job_spec.rb +3 -3
  502. data/spec/arachni/browser_cluster/jobs/{resource_exploration → dom_exploration}/event_trigger/result_spec.rb +1 -1
  503. data/spec/arachni/browser_cluster/jobs/{resource_exploration → dom_exploration}/event_trigger_spec.rb +4 -4
  504. data/spec/arachni/browser_cluster/jobs/{resource_exploration → dom_exploration}/result_spec.rb +1 -1
  505. data/spec/arachni/browser_cluster/jobs/{resource_exploration_spec.rb → dom_exploration_spec.rb} +4 -4
  506. data/spec/arachni/browser_cluster/jobs/taint_tracer_spec.rb +9 -9
  507. data/spec/arachni/browser_cluster/worker_spec.rb +46 -67
  508. data/spec/arachni/browser_cluster_spec.rb +19 -17
  509. data/spec/arachni/browser_spec.rb +506 -183
  510. data/spec/arachni/check/auditor_spec.rb +70 -25
  511. data/spec/arachni/component/manager_spec.rb +19 -20
  512. data/spec/arachni/data/framework/rpc_spec.rb +1 -1
  513. data/spec/arachni/data/framework_spec.rb +1 -1
  514. data/spec/arachni/data/issues_spec.rb +3 -3
  515. data/spec/arachni/element/capabilities/analyzable/differential_spec.rb +44 -0
  516. data/spec/arachni/element/capabilities/analyzable/signature_spec.rb +33 -162
  517. data/spec/arachni/element/capabilities/analyzable/timeout_spec.rb +4 -4
  518. data/spec/arachni/element/cookie_spec.rb +98 -49
  519. data/spec/arachni/element/form/dom_spec.rb +1 -22
  520. data/spec/arachni/element/form_spec.rb +7 -7
  521. data/spec/arachni/element/header_spec.rb +2 -2
  522. data/spec/arachni/element/json_spec.rb +2 -2
  523. data/spec/arachni/element/link/dom_spec.rb +1 -22
  524. data/spec/arachni/element/link_spec.rb +17 -1
  525. data/spec/arachni/element/link_template/dom_spec.rb +1 -22
  526. data/spec/arachni/element/link_template_spec.rb +3 -3
  527. data/spec/arachni/element/ui_form/{ui_form_dom_spec.rb → dom_spec.rb} +72 -22
  528. data/spec/arachni/element/ui_form_spec.rb +1 -0
  529. data/spec/arachni/element/ui_input/dom_spec.rb +64 -22
  530. data/spec/arachni/element/ui_input_spec.rb +1 -0
  531. data/spec/arachni/element/xml_spec.rb +1 -0
  532. data/spec/arachni/framework/parts/audit_spec.rb +7 -5
  533. data/spec/arachni/framework/parts/browser_spec.rb +8 -8
  534. data/spec/arachni/framework/parts/check_spec.rb +1 -1
  535. data/spec/arachni/framework/parts/data_spec.rb +4 -4
  536. data/spec/arachni/framework/parts/scope_spec.rb +2 -2
  537. data/spec/arachni/framework_spec.rb +1 -1
  538. data/spec/arachni/http/client/dynamic_404_handlers_spec.rb +26 -13
  539. data/spec/arachni/http/client_spec.rb +80 -45
  540. data/spec/arachni/http/cookie_jar_spec.rb +6 -6
  541. data/spec/arachni/http/proxy_server_spec.rb +69 -66
  542. data/spec/arachni/http/request_spec.rb +147 -23
  543. data/spec/arachni/http/response/scope_spec.rb +12 -12
  544. data/spec/arachni/http/response_spec.rb +62 -4
  545. data/spec/arachni/issue_spec.rb +6 -6
  546. data/spec/arachni/option_groups/audit_spec.rb +25 -8
  547. data/spec/arachni/option_groups/browser_cluster_spec.rb +27 -1
  548. data/spec/arachni/option_groups/dispatcher_spec.rb +3 -3
  549. data/spec/arachni/option_groups/input_spec.rb +9 -9
  550. data/spec/arachni/option_groups/paths_spec.rb +2 -2
  551. data/spec/arachni/option_groups/scope_spec.rb +32 -16
  552. data/spec/arachni/options_spec.rb +4 -4
  553. data/spec/arachni/page/dom/transition_spec.rb +17 -10
  554. data/spec/arachni/page/dom_spec.rb +19 -0
  555. data/spec/arachni/page/scope_spec.rb +4 -4
  556. data/spec/arachni/page_spec.rb +15 -15
  557. data/spec/arachni/platform/manager_spec.rb +2 -2
  558. data/spec/arachni/plugin/base_spec.rb +1 -0
  559. data/spec/arachni/reporter/base_spec.rb +2 -2
  560. data/spec/arachni/reporter/manager_spec.rb +2 -2
  561. data/spec/arachni/rest/server_spec.rb +495 -0
  562. data/spec/arachni/rpc/server/active_options_spec.rb +63 -12
  563. data/spec/arachni/rpc/server/base_spec.rb +1 -1
  564. data/spec/arachni/rpc/server/framework/distributor_spec.rb +2 -2
  565. data/spec/arachni/rpc/server/framework_multi_spec.rb +6 -6
  566. data/spec/arachni/rpc/server/framework_spec.rb +4 -4
  567. data/spec/arachni/rpc/server/instance_spec.rb +24 -24
  568. data/spec/arachni/ruby/array_spec.rb +2 -2
  569. data/spec/arachni/ruby/string_spec.rb +52 -0
  570. data/spec/arachni/session_spec.rb +19 -2
  571. data/spec/arachni/snapshot_spec.rb +1 -1
  572. data/spec/arachni/state/audit_spec.rb +1 -1
  573. data/spec/arachni/state/framework_spec.rb +2 -2
  574. data/spec/arachni/support/cache/least_recently_used_spec.rb +0 -2
  575. data/spec/arachni/support/glob_spec.rb +75 -0
  576. data/spec/arachni/support/lookup/hash_set_spec.rb +1 -1
  577. data/spec/arachni/support/lookup/moolb_spec.rb +2 -2
  578. data/spec/arachni/support/signature_spec.rb +4 -4
  579. data/spec/arachni/trainer_spec.rb +48 -4
  580. data/spec/arachni/uri/scope_spec.rb +54 -10
  581. data/spec/arachni/uri_spec.rb +110 -89
  582. data/spec/arachni/utilities_spec.rb +8 -8
  583. data/spec/components/checks/active/code_injection_spec.rb +9 -9
  584. data/spec/components/checks/active/file_inclusion_spec.rb +20 -20
  585. data/spec/components/checks/active/ldap_injection_spec.rb +1 -1
  586. data/spec/components/checks/active/no_sql_injection_spec.rb +1 -1
  587. data/spec/components/checks/active/os_cmd_injection_spec.rb +3 -3
  588. data/spec/components/checks/active/path_traversal_spec.rb +11 -11
  589. data/spec/components/checks/active/response_splitting_spec.rb +2 -2
  590. data/spec/components/checks/active/rfi_spec.rb +3 -3
  591. data/spec/components/checks/active/session_fixation_spec.rb +1 -1
  592. data/spec/components/checks/active/source_code_disclosure_spec.rb +4 -4
  593. data/spec/components/checks/active/sql_injection_spec.rb +58 -59
  594. data/spec/components/checks/active/unvalidated_redirect_spec.rb +2 -2
  595. data/spec/components/checks/active/xpath_injection_spec.rb +3 -3
  596. data/spec/components/checks/active/xss_dom_script_context_spec.rb +1 -1
  597. data/spec/components/checks/active/xss_dom_spec.rb +1 -1
  598. data/spec/components/checks/active/xss_script_context_spec.rb +5 -5
  599. data/spec/components/checks/active/xss_spec.rb +5 -5
  600. data/spec/components/checks/passive/grep/credit_card_spec.rb +1 -1
  601. data/spec/components/checks/passive/grep/emails_spec.rb +12 -2
  602. data/spec/components/checks/passive/grep/ssn_spec.rb +1 -1
  603. data/spec/components/path_extractors/meta_refresh_spec.rb +3 -1
  604. data/spec/components/plugins/exec_spec.rb +2 -2
  605. data/spec/components/plugins/login_script_spec.rb +22 -2
  606. data/spec/components/plugins/vector_feed_spec.rb +3 -3
  607. data/spec/spec_helper.rb +10 -4
  608. data/spec/support/factories/browser_cluster/job.rb +1 -0
  609. data/spec/support/fixtures/check_with_invalid_platforms/with_invalid_platforms.rb +1 -1
  610. data/spec/support/fixtures/checks/test.rb +1 -1
  611. data/spec/support/fixtures/checks/test2.rb +1 -1
  612. data/spec/support/fixtures/checks/test3.rb +1 -1
  613. data/spec/support/fixtures/fingerprinters/test.rb +1 -1
  614. data/spec/support/fixtures/plugins/bad.rb +1 -1
  615. data/spec/support/fixtures/plugins/defaults/default.rb +1 -1
  616. data/spec/support/fixtures/plugins/distributable.rb +1 -1
  617. data/spec/support/fixtures/plugins/loop.rb +1 -1
  618. data/spec/support/fixtures/plugins/suspendable.rb +1 -1
  619. data/spec/support/fixtures/plugins/wait.rb +1 -1
  620. data/spec/support/fixtures/plugins/with_options.rb +1 -1
  621. data/spec/support/fixtures/plugins_with_priorities/p0.rb +1 -1
  622. data/spec/support/fixtures/plugins_with_priorities/p00.rb +1 -1
  623. data/spec/support/fixtures/plugins_with_priorities/p1.rb +1 -1
  624. data/spec/support/fixtures/plugins_with_priorities/p2.rb +1 -1
  625. data/spec/support/fixtures/plugins_with_priorities/p22.rb +1 -1
  626. data/spec/support/fixtures/plugins_with_priorities/p222.rb +1 -1
  627. data/spec/support/fixtures/plugins_with_priorities/p_nil.rb +1 -1
  628. data/spec/support/fixtures/plugins_with_priorities/p_nil2.rb +1 -1
  629. data/spec/support/fixtures/report.afr +0 -0
  630. data/spec/support/fixtures/reporters/base_spec/plugin_formatters/with_formatters/foobar.rb +1 -1
  631. data/spec/support/fixtures/reporters/base_spec/with_formatters.rb +1 -1
  632. data/spec/support/fixtures/reporters/base_spec/with_outfile.rb +1 -1
  633. data/spec/support/fixtures/reporters/base_spec/without_outfile.rb +1 -1
  634. data/spec/support/fixtures/reporters/manager_spec/afr.rb +1 -1
  635. data/spec/support/fixtures/reporters/manager_spec/error.rb +1 -1
  636. data/spec/support/fixtures/reporters/manager_spec/foo.rb +1 -1
  637. data/spec/support/fixtures/run_check/body.rb +1 -1
  638. data/spec/support/fixtures/run_check/cookies.rb +1 -1
  639. data/spec/support/fixtures/run_check/empty.rb +1 -1
  640. data/spec/support/fixtures/run_check/flch.rb +1 -1
  641. data/spec/support/fixtures/run_check/forms.rb +1 -1
  642. data/spec/support/fixtures/run_check/headers.rb +1 -1
  643. data/spec/support/fixtures/run_check/links.rb +1 -1
  644. data/spec/support/fixtures/run_check/nil.rb +1 -1
  645. data/spec/support/fixtures/run_check/path.rb +1 -1
  646. data/spec/support/fixtures/run_check/server.rb +1 -1
  647. data/spec/support/fixtures/signature_check/signature.rb +1 -1
  648. data/spec/support/fixtures/wait_check/wait.rb +1 -1
  649. data/spec/support/helpers/framework.rb +1 -1
  650. data/spec/support/helpers/misc.rb +1 -1
  651. data/spec/support/helpers/paths.rb +1 -1
  652. data/spec/support/helpers/request_helpers.rb +38 -0
  653. data/spec/support/helpers/requires.rb +1 -1
  654. data/spec/support/helpers/resets.rb +1 -1
  655. data/spec/support/helpers/web_server.rb +1 -1
  656. data/spec/support/lib/factory.rb +1 -1
  657. data/spec/support/lib/web_server_client.rb +1 -1
  658. data/spec/support/lib/web_server_dispatcher.rb +1 -1
  659. data/spec/support/lib/web_server_manager.rb +2 -2
  660. data/spec/support/servers/arachni/browser.rb +182 -15
  661. data/spec/support/servers/arachni/browser/javascript/angular-1.2.8.js +1 -1
  662. data/spec/support/servers/arachni/browser/javascript/angular-route.js +1 -1
  663. data/spec/support/servers/arachni/browser/javascript/dom_monitor.rb +27 -4
  664. data/spec/support/servers/arachni/element/capabilities/analyzable/differential.rb +103 -0
  665. data/spec/support/servers/arachni/element/capabilities/analyzable/timeout.rb +5 -2
  666. data/spec/support/servers/arachni/element/header.rb +1 -1
  667. data/spec/support/servers/arachni/http/client.rb +46 -0
  668. data/spec/support/servers/arachni/http/client/dynamic_404_handler.rb +7 -1
  669. data/spec/support/servers/checks/active/code_injection.rb +5 -5
  670. data/spec/support/servers/checks/active/no_sql_injection.rb +0 -6
  671. data/spec/support/servers/checks/active/no_sql_injection_differential.rb +1 -1
  672. data/spec/support/servers/checks/active/sql_injection.rb +5 -2
  673. data/spec/support/servers/checks/active/sql_injection_differential.rb +1 -1
  674. data/spec/support/servers/checks/active/trainer_check.rb +6 -6
  675. data/spec/support/servers/checks/passive/backdoors.rb +1 -0
  676. data/spec/support/servers/checks/passive/backup_directories.rb +2 -0
  677. data/spec/support/servers/checks/passive/backup_files.rb +2 -0
  678. data/spec/support/servers/checks/passive/grep/emails.rb +6 -6
  679. data/spec/support/shared/check.rb +28 -0
  680. data/spec/support/shared/element/capabilities/auditable.rb +76 -13
  681. data/spec/support/shared/element/capabilities/dom_only.rb +5 -6
  682. data/spec/support/shared/element/capabilities/inputtable.rb +74 -4
  683. data/spec/support/shared/element/capabilities/mutable.rb +86 -14
  684. data/spec/support/shared/element/capabilities/submittable.rb +12 -0
  685. data/spec/support/shared/element/capabilities/with_dom.rb +13 -4
  686. data/spec/support/shared/element/capabilities/with_node.rb +1 -1
  687. data/spec/support/shared/element/capabilities/with_source.rb +1 -6
  688. data/spec/support/shared/element/dom/locatable.rb +20 -0
  689. data/spec/support/shared/element/dom/submittable.rb +4 -17
  690. data/spec/support/shared/http/message.rb +37 -5
  691. data/spec/support/shared/support/cache.rb +5 -4
  692. data/ui/cli/framework.rb +4 -3
  693. data/ui/cli/framework/option_parser.rb +20 -8
  694. data/ui/cli/option_parser.rb +1 -1
  695. data/ui/cli/output.rb +40 -4
  696. data/ui/cli/reporter.rb +1 -1
  697. data/ui/cli/reporter/option_parser.rb +4 -4
  698. data/ui/cli/rest/server.rb +43 -0
  699. data/ui/cli/rest/server/option_parser.rb +115 -0
  700. data/ui/cli/restored_framework.rb +1 -1
  701. data/ui/cli/restored_framework/option_parser.rb +1 -1
  702. data/ui/cli/rpc/client/dispatcher_monitor.rb +1 -1
  703. data/ui/cli/rpc/client/dispatcher_monitor/option_parser.rb +1 -1
  704. data/ui/cli/rpc/client/instance.rb +1 -1
  705. data/ui/cli/rpc/client/local.rb +1 -1
  706. data/ui/cli/rpc/client/local/option_parser.rb +1 -1
  707. data/ui/cli/rpc/client/remote.rb +1 -1
  708. data/ui/cli/rpc/client/remote/option_parser.rb +1 -1
  709. data/ui/cli/rpc/server/dispatcher.rb +1 -1
  710. data/ui/cli/rpc/server/dispatcher/option_parser.rb +1 -1
  711. data/ui/cli/utilities.rb +1 -1
  712. metadata +197 -84
  713. data/components/checks/active/no_sql_injection/patterns/mongodb +0 -1
  714. data/components/checks/active/no_sql_injection/regexp_ignore.txt +0 -0
  715. data/components/checks/active/sql_injection/patterns/access +0 -3
  716. data/components/checks/active/sql_injection/patterns/db2 +0 -5
  717. data/components/checks/active/sql_injection/patterns/frontbase +0 -1
  718. data/components/checks/active/sql_injection/patterns/hsqldb +0 -1
  719. data/components/checks/active/sql_injection/patterns/ingres +0 -3
  720. data/components/checks/active/sql_injection/patterns/maxdb +0 -2
  721. data/components/checks/active/sql_injection/patterns/mssql +0 -25
  722. data/components/checks/active/sql_injection/patterns/oracle +0 -6
  723. data/components/checks/active/sql_injection/patterns/sqlite +0 -5
  724. data/components/checks/active/sql_injection/patterns/sybase +0 -3
  725. data/lib/arachni/ruby/io.rb +0 -39
  726. data/lib/arachni/selenium/webdriver/remote/http/typhoeus.rb +0 -63
  727. data/spec/arachni/ruby/io_spec.rb +0 -26
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -11,7 +11,6 @@ require 'nokogiri'
11
11
  # Creates an XML report of the audit.
12
12
  #
13
13
  # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
14
- # @version 0.3.4
15
14
  class Arachni::Reporters::XML < Arachni::Reporter::Base
16
15
 
17
16
  LOCAL_SCHEMA = File.dirname( __FILE__ ) + '/xml/schema.xsd'
@@ -136,11 +135,37 @@ class Arachni::Reporters::XML < Arachni::Reporter::Base
136
135
  has_errors = false
137
136
  xsd.validate( Nokogiri::XML( xml ) ).each do |error|
138
137
  puts error.message
139
- ap error
138
+ puts " -- Line #{error.line}, column #{error.column}, level #{error.level}."
139
+ puts '-' * 100
140
+
141
+ justify = (error.line+10).to_s.size
142
+ lines = xml.lines
143
+ ((error.line-10)..(error.line+10)).each do |i|
144
+ line = lines[i]
145
+ next if i < 0 || !line
146
+ i = i + 1
147
+
148
+ printf( "%#{justify}s | %s", i, line )
149
+
150
+ if i == error.line
151
+ printf( "%#{justify}s |", i )
152
+ line.size.times.each do |c|
153
+ print error.column == c ? '^' : '-'
154
+ end
155
+ puts
156
+ end
157
+ end
158
+
159
+ puts '-' * 100
160
+ puts
161
+
140
162
  has_errors = true
141
163
  end
142
164
 
143
- fail 'XML report could not be validated against the XSD.' if has_errors
165
+ if has_errors
166
+ print_error 'Report could not be validated against the XSD due to the above errors.'
167
+ return
168
+ end
144
169
 
145
170
  IO.binwrite( outfile, xml )
146
171
  print_status "Saved in '#{outfile}'."
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -8,8 +8,9 @@
8
8
 
9
9
  require 'rubygems'
10
10
  require 'bundler/setup'
11
-
12
- require 'oj_mimic_json'
11
+ require 'concurrent'
12
+ require 'pp'
13
+ require 'ap'
13
14
 
14
15
  def ap( obj )
15
16
  super obj, raw: true
@@ -19,6 +20,20 @@ module Arachni
19
20
 
20
21
  class <<self
21
22
 
23
+ # Runs a minor GC to collect young, short-lived objects.
24
+ #
25
+ # Generally called after analysis operations that generate a lot of
26
+ # new temporary objects.
27
+ def collect_young_objects
28
+ GC.start( full_mark: false )
29
+ end
30
+
31
+ def tmpdir
32
+ # On MS Windows Dir.tmpdir can return the path with a shortname,
33
+ # better avoid that as it can be insonsistent with other paths.
34
+ get_long_win32_filename( Dir.tmpdir )
35
+ end
36
+
22
37
  def null_device
23
38
  Gem.win_platform? ? 'NUL' : '/dev/null'
24
39
  end
@@ -39,10 +54,40 @@ module Arachni
39
54
  !!ENV['ARACHNI_PROFILER']
40
55
  end
41
56
 
57
+ if Arachni.windows?
58
+ require 'find'
59
+ require 'fileutils'
60
+ require 'Win32API'
61
+ require 'win32ole'
62
+
63
+ def get_long_win32_filename( short_name )
64
+ short_name = short_name.dup
65
+ max_path = 1024
66
+ long_name = ' ' * max_path
67
+
68
+ lfn_size = Win32API.new(
69
+ "kernel32",
70
+ "GetLongPathName",
71
+ ['P','P','L'],
72
+ 'L'
73
+ ).call( short_name, long_name, max_path )
74
+
75
+ (1..max_path).include?( lfn_size ) ?
76
+ long_name[0..lfn_size-1] : short_name
77
+ end
78
+ else
79
+ def get_long_win32_filename( short_name )
80
+ short_name
81
+ end
82
+ end
42
83
  end
43
84
 
44
85
  end
45
86
 
87
+ if !Arachni.jruby?
88
+ require 'oj_mimic_json'
89
+ end
90
+
46
91
  require_relative 'arachni/version'
47
92
  require_relative 'arachni/banner'
48
93
 
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2010-2015 Tasos Laskos <tasos.laskos@arachni-scanner.com>
2
+ Copyright 2010-2016 Tasos Laskos <tasos.laskos@arachni-scanner.com>
3
3
 
4
4
  This file is part of the Arachni Framework project and is subject to
5
5
  redistribution and commercial restrictions. Please see the Arachni Framework
@@ -8,8 +8,7 @@
8
8
 
9
9
  require 'childprocess'
10
10
  require 'watir-webdriver'
11
- require_relative 'watir/element'
12
- require_relative 'selenium/webdriver/remote/http/typhoeus'
11
+ require_relative 'selenium/webdriver/element'
13
12
  require_relative 'processes/manager'
14
13
  require_relative 'browser/element_locator'
15
14
  require_relative 'browser/javascript'
@@ -70,9 +69,14 @@ class Browser
70
69
  # Let the browser take as long as it needs to complete an operation.
71
70
  WATIR_COM_TIMEOUT = 3600 # 1 hour.
72
71
 
73
- ASSET_EXTENSIONS = Set.new(
74
- ['css', 'js', 'jpg', 'jpeg', 'png', 'gif', 'woff', 'json']
75
- )
72
+ ASSET_EXTENSIONS = Set.new(%w( css js jpg jpeg png gif json ))
73
+
74
+ INPUT_EVENTS = Set.new([
75
+ :change, :blur, :focus, :select, :keyup, :keypress, :keydown, :input
76
+ ])
77
+ INPUT_EVENTS_TO_FORCE = Set.new([
78
+ :focus, :change, :blur, :select
79
+ ])
76
80
 
77
81
  ASSET_EXTRACTORS = [
78
82
  /<\s*link.*?href=['"](.*?)['"].*?>/im,
@@ -92,6 +96,10 @@ class Browser
92
96
  # Watir driver interface.
93
97
  attr_reader :watir
94
98
 
99
+ # @return [Selenium::WebDriver]
100
+ # Selenium driver interface.
101
+ attr_reader :selenium
102
+
95
103
  # @return [Array<Page>]
96
104
  # Same as {#page_snapshots} but it doesn't deduplicate and only contains
97
105
  # pages with sink ({Page::DOM#data_flow_sinks} or {Page::DOM#execution_flow_sinks})
@@ -114,36 +122,44 @@ class Browser
114
122
  attr_reader :skip_states
115
123
 
116
124
  # @return [Integer]
117
- attr_reader :pid
125
+ # PID of the lifeline process managing the browser process.
126
+ attr_reader :lifeline_pid
127
+
128
+ # @return [Integer]
129
+ # PID of the browser process.
130
+ attr_reader :browser_pid
118
131
 
119
132
  attr_reader :last_url
120
133
 
121
- attr_reader :last_dom_url
134
+ class <<self
122
135
 
123
- # @return [Bool]
124
- # `true` if a supported browser is in the OS PATH, `false` otherwise.
125
- def self.has_executable?
126
- !!executable
127
- end
136
+ # @return [Bool]
137
+ # `true` if a supported browser is in the OS PATH, `false` otherwise.
138
+ def has_executable?
139
+ !!executable
140
+ end
128
141
 
129
- # @return [String]
130
- # Path to the PhantomJS executable.
131
- def self.executable
132
- Selenium::WebDriver::PhantomJS.path
133
- end
142
+ # @return [String]
143
+ # Path to the PhantomJS executable.
144
+ def executable
145
+ Selenium::WebDriver::PhantomJS.path
146
+ end
134
147
 
135
- def self.asset_domains
136
- @asset_domains ||= Set.new
137
- end
138
- asset_domains
148
+ def asset_domains
149
+ @asset_domains ||= Set.new
150
+ end
139
151
 
140
- def self.add_asset_domain( url )
141
- return if url.to_s.empty?
142
- return if !(curl = Arachni::URI( url ))
143
- return if !(domain = curl.domain)
152
+ def add_asset_domain( url )
153
+ return if url.to_s.empty?
154
+ return if !(curl = Arachni::URI( url ))
155
+ return if !(domain = curl.domain)
156
+
157
+ asset_domains << domain
158
+ domain
159
+ end
144
160
 
145
- asset_domains << domain
146
161
  end
162
+ asset_domains
147
163
 
148
164
  # @param [Hash] options
149
165
  # @option options [Integer] :concurrency
@@ -167,10 +183,10 @@ class Browser
167
183
  concurrency: @options[:concurrency],
168
184
  address: '127.0.0.1',
169
185
  request_handler: proc do |request, response|
170
- synchronize { exception_jail { request_handler( request, response ) } }
186
+ exception_jail { request_handler( request, response ) }
171
187
  end,
172
188
  response_handler: proc do |request, response|
173
- synchronize { exception_jail { response_handler( request, response ) } }
189
+ exception_jail { response_handler( request, response ) }
174
190
  end
175
191
  )
176
192
 
@@ -200,6 +216,8 @@ class Browser
200
216
  # Captures HTTP::Response objects per URL for open windows.
201
217
  @window_responses = {}
202
218
 
219
+ @elements_with_events = {}
220
+
203
221
  # Keeps track of resources which should be skipped -- like already fired
204
222
  # events and clicked links etc.
205
223
  @skip_states = Support::LookUp::HashSet.new( hasher: :persistent_hash )
@@ -212,12 +230,11 @@ class Browser
212
230
  @last_url = nil
213
231
 
214
232
  @javascript = Javascript.new( self )
215
-
216
- ensure_open_window
217
233
  end
218
234
 
219
235
  def clear_buffers
220
236
  synchronize do
237
+ @elements_with_events.clear
221
238
  @preloads.clear
222
239
  @cache.clear
223
240
  @captured_pages.clear
@@ -235,14 +252,13 @@ class Browser
235
252
  end.join
236
253
  end
237
254
 
238
- # @param [String, HTTP::Response, Page] resource
255
+ # @param [String, HTTP::Response, Page, Page:::DOM] resource
239
256
  # Loads the given resource in the browser. If it is a string it will be
240
257
  # treated like a URL.
241
258
  #
242
259
  # @return [Browser]
243
260
  # `self`
244
261
  def load( resource, options = {} )
245
- @last_dom_url = nil
246
262
 
247
263
  case resource
248
264
  when String
@@ -254,13 +270,14 @@ class Browser
254
270
  when Page
255
271
  HTTP::Client.update_cookies resource.cookie_jar
256
272
 
257
- @transitions = resource.dom.transitions.dup
258
- update_skip_states resource.dom.skip_states
273
+ load resource.dom
259
274
 
260
- @last_dom_url = resource.dom.url
275
+ when Page::DOM
276
+ @transitions = resource.transitions.dup
277
+ update_skip_states resource.skip_states
261
278
 
262
279
  @add_request_transitions = false if @transitions.any?
263
- resource.dom.restore self
280
+ resource.restore self
264
281
  @add_request_transitions = true
265
282
 
266
283
  else
@@ -350,22 +367,22 @@ class Browser
350
367
  url: url,
351
368
  cookies: extra_cookies
352
369
  ) do
353
- print_debug_level_2 "Loading: #{url}"
354
- watir.goto url
355
- print_debug_level_2 'Done'
370
+ print_debug_level_2 "Loading #{url} ..."
371
+ @selenium.navigate.to url
372
+ print_debug_level_2 '...done.'
356
373
 
357
374
  wait_till_ready
358
375
 
359
- url = watir.url
360
376
  Options.browser_cluster.css_to_wait_for( url ).each do |css|
361
377
  print_info "Waiting for #{css.inspect} to appear for: #{url}"
362
378
 
363
379
  begin
364
- watir.element( css: css ).
365
- wait_until_present( Options.browser_cluster.job_timeout )
380
+ Selenium::WebDriver::Wait.new(
381
+ timeout: Options.browser_cluster.job_timeout
382
+ ).until { @selenium.find_element( :css, css ) }
366
383
 
367
384
  print_info "#{css.inspect} appeared for: #{url}"
368
- rescue Watir::Wait::TimeoutError
385
+ rescue Selenium::WebDriver::Error::TimeOutError
369
386
  print_bad "#{css.inspect} did not appeared for: #{url}"
370
387
  end
371
388
 
@@ -380,7 +397,7 @@ class Browser
380
397
 
381
398
  @add_request_transitions = pre_add_request_transitions
382
399
 
383
- HTTP::Client.update_cookies cookies
400
+ update_cookies
384
401
 
385
402
  # Capture the page at its initial state.
386
403
  capture_snapshot if take_snapshot
@@ -391,32 +408,35 @@ class Browser
391
408
  def wait_till_ready
392
409
  print_debug_level_2 'Waiting for custom JS...'
393
410
  @javascript.wait_till_ready
394
- print_debug_level_2 'Done'
411
+ print_debug_level_2 '...done.'
395
412
 
396
- print_debug_level_2 "Waiting for #{@proxy.active_connections} connections to close.."
397
- wait_for_pending_requests
398
- print_debug_level_2 'Done'
399
-
400
- print_debug_level_2 'Waiting for timers...'
401
413
  wait_for_timers
402
- print_debug_level_2 'Done'
414
+
415
+ wait_for_pending_requests
403
416
  end
404
417
 
405
418
  def shutdown
406
419
  begin
407
- watir.close if browser_alive?
408
- rescue Selenium::WebDriver::Error::WebDriverError,
409
- Watir::Exception::Error
420
+ watir.close if alive?
421
+ # Bucnh of dirrent errors can be raised here, Selenium, HTTP client,
422
+ # don't try to catch them by type because we'll probably miss some.
423
+ rescue
410
424
  end
411
425
 
412
426
  kill_process
413
- @proxy.shutdown
427
+ @proxy.shutdown rescue Reactor::Error::NotRunning
414
428
  end
415
429
 
416
430
  # @return [String]
417
- # Current URL.
431
+ # Current URL, noralized via #{Arachni::URI.}
418
432
  def url
419
- normalize_url watir.url
433
+ normalize_url dom_url
434
+ end
435
+
436
+ # @return [String]
437
+ # Current URL, as provided by the browser.
438
+ def dom_url
439
+ javascript.run( 'return document.URL;' )
420
440
  end
421
441
 
422
442
  # Explores the browser's DOM tree and captures page snapshots for each
@@ -448,15 +468,11 @@ class Browser
448
468
  # Iterates over all elements which have events and passes their info to the
449
469
  # given block.
450
470
  #
451
- # @param [Bool] mark_state
452
- # Mark each element/events as visited and skip it if it has already been
453
- # seen.
454
- #
455
471
  # @yield [ElementLocator,Array<Symbol>]
456
- # Hash with information about the element, its tag name, applicable events
457
- # along with their handlers and attributes.
458
- def each_element_with_events( mark_state = true )
459
- current_url = url
472
+ # Element locator along with the element's applicable events along with
473
+ # their handlers and attributes.
474
+ def each_element_with_events
475
+ current_url = self.url
460
476
 
461
477
  javascript.dom_elements_with_events.each do |element|
462
478
  tag_name = element['tag_name']
@@ -469,7 +485,7 @@ class Browser
469
485
 
470
486
  if !href.empty?
471
487
  if href.downcase.start_with?( 'javascript:' )
472
- events << [ :click, href ]
488
+ (events[:click] ||= []) << href
473
489
  else
474
490
  next if skip_path?( to_absolute( href, current_url ) )
475
491
  end
@@ -477,7 +493,7 @@ class Browser
477
493
 
478
494
  when 'input'
479
495
  if attributes['type'].to_s.downcase == 'image'
480
- events << [ :click, 'image' ]
496
+ (events[:click] ||= []) << 'image'
481
497
  end
482
498
 
483
499
  when 'form'
@@ -485,7 +501,7 @@ class Browser
485
501
 
486
502
  if !action.empty?
487
503
  if action.downcase.start_with?( 'javascript:' )
488
- events << [ :submit, action ]
504
+ (events[:submit] ||= []) << action
489
505
  else
490
506
  next if skip_path?( to_absolute( action, current_url ) )
491
507
  end
@@ -494,12 +510,6 @@ class Browser
494
510
 
495
511
  next if events.empty?
496
512
 
497
- if mark_state
498
- state = "#{tag_name}#{attributes}#{events}"
499
- next if skip_state?( state )
500
- skip_state state
501
- end
502
-
503
513
  yield ElementLocator.new( tag_name: tag_name, attributes: attributes ),
504
514
  events
505
515
  end
@@ -507,21 +517,47 @@ class Browser
507
517
  self
508
518
  end
509
519
 
520
+ # @note The results will be cached, if direct access in necessary
521
+ # use {#each_element_with_events}.
522
+ #
523
+ # @return [Hash<ElementLocator,Array<Symbol>>]
524
+ # Element locator along with the element's applicable events along with
525
+ # their handlers and attributes.
526
+ def elements_with_events( clear_cache = false )
527
+ current_url = self.url
528
+
529
+ @elements_with_events.clear if clear_cache
530
+
531
+ if @elements_with_events.include?( current_url )
532
+ return @elements_with_events[current_url]
533
+ end
534
+
535
+ @elements_with_events.clear
536
+ @elements_with_events[current_url] ||= {}
537
+
538
+ each_element_with_events do |locator, events|
539
+ @elements_with_events[current_url][locator] = events
540
+ end
541
+
542
+ @elements_with_events[current_url]
543
+ end
544
+
510
545
  # @return [String]
511
546
  # Snapshot ID used to determine whether or not a page snapshot has already
512
- # been seen. Uses both elements and their DOM events and possible audit
513
- # workload to determine the ID, as page snapshots should be retained both
514
- # when further browser analysis can be performed and when new element
515
- # audit workload (but possibly without any DOM relevance) is available.
547
+ # been seen.
548
+ #
549
+ # Uses both elements and their DOM events and possible audit workload to
550
+ # determine the ID, as page snapshots should be retained both when further
551
+ # browser analysis can be performed and when new element audit workload
552
+ # (but possibly without any DOM relevance) is available.
516
553
  def snapshot_id
517
- current_url = url
554
+ current_url = self.url
518
555
 
519
- id = []
556
+ id = Set.new
520
557
  javascript.dom_elements_with_events.each do |element|
521
558
  tag_name = element['tag_name']
522
559
  attributes = element['attributes']
523
- events = element['events'] +
524
- Javascript.select_event_attributes( attributes ).to_a
560
+ events = element['events']
525
561
  element_id = attributes['id'].to_s
526
562
 
527
563
  case tag_name
@@ -531,19 +567,19 @@ class Browser
531
567
 
532
568
  if !href.empty?
533
569
  if href.downcase.start_with?( 'javascript:' )
534
- events << [ :click, href ]
570
+ (events[:click] ||= []) << href
535
571
  else
536
572
  absolute = to_absolute( href, current_url )
537
573
  next if skip_path?( absolute )
538
574
 
539
- events << [ :click, href ]
575
+ (events[:click] ||= []) << href
540
576
  end
541
577
  else
542
- events << [ :click, current_url ]
578
+ (events[:click] ||= []) << current_url
543
579
  end
544
580
 
545
581
  when 'input', 'textarea', 'select'
546
- events << [ tag_name.to_sym ]
582
+ (events[:input] ||= []) << tag_name.to_sym
547
583
  element_id << attributes['name'].to_s
548
584
 
549
585
  when 'form'
@@ -552,24 +588,26 @@ class Browser
552
588
 
553
589
  if !action.empty?
554
590
  if action.downcase.start_with?( 'javascript:' )
555
- events << [ :submit, action ]
591
+ (events[:submit] ||= []) << action
556
592
  else
557
593
  absolute = to_absolute( action, current_url )
558
594
  if !skip_path?( absolute )
559
- events << [ :submit, absolute ]
595
+ (events[:submit] ||= []) << absolute
560
596
  end
561
597
  end
562
598
  else
563
- events << [ :submit, current_url ]
599
+ (events[:submit] ||= []) << current_url
564
600
  end
565
601
  end
566
602
 
567
603
  next if events.empty?
568
604
 
569
- id << "#{tag_name}:#{element_id}:#{events}"
605
+ id << "#{tag_name}:#{element_id}:#{events.keys.sort}".persistent_hash
570
606
  end
571
607
 
572
- id.uniq.sort.to_s
608
+ id << [:cookies, cookies.map(&:name).sort].to_s.persistent_hash
609
+
610
+ id.to_a.sort.map(&:to_s).join(':')
573
611
  end
574
612
 
575
613
  # Triggers all events on all elements (**once**) and captures
@@ -578,11 +616,15 @@ class Browser
578
616
  # @return [Browser]
579
617
  # `self`
580
618
  def trigger_events
581
- root_page = to_page
619
+ dom = self.state
620
+
621
+ elements_with_events( true ).each do |locator, events|
622
+ state = "#{locator.tag_name}:#{locator.attributes}:#{events.keys.sort}"
623
+ next if skip_state?( state )
624
+ skip_state state
582
625
 
583
- each_element_with_events do |locator, events|
584
626
  events.each do |name, _|
585
- distribute_event( root_page, locator, name.to_sym )
627
+ distribute_event( dom, locator, name.to_sym )
586
628
  end
587
629
  end
588
630
 
@@ -595,23 +637,23 @@ class Browser
595
637
  # Distributes the triggering of `event` on the element at `element_index`
596
638
  # on `page`.
597
639
  #
598
- # @param [Page] page
640
+ # @param [String, Page, Page::DOM, HTTP::Response] resource
599
641
  # @param [ElementLocator] locator
600
642
  # @param [Symbol] event
601
- def distribute_event( page, locator, event )
602
- trigger_event( page, locator, event )
643
+ def distribute_event( resource, locator, event )
644
+ trigger_event( resource, locator, event )
603
645
  end
604
646
 
605
647
  # @note Captures page {#page_snapshots}.
606
648
  #
607
649
  # Triggers `event` on the element described by `tag` on `page`.
608
650
  #
609
- # @param [Page] page
651
+ # @param [String, Page, Page::DOM, HTTP::Response] resource
610
652
  # Page containing the element's `tag`.
611
653
  # @param [ElementLocator] element
612
654
  # @param [Symbol] event
613
655
  # Event to trigger.
614
- def trigger_event( page, element, event )
656
+ def trigger_event( resource, element, event, restore = true )
615
657
  event = event.to_sym
616
658
  transition = fire_event( element, event )
617
659
 
@@ -620,18 +662,21 @@ class Browser
620
662
  ' the page has changed, capturing a new snapshot.'
621
663
  capture_snapshot
622
664
 
623
- print_info 'Restoring page.'
624
- restore page
665
+ if restore
666
+ print_info 'Restoring page.'
667
+ restore( resource )
668
+ end
669
+
625
670
  return
626
671
  end
627
672
 
628
673
  capture_snapshot( transition )
629
- restore page
674
+ restore( resource ) if restore
630
675
  end
631
676
 
632
677
  # Triggers `event` on `element`.
633
678
  #
634
- # @param [Watir::Element, ElementLocator] element
679
+ # @param [Selenium::WebDriver::Element, ElementLocator] element
635
680
  # @param [Symbol] event
636
681
  # @param [Hash] options
637
682
  # @option options [Hash<Symbol,String=>String>] :inputs
@@ -651,10 +696,10 @@ class Browser
651
696
  locator = element
652
697
 
653
698
  begin
654
- element = element.locate( self )
655
- rescue Selenium::WebDriver::Error::WebDriverError,
656
- Watir::Exception::Error => e
699
+ Selenium::WebDriver::Wait.new( timeout: ELEMENT_APPEARANCE_TIMEOUT ).
700
+ until { element = element.locate( self ) }
657
701
 
702
+ rescue Selenium::WebDriver::Error::WebDriverError => e
658
703
  print_debug "Element '#{element.inspect}' could not be " <<
659
704
  "located for triggering '#{event}'."
660
705
  print_debug
@@ -663,23 +708,6 @@ class Browser
663
708
  end
664
709
  end
665
710
 
666
- # The page may need a bit to settle and the element is lazily located
667
- # by Watir so give it a few tries.
668
- begin
669
- with_timeout ELEMENT_APPEARANCE_TIMEOUT do
670
- sleep 0.1 while !element.exists?
671
- end
672
- rescue Timeout::Error
673
- print_debug_level_2 "#{element.inspect} did not appear in " <<
674
- "#{ELEMENT_APPEARANCE_TIMEOUT}."
675
- return
676
- end
677
-
678
- if !element.visible?
679
- print_debug_level_2 "#{element.inspect} is not visible, skipping..."
680
- return
681
- end
682
-
683
711
  if locator
684
712
  opening_tag = locator.to_s
685
713
  tag_name = locator.tag_name
@@ -689,71 +717,115 @@ class Browser
689
717
  locator = ElementLocator.from_html( opening_tag )
690
718
  end
691
719
 
692
- print_debug_level_2 "#{__method__} [start]: #{event} (#{options}) #{locator}"
720
+ print_debug_level_2 "[start]: #{event} (#{options}) #{locator}"
693
721
 
694
722
  tag_name = tag_name.to_sym
695
723
 
696
724
  notify_on_fire_event( element, event )
697
725
 
698
- tries = 0
726
+ pre_timeouts = javascript.timeouts
727
+
699
728
  begin
700
- Page::DOM::Transition.new( locator, event, options ) do
701
- had_special_trigger = false
729
+ transition = Page::DOM::Transition.new( locator, event, options ) do
730
+ force = true
702
731
 
732
+ # It's better to use the Watir helpers whenever possible instead
733
+ # of firing events manually.
703
734
  if tag_name == :form
704
735
  fill_in_form_inputs( element, options[:inputs] )
705
736
 
706
737
  if event == :submit
707
- element.to_subtype.submit
708
- had_special_trigger = true
738
+ force = false
739
+
740
+ element.submit
709
741
  end
710
- elsif tag_name == :input && event == :click &&
711
- element.attribute_value(:type) == 'image'
712
742
 
713
- element.to_subtype.click
714
- had_special_trigger = true
743
+ elsif event == :click
744
+ force = false
715
745
 
716
- elsif [:keyup, :keypress, :keydown, :change, :input, :focus, :blur, :select].include? event
746
+ element.click
717
747
 
718
- # Some of these need an explicit event triggers.
719
- had_special_trigger = true if ![:change, :blur, :focus, :select].include? event
748
+ elsif INPUT_EVENTS.include? event
749
+ force = INPUT_EVENTS_TO_FORCE.include?( event )
720
750
 
721
751
  # Send keys will append to the existing value, so we need to
722
- # clear it first. The receiving input may support values
752
+ # clear it first. The receiving input may not support values
723
753
  # though, so watch out.
724
- if (subtype = element.to_subtype).respond_to?( :value= )
725
- subtype.value = ''
726
- end
754
+ element.clear if [:input, :textarea].include?( tag_name )
727
755
 
756
+ # Simulates real text input and will trigger associated events.
757
+ # Except for INPUT_EVENTS_TO_FORCE of course.
728
758
  element.send_keys( (options[:value] || value_for( element )).to_s )
729
759
  end
730
760
 
731
- element.fire_event( event ) if !had_special_trigger
761
+ if force
762
+ print_debug_level_2 "[forcing event]: #{event} (#{options}) #{locator}"
763
+ fire_event_js locator, event
764
+ end
732
765
 
733
- print_debug_level_2 "#{__method__} [waiting for requests]: #{event} (#{options}) #{locator}"
766
+ print_debug_level_2 "[waiting for requests]: #{event} (#{options}) #{locator}"
734
767
  wait_for_pending_requests
735
- print_debug_level_2 "#{__method__} [done waiting for requests]: #{event} (#{options}) #{locator}"
768
+ print_debug_level_2 "[done waiting for requests]: #{event} (#{options}) #{locator}"
736
769
 
737
- # puts source_with_line_numbers
738
- print_debug_level_2 "#{__method__} [done]: #{event} (#{options}) #{locator}"
770
+ update_cookies
739
771
  end
740
- rescue Selenium::WebDriver::Error::WebDriverError,
741
- Watir::Exception::Error => e
742
772
 
743
- sleep 0.1
773
+ print_debug_level_2 "[done in #{transition.time}s]: #{event} (#{options}) #{locator}"
774
+
775
+ delay = (javascript.timeouts - pre_timeouts).compact.map { |t| t[1].to_i }.max
776
+ if delay
777
+ print_debug_level_2 "Found new timers with max #{delay}ms."
778
+ delay = [Options.http.request_timeout, delay].min / 1000.0
779
+
780
+ print_debug_level_2 "Will wait for #{delay}s."
781
+ sleep delay
782
+ end
744
783
 
745
- tries += 1
746
- retry if tries < 5
784
+ transition
785
+ rescue Selenium::WebDriver::Error::WebDriverError => e
747
786
 
748
787
  print_debug "Error when triggering event for: #{url}"
749
- print_debug "-- '#{event}' on: #{opening_tag}"
788
+ print_debug "-- '#{event}' on: #{opening_tag} -- #{locator.css}"
750
789
  print_debug
751
790
  print_debug_exception e
752
-
753
791
  nil
754
792
  end
755
793
  end
756
794
 
795
+ # This is essentially the same thing as Watir::Element#fire_event
796
+ # but 10 times faster.
797
+ #
798
+ # Does not perform any sort of sanitization nor sanity checking, it will
799
+ # just try to trigger the event.
800
+ #
801
+ # @param [Browser::ElementLocator] locator
802
+ # @param [Symbol,String] event
803
+ # @param [Bool] ret
804
+ # Return JS result?
805
+ # @param [Numeric] wait
806
+ # Amount of time to wait (in seconds) after triggering the event.
807
+ def fire_event_js( locator, event, ret: false, wait: 0.1 )
808
+ r = javascript.run <<-EOJS
809
+ var element = document.querySelector( #{locator.css.inspect} );
810
+ var event = document.createEvent( "Events" );
811
+
812
+ event.initEvent( "#{event}", true, true );
813
+
814
+ event.view = window;
815
+ event.altKey = false;
816
+ event.ctrlKey = false;
817
+ event.shiftKey = false;
818
+ event.metaKey = false;
819
+ event.keyCode = 0;
820
+ event.charCode = 'a';
821
+
822
+ #{'return' if ret} element.dispatchEvent( event );
823
+ EOJS
824
+
825
+ sleep wait
826
+ r
827
+ end
828
+
757
829
  # Starts capturing requests and parses them into elements of pages,
758
830
  # accessible via {#captured_pages}.
759
831
  #
@@ -805,13 +877,29 @@ class Browser
805
877
  @captured_pages
806
878
  end
807
879
 
880
+ # @return [Page::DOM]
881
+ def state
882
+ d_url = dom_url
883
+
884
+ return if !response
885
+
886
+ Page::DOM.new(
887
+ url: d_url,
888
+ transitions: @transitions.dup,
889
+ digest: @javascript.dom_digest,
890
+ skip_states: skip_states.dup
891
+ )
892
+ end
893
+
808
894
  # @return [Page]
809
895
  # Converts the current browser window to a {Page page}.
810
896
  def to_page
897
+ d_url = dom_url
898
+
811
899
  if !(r = response)
812
900
  return Page.from_data(
813
901
  dom: {
814
- url: watir.url
902
+ url: d_url
815
903
  },
816
904
  response: {
817
905
  code: 0,
@@ -820,12 +908,19 @@ class Browser
820
908
  )
821
909
  end
822
910
 
911
+ # We need sink data for both the current taint and to determine cookie
912
+ # usage, so grab all of the data-flow sinks once.
913
+ data_flow_sinks = {}
914
+ if @javascript.supported?
915
+ data_flow_sinks = @javascript.taint_tracer.data_flow_sinks
916
+ end
917
+
823
918
  page = r.to_page
824
919
  page.body = source
825
- page.dom.url = watir.url
920
+ page.dom.url = d_url
826
921
  page.dom.digest = @javascript.dom_digest
827
922
  page.dom.execution_flow_sinks = @javascript.execution_flow_sinks
828
- page.dom.data_flow_sinks = @javascript.data_flow_sinks
923
+ page.dom.data_flow_sinks = data_flow_sinks[@javascript.taint] || []
829
924
  page.dom.transitions = @transitions.dup
830
925
  page.dom.skip_states = skip_states.dup
831
926
 
@@ -860,10 +955,9 @@ class Browser
860
955
  end
861
956
 
862
957
  if Options.audit.cookie_doms?
863
- sinks = @javascript.taint_tracer.data_flow_sinks
864
958
  page.cookies.each do |cookie|
865
- next if sinks.include?( cookie.name ) ||
866
- sinks.include?( cookie.value )
959
+ next if data_flow_sinks.include?( cookie.name ) ||
960
+ data_flow_sinks.include?( cookie.value )
867
961
 
868
962
  cookie.skip_dom = true
869
963
  end
@@ -881,32 +975,46 @@ class Browser
881
975
  request_transitions = flush_request_transitions
882
976
  transitions = ([transition] + request_transitions).flatten.compact
883
977
 
978
+ window_handles = @selenium.window_handles
979
+
884
980
  begin
885
- # Skip about:blank windows.
886
- watir.windows( url: /^http/ ).each do |window|
887
- window.use do
888
- next if !(page = to_page)
889
-
890
- if pages.empty?
891
- transitions.each do |t|
892
- @transitions << t
893
- page.dom.push_transition t
894
- end
895
- end
981
+ window_handles.each do |handle|
982
+ if window_handles.size > 1
983
+ @selenium.switch_to.window( handle )
984
+ end
896
985
 
897
- capture_snapshot_with_sink( page )
986
+ # We don't even have an HTTP response for the page, don't
987
+ # bother trying anything else.
988
+ next if !response
898
989
 
899
- unique_id = self.snapshot_id
900
- next if skip_state? unique_id
901
- skip_state unique_id
990
+ unique_id = self.snapshot_id
991
+ already_seen = skip_state?( unique_id )
992
+ skip_state unique_id
902
993
 
903
- notify_on_new_page( page )
994
+ with_sinks = javascript.has_sinks?
904
995
 
905
- if store_pages?
906
- @page_snapshots[unique_id.hash] = page
907
- pages << page
996
+ # Avoid a #to_page call if at all possible because it'll generate
997
+ # loads of data.
998
+ next if (already_seen && !with_sinks) ||
999
+ (page = to_page).code == 0
1000
+
1001
+ if pages.empty?
1002
+ transitions.each do |t|
1003
+ @transitions << t
1004
+ page.dom.push_transition t
908
1005
  end
909
1006
  end
1007
+
1008
+ capture_snapshot_with_sink( page )
1009
+
1010
+ next if already_seen
1011
+
1012
+ notify_on_new_page( page )
1013
+
1014
+ if store_pages?
1015
+ @page_snapshots[unique_id] = page
1016
+ pages << page
1017
+ end
910
1018
  end
911
1019
  rescue => e
912
1020
  print_debug "Could not capture snapshot for: #{@last_url}"
@@ -917,6 +1025,8 @@ class Browser
917
1025
 
918
1026
  print_debug
919
1027
  print_debug_exception e
1028
+ ensure
1029
+ @selenium.switch_to.default_content
920
1030
  end
921
1031
 
922
1032
  pages
@@ -947,33 +1057,49 @@ class Browser
947
1057
  end
948
1058
 
949
1059
  # @return [Array<Cookie>]
950
- # Browser cookies.
1060
+ # Cookies visible to JS.
951
1061
  def cookies
952
1062
  js_cookies = begin
953
- # Watir doesn't tell us if cookies are HttpOnly, so we need to figure
954
- # this out ourselves, by checking for JS visibility.
1063
+ # Watir doesn't tell us if cookies are HttpOnly, so we need to figure
1064
+ # this out ourselves, by checking for JS visibility.
955
1065
  javascript.run( 'return document.cookie' )
956
1066
  # We may not have a page.
957
1067
  rescue Selenium::WebDriver::Error::WebDriverError
958
1068
  ''
959
1069
  end
960
1070
 
961
- watir.cookies.to_a.map do |c|
962
- original_name = c[:name].to_s
1071
+ # The domain attribute cannot be trusted, PhantomJS thinks all cookies
1072
+ # are for subdomains too.
1073
+ # Do not try to hack around this because it'll be a waste of time,
1074
+ # leading to confusion and duplicate cookies.
1075
+ #
1076
+ # Still, we ask Selenium for cookies instead of parsing the JS ones
1077
+ # and merging with the HTTP cookiejar because this allows us to get
1078
+ # a path attribute for JS cookies.
1079
+ @selenium.manage.all_cookies.map do |c|
1080
+
1081
+ c[:httponly] = !js_cookies.include?( c[:name].to_s )
1082
+ c[:path] = c[:path].gsub( /\/+/, '/' )
1083
+ c[:expires] = Time.parse( c[:expires].to_s ) if c[:expires]
963
1084
 
964
- c[:path] = '/' if c[:path] == '//'
965
- c[:name] = Cookie.decode( c[:name].to_s )
966
- c[:value] = Cookie.value_to_v0( c[:value].to_s )
967
- c[:httponly] = !js_cookies.include?( original_name )
1085
+ c[:raw_name] = c[:name].to_s
1086
+ c[:raw_value] = c[:value].to_s
968
1087
 
969
- Cookie.new c.merge( url: @last_url || url )
1088
+ c[:name] = Cookie.decode( c[:name].to_s )
1089
+ c[:value] = Cookie.value_to_v0( c[:value].to_s )
1090
+
1091
+ Cookie.new c.merge( url: @last_url || self.url )
970
1092
  end
971
1093
  end
972
1094
 
1095
+ def update_cookies
1096
+ HTTP::Client.update_cookies self.cookies
1097
+ end
1098
+
973
1099
  # @return [String]
974
1100
  # HTML code of the evaluated (DOM/JS/AJAX) page.
975
1101
  def source
976
- watir.html
1102
+ @selenium.page_source
977
1103
  end
978
1104
 
979
1105
  def load_delay
@@ -985,7 +1111,11 @@ class Browser
985
1111
  delay = load_delay
986
1112
  return if !delay
987
1113
 
1114
+ print_debug_level_2 'Waiting for timers...'
1115
+
988
1116
  sleep [Options.http.request_timeout, delay].min / 1000.0
1117
+
1118
+ print_debug_level_2 '...done.'
989
1119
  end
990
1120
 
991
1121
  def skip_path?( path )
@@ -993,22 +1123,26 @@ class Browser
993
1123
  end
994
1124
 
995
1125
  def response
996
- u = watir.url
1126
+ u = dom_url
997
1127
 
998
- return if skip_path?( u )
1128
+ if dom_url == 'about:blank'
1129
+ print_debug 'Blank page.'
1130
+ return
1131
+ end
999
1132
 
1000
- begin
1001
- with_timeout Options.http.request_timeout / 1_000 do
1002
- while !(r = get_response(u))
1003
- sleep 0.1
1004
- end
1133
+ if skip_path?( u )
1134
+ print_debug "Response is out of scope: #{u}"
1135
+ return
1136
+ end
1005
1137
 
1006
- fail Timeout::Error if r.timed_out?
1138
+ r = get_response( u )
1007
1139
 
1008
- return r
1009
- end
1010
- rescue Timeout::Error
1011
- print_debug "Response for '#{u}' never arrived."
1140
+ return r if r && r.code != 504
1141
+
1142
+ if r
1143
+ print_debug "Origin server timed-out when requesting: #{u}"
1144
+ else
1145
+ print_debug "Response never arrived: #{u}"
1012
1146
  end
1013
1147
 
1014
1148
  nil
@@ -1019,7 +1153,7 @@ class Browser
1019
1153
  def selenium
1020
1154
  return @selenium if @selenium
1021
1155
 
1022
- client = Selenium::WebDriver::Remote::Http::Typhoeus.new
1156
+ client = Selenium::WebDriver::Remote::Http::Default.new
1023
1157
  client.timeout = WATIR_COM_TIMEOUT
1024
1158
 
1025
1159
  @selenium = Selenium::WebDriver.for(
@@ -1033,47 +1167,53 @@ class Browser
1033
1167
  )
1034
1168
  end
1035
1169
 
1170
+ def alive?
1171
+ @lifeline_pid && Processes::Manager.alive?( @lifeline_pid )
1172
+ end
1173
+
1036
1174
  def inspect
1037
1175
  s = "#<#{self.class} "
1038
- s << "pid=#{@pid} "
1176
+ s << "pid=#{@lifeline_pid} "
1177
+ s << "browser_pid=#{@browser_pid} "
1039
1178
  s << "last-url=#{@last_url.inspect} "
1040
1179
  s << "transitions=#{@transitions.size}"
1041
1180
  s << '>'
1042
1181
  end
1043
1182
 
1044
- def filter_events( element, events )
1045
- supported = Set.new( Arachni::Browser::Javascript.events_for( element ) )
1046
- events.reject { |name, _| !supported.include? ('on' + name.to_s.gsub( /^on/, '' )).to_sym }
1047
- end
1048
-
1049
1183
  private
1050
1184
 
1051
1185
  def fill_in_form_inputs( form, inputs = nil )
1052
- form.text_fields.each do |input|
1186
+ form.find_elements( :css, 'input, textarea' ).each do |input|
1053
1187
  name_or_id = name_or_id_for( input )
1054
- value = inputs ? inputs[name_or_id] : value_for( input )
1188
+ value = inputs ? inputs[name_or_id] : value_for_name( name_or_id )
1055
1189
 
1056
1190
  begin
1057
- input.set( value.to_s.recode )
1191
+ input.clear
1192
+ input.send_keys( value.to_s.recode )
1058
1193
  # Disabled inputs and such...
1059
- rescue Selenium::WebDriver::Error::WebDriverError,
1060
- Watir::Exception::Error => e
1194
+ rescue Selenium::WebDriver::Error::WebDriverError => e
1061
1195
  print_debug_level_2 "Could not fill in form input '#{name_or_id}'" <<
1062
1196
  " because: #{e} [#{e.class}"
1063
1197
  end
1064
1198
  end
1065
1199
 
1066
- form.selects.each do |input|
1067
- name_or_id = name_or_id_for( input )
1068
- value = inputs ? inputs[name_or_id] : value_for( input )
1200
+ form.find_elements( :tag_name, 'select' ).each do |select|
1201
+ name_or_id = name_or_id_for( select )
1202
+ value = inputs ? inputs[name_or_id] : value_for_name( name_or_id )
1069
1203
 
1070
- begin
1071
- input.select_value( value.to_s.recode )
1072
- # Disabled inputs and such...
1073
- rescue Selenium::WebDriver::Error::WebDriverError,
1074
- Watir::Exception::Error => e
1075
- print_debug_level_2 "Could not fill in form select '#{name_or_id}'" <<
1076
- " because: #{e} [#{e.class}"
1204
+ options = select.find_elements( tag_name: 'option' )
1205
+ options.each do |option|
1206
+
1207
+ begin
1208
+ if option[:value] == value || option.text == value
1209
+ option.click
1210
+ return
1211
+ end
1212
+ # Disabled inputs and such...
1213
+ rescue Selenium::WebDriver::Error::WebDriverError => e
1214
+ print_debug_level_2 "Could not fill in form select '#{name_or_id}'" <<
1215
+ " because: #{e} [#{e.class}"
1216
+ end
1077
1217
  end
1078
1218
  end
1079
1219
  end
@@ -1091,10 +1231,10 @@ class Browser
1091
1231
  end
1092
1232
 
1093
1233
  def name_or_id_for( element )
1094
- name = element.attribute_value(:name).to_s
1234
+ name = element[:name].to_s
1095
1235
  return name if !name.empty?
1096
1236
 
1097
- id = element.attribute_value(:id).to_s
1237
+ id = element[:id].to_s
1098
1238
  return id if !id.empty?
1099
1239
 
1100
1240
  nil
@@ -1119,6 +1259,10 @@ class Browser
1119
1259
  Options.input.value_for_name( name_or_id_for( element ) )
1120
1260
  end
1121
1261
 
1262
+ def value_for_name( name )
1263
+ Options.input.value_for_name( name )
1264
+ end
1265
+
1122
1266
  def spawn_browser
1123
1267
  if !spawn_phantomjs
1124
1268
  fail Error::Spawn, 'Could not start the browser process.'
@@ -1134,11 +1278,14 @@ class Browser
1134
1278
 
1135
1279
  ChildProcess.posix_spawn = true
1136
1280
 
1137
- port = nil
1138
- last_attempt_output = nil
1281
+ port = nil
1282
+ output = ''
1283
+
1139
1284
  10.times do |i|
1140
- done = false
1141
- port = available_port
1285
+ # Clear output of previous attempt.
1286
+ output = ''
1287
+ done = false
1288
+ port = Utilities.available_port
1142
1289
 
1143
1290
  print_debug "Attempt ##{i}, chose port number #{port}"
1144
1291
 
@@ -1146,52 +1293,48 @@ class Browser
1146
1293
  with_timeout 10 do
1147
1294
  print_debug "Spawning process: #{self.class.executable}"
1148
1295
 
1149
- @process = ChildProcess.build(
1150
- self.class.executable,
1151
- "--webdriver=#{port}",
1152
- "--proxy=http://#{@proxy.address}/",
1153
-
1154
- # As lax as possible to allow for easy SSL interception.
1155
- # The actual request to the origin server will obey
1156
- # the system-side SSL options.
1157
- '--ignore-ssl-errors=true',
1158
- '--ssl-protocol=any',
1159
-
1160
- '--disk-cache=true',
1161
- "--debug=#{!!debug?}"
1296
+ r, w = IO.pipe
1297
+ ri, @kill_process = IO.pipe
1298
+
1299
+ @lifeline_pid = Processes::Manager.spawn(
1300
+ :browser,
1301
+ executable: self.class.executable,
1302
+ without_arachni: true,
1303
+ fork: false,
1304
+ new_pgroup: true,
1305
+ stdin: ri,
1306
+ stdout: w,
1307
+ stderr: w,
1308
+ port: port,
1309
+ proxy_url: @proxy.url
1162
1310
  )
1163
- # @process.leader = true
1164
- @process.detach = true
1165
1311
 
1166
- @process.io.stdout = Tempfile.new( 'phantomjs-out' )
1167
- @process.io.stderr = @process.io.stdout
1168
- @process.io.stderr.sync = @process.io.stdout.sync = true
1312
+ w.close
1313
+ ri.close
1169
1314
 
1170
- @process.start
1171
1315
  print_debug 'Process spawned, waiting for it to boot-up...'
1172
1316
 
1173
- File.open( @process.io.stdout.path, 'r' ) do |out|
1174
- buff = ''
1175
- # Wait for PhantomJS to initialize.
1176
- while !buff.include?( 'running on port' )
1177
- # This can be problematic on something other than
1178
- # MRI.
1179
- buff << (out.readline rescue '').to_s
1317
+ # Wait for PhantomJS to initialize.
1318
+ while !output.include?( 'running on port' )
1319
+ begin
1320
+ output << r.readpartial( 8192 )
1321
+ # EOF or something, take a breather before retrying.
1322
+ rescue
1323
+ sleep 0.05
1180
1324
  end
1325
+ end
1181
1326
 
1182
- buff = nil
1183
- print_debug 'Boot-up complete.'
1184
- done = true
1185
- end
1327
+ @browser_pid = output.scan( /^PID: (\d+)/ ).flatten.first.to_i
1328
+
1329
+ print_debug 'Boot-up complete.'
1330
+ done = true
1186
1331
  end
1187
1332
  rescue Timeout::Error
1188
1333
  print_debug 'Spawn timed-out.'
1189
1334
  end
1190
1335
 
1191
- if @process.io.stdout
1192
- last_attempt_output = IO.read( @process.io.stdout )
1193
- print_debug last_attempt_output
1194
- @process.io.stdout.close!
1336
+ if !output.empty?
1337
+ print_debug output
1195
1338
  end
1196
1339
 
1197
1340
  if done
@@ -1208,42 +1351,29 @@ class Browser
1208
1351
  #
1209
1352
  # Bail out for now and count on the BrowserCluster to retry to boot
1210
1353
  # another process ass needed.
1211
- if !@process
1354
+ if !@lifeline_pid
1212
1355
  log_error 'Could not spawn browser process.'
1213
- log_error last_attempt_output
1356
+ log_error output
1214
1357
  return
1215
1358
  end
1216
1359
 
1217
- begin
1218
- @pid = @process.pid
1219
- # Not supported on JRuby on MS Windows.
1220
- rescue NotImplementedError
1221
- end
1222
-
1223
1360
  @browser_url = "http://127.0.0.1:#{port}"
1224
1361
  end
1225
1362
 
1226
1363
  def kill_process
1227
- begin
1228
- if @process && @process.alive?
1229
- @process.stop
1230
- @process.io.close rescue nil
1364
+ if @kill_process
1365
+ begin
1366
+ @kill_process.close
1367
+ rescue
1231
1368
  end
1232
- rescue Errno::ECHILD
1233
- false
1234
1369
  end
1235
1370
 
1236
- @process = nil
1237
- @watir = nil
1238
- @selenium = nil
1239
- @pid = nil
1240
- @browser_url = nil
1241
- end
1242
-
1243
- def browser_alive?
1244
- @watir && @process && @process.alive?
1245
- rescue Errno::ECHILD
1246
- false
1371
+ @kill_process = nil
1372
+ @watir = nil
1373
+ @selenium = nil
1374
+ @lifeline_pid = nil
1375
+ @browser_pid = nil
1376
+ @browser_url = nil
1247
1377
  end
1248
1378
 
1249
1379
  def store_pages?
@@ -1267,52 +1397,50 @@ class Browser
1267
1397
  end
1268
1398
 
1269
1399
  def wait_for_pending_requests
1270
- # With AJAX requests being asynchronous and everything we need
1271
- # to wait a split second to give the browser time to initialize
1272
- # a connection.
1273
- #
1274
- # TODO: Add XMLHttpRequest.send() overrides to the DOMMonitor so
1275
- # that we'll know for sure when to wait.
1276
- sleep 0.1
1400
+ print_debug_level_2 "Waiting for #{@proxy.pending_requests} requests to complete..."
1277
1401
 
1278
- # Wait for pending requests to complete.
1279
- #
1280
- # The HTTP timeout option already guards us against this but I don't
1281
- # fully trust the proxy so we're using #with_timeout as a fallback.
1282
- with_timeout Options.http.request_timeout / 1_000 do
1283
- sleep 0.1 while @proxy.has_connections?
1284
- end
1402
+ sleep 0.1
1403
+ sleep 0.01 while @proxy.has_pending_requests?
1285
1404
 
1286
- true
1287
- rescue Timeout::Error
1288
- #ap 'PENDING REQUESTS TIMEOUT'
1289
- #ap caller
1290
- false
1405
+ print_debug_level_2 '...done.'
1291
1406
  end
1292
1407
 
1293
1408
  def load_cookies( url, cookies = {} )
1294
1409
  # First clears the browser's cookies and then tricks it into accepting
1295
1410
  # the system cookies for its cookie-jar.
1411
+ #
1412
+ # Well, it doesn't really clear the browser's cookie-jar, but that's
1413
+ # not necessary because whatever cookies the browser has have already
1414
+ # gotten into the system-wide cookiejar, and since we're passing
1415
+ # all applicable cookies to the browser the end result will be that
1416
+ # it'll have the wanted values.
1296
1417
 
1297
1418
  url = normalize_url( url )
1298
- watir.cookies.clear
1299
1419
 
1300
1420
  set_cookies = {}
1301
1421
  HTTP::Client.cookie_jar.for_url( url ).each do |cookie|
1302
1422
  cookie = cookie.dup
1303
- cookie.data.delete :domain
1304
1423
  set_cookies[cookie.name] = cookie
1305
1424
  end
1425
+
1306
1426
  cookies.each do |name, value|
1307
1427
  if set_cookies[name]
1308
1428
  set_cookies[name] = set_cookies[name].dup
1429
+
1430
+ # Don't forget this, otherwise the #to_set_cookie call will
1431
+ # return the original raw data.
1432
+ set_cookies[name].affected_input_name = name
1309
1433
  set_cookies[name].update( name => value )
1310
1434
  else
1311
1435
  set_cookies[name] = Cookie.new( url: url, inputs: { name => value } )
1312
1436
  end
1313
1437
  end
1314
1438
 
1315
- url = "#{url}/set-cookies-#{request_token}"
1439
+ return if set_cookies.empty? &&
1440
+ Arachni::Options::browser_cluster.local_storage.empty?
1441
+
1442
+ set_cookie = set_cookies.values.map(&:to_set_cookie)
1443
+ print_debug_level_2 "Setting cookies: #{set_cookie}"
1316
1444
 
1317
1445
  body = ''
1318
1446
  if Arachni::Options::browser_cluster.local_storage.any?
@@ -1327,11 +1455,12 @@ class Browser
1327
1455
  EOJS
1328
1456
  end
1329
1457
 
1330
- watir.goto preload( HTTP::Response.new(
1331
- url: url,
1458
+ @selenium.navigate.to preload( HTTP::Response.new(
1459
+ code: 200,
1460
+ url: "#{url}/set-cookies-#{request_token}",
1332
1461
  body: body,
1333
1462
  headers: {
1334
- 'Set-Cookie' => set_cookies.values.map(&:to_set_cookie)
1463
+ 'Set-Cookie' => set_cookie
1335
1464
  }
1336
1465
  ))
1337
1466
  end
@@ -1339,12 +1468,26 @@ EOJS
1339
1468
  # Makes sure we have at least 2 windows open so that we can switch to the
1340
1469
  # last available one in case there's some JS in the page that closes one.
1341
1470
  def ensure_open_window
1342
- return if watir.windows.size > 1
1471
+ window_handles = @selenium.window_handles
1472
+
1473
+ if window_handles.size == 0
1474
+ @javascript.run( 'window.open()' )
1475
+ @selenium.switch_to.window( @selenium.window_handles.last )
1476
+ else
1477
+ if window_handles.size > 1
1478
+ # Keep the first
1479
+ window_handles[1..-1].each do |handle|
1480
+ @selenium.switch_to.window( handle )
1481
+ @selenium.close
1482
+ end
1343
1483
 
1344
- watir.windows.last.use
1345
- watir.window.resize_to( @width, @height )
1484
+ @selenium.switch_to.window( @selenium.window_handles.first )
1485
+ end
1346
1486
 
1347
- @javascript.run( 'window.open()' )
1487
+ @selenium.navigate.to 'about:blank'
1488
+ end
1489
+
1490
+ @selenium.manage.window.resize_to( @width, @height )
1348
1491
  end
1349
1492
 
1350
1493
  # # Firefox driver, only used for debugging.
@@ -1362,6 +1505,11 @@ EOJS
1362
1505
 
1363
1506
  def capabilities
1364
1507
  Selenium::WebDriver::Remote::Capabilities.phantomjs(
1508
+ # Selenium tries to be helpful by including screenshots for errors
1509
+ # in the JSON response. That's not gonna fly in this use case as
1510
+ # parsing lots of massive JSON responses at the same time will
1511
+ # have a significant impact on performance.
1512
+ takes_screenshot: false,
1365
1513
  'phantomjs.page.settings.userAgent' => Options.http.user_agent,
1366
1514
  'phantomjs.page.customHeaders.X-Arachni-Browser-Auth' => auth_token,
1367
1515
  'phantomjs.page.settings.resourceTimeout' => Options.http.request_timeout,
@@ -1389,6 +1537,8 @@ EOJS
1389
1537
  return if request.headers['X-Arachni-Browser-Auth'] != auth_token
1390
1538
  request.headers.delete 'X-Arachni-Browser-Auth'
1391
1539
 
1540
+ print_debug_level_2 "Request: #{request.url}"
1541
+
1392
1542
  # We can't have 304 page responses in the framework, we need full request
1393
1543
  # and response data, the browser cache doesn't help us here.
1394
1544
  #
@@ -1399,41 +1549,47 @@ EOJS
1399
1549
  request.headers.delete 'If-Modified-Since'
1400
1550
  end
1401
1551
 
1402
- return if @javascript.serve( request, response )
1552
+ if @javascript.serve( request, response )
1553
+ print_debug_level_2 "Serving local JS."
1554
+ return
1555
+ end
1403
1556
 
1404
1557
  if !request.url.include?( request_token )
1405
- return if ignore_request?( request )
1558
+ if ignore_request?( request )
1559
+ print_debug_level_2 "Out of scope, ignoring."
1560
+ return
1561
+ end
1406
1562
 
1407
1563
  if @add_request_transitions
1408
- @request_transitions << Page::DOM::Transition.new( request.url, :request )
1564
+ synchronize do
1565
+ @request_transitions << Page::DOM::Transition.new(
1566
+ request.url, :request
1567
+ )
1568
+ end
1409
1569
  end
1410
1570
  end
1411
1571
 
1412
1572
  # Signal the proxy to not actually perform the request if we have a
1413
1573
  # preloaded or cached response for it.
1414
1574
  if from_preloads( request, response ) || from_cache( request, response )
1575
+ print_debug_level_2 'Resource has been preloaded.'
1576
+
1415
1577
  # There may be taints or custom JS code that need to be updated.
1416
1578
  javascript.inject response
1417
1579
  return
1418
1580
  end
1419
1581
 
1582
+ print_debug_level_2 'Request can proceed to origin.'
1583
+
1420
1584
  # Capture the request as elements of pages -- let's us grab AJAX and
1421
1585
  # other browser requests and convert them into elements we can analyze
1422
1586
  # and audit.
1423
- capture( request )
1587
+ if request.scope.in?
1588
+ capture( request )
1589
+ end
1424
1590
 
1425
1591
  request.headers['user-agent'] = Options.http.user_agent
1426
1592
 
1427
- # The proxy has an unlimited response_max_size so if we're not requesting
1428
- # an asset remove the response_max_size option so that it'll end up using
1429
- # the system settings.
1430
- #
1431
- # However, this is not foolproof, a lot of assets don't have the expected
1432
- # extension.
1433
- if !request_for_asset?( request )
1434
- request.response_max_size = nil
1435
- end
1436
-
1437
1593
  # Signal the proxy to continue with its request to the origin server.
1438
1594
  true
1439
1595
  end
@@ -1441,41 +1597,99 @@ EOJS
1441
1597
  def response_handler( request, response )
1442
1598
  return if request.url.include?( request_token )
1443
1599
 
1600
+ print_debug_level_2 "Got response: #{response.url}"
1601
+
1444
1602
  @request_transitions.each do |transition|
1445
1603
  next if !transition.running? || transition.element != request.url
1446
1604
  transition.complete
1447
1605
  end
1448
1606
 
1449
- javascript.inject response
1607
+ # If we abort the request because it's out of scope we need to emulate
1608
+ # an OK response because we **do** want to be able to grab a page with
1609
+ # the out of scope URL, even if it's empty.
1610
+ # For example, unvalidated_redirect checks need this.
1611
+ if response.code == 0
1612
+ if enforce_scope? && response.scope.out?
1613
+ response.code = 200
1614
+ end
1615
+ else
1616
+ if javascript.inject( response )
1617
+ print_debug_level_2 'Injected custom JS.'
1618
+ end
1619
+ end
1450
1620
 
1451
1621
  # Don't store assets, the browsers will cache them accordingly.
1452
- return if request_for_asset?( request ) || !response.text?
1622
+ if request_for_asset?( request ) || !response.text?
1623
+ print_debug_level_2 'Asset detected, will not store.'
1624
+ return
1625
+ end
1453
1626
 
1454
1627
  # No-matter the scope, don't store resources for external domains.
1455
- return if !response.scope.in_domain?
1628
+ if !response.scope.in_domain?
1629
+ print_debug_level_2 'Outside of domain scope, will not store.'
1630
+ return
1631
+ end
1456
1632
 
1457
- return if enforce_scope? && response.scope.out?
1633
+ if enforce_scope? && response.scope.out?
1634
+ print_debug_level_2 'Outside of general scope, will not store.'
1635
+ return
1636
+ end
1458
1637
 
1459
1638
  whitelist_asset_domains( response )
1460
1639
  save_response response
1461
1640
 
1641
+ print_debug_level_2 'Stored.'
1642
+
1462
1643
  nil
1463
1644
  end
1464
1645
 
1465
1646
  def ignore_request?( request )
1466
- return if !enforce_scope?
1647
+ print_debug_level_2 "Checking: #{request.url}"
1648
+
1649
+ if !enforce_scope?
1650
+ print_debug_level_2 'Allow: Scope enforcement disabled.'
1651
+ return
1652
+ end
1653
+
1654
+ if request_for_asset?( request )
1655
+ print_debug_level_2 'Allow: Asset detected.'
1656
+ return false
1657
+ end
1658
+
1659
+ if !request.scope.follow_protocol?
1660
+ print_debug_level_2 'Disallow: Cannot follow protocol.'
1661
+ return true
1662
+ end
1663
+
1664
+ if !request.scope.in_domain?
1665
+ if self.class.asset_domains.include?( request.parsed_url.domain )
1666
+ print_debug_level_2 'Allow: Out of scope but in CDN list.'
1667
+ return false
1668
+ end
1467
1669
 
1468
- return false if request_for_asset?( request )
1670
+ print_debug_level_2 'Disallow: Domain out of scope and not in CDN list.'
1671
+ return true
1672
+ end
1469
1673
 
1470
- return true if !request.scope.follow_protocol?
1674
+ if request.scope.too_deep?
1675
+ print_debug_level_2 'Disallow: Too deep.'
1676
+ return true
1677
+ end
1471
1678
 
1472
- return true if !request.scope.in_domain? &&
1473
- !self.class.asset_domains.include?( request.parsed_url.domain )
1679
+ if !request.scope.include?
1680
+ print_debug_level_2 'Disallow: Does not match inclusion rules.'
1681
+ return true
1682
+ end
1474
1683
 
1475
- return true if request.scope.too_deep?
1476
- return true if !request.scope.include?
1477
- return true if request.scope.exclude?
1478
- return true if request.scope.redundant?
1684
+ if request.scope.exclude?
1685
+ print_debug_level_2 'Disallow: Matches exclusion rules.'
1686
+ return true
1687
+ end
1688
+
1689
+ if request.scope.redundant?
1690
+ print_debug_level_2 'Disallow: Matches redundant rules.'
1691
+ return true
1692
+ end
1479
1693
 
1480
1694
  false
1481
1695
  end
@@ -1485,9 +1699,13 @@ EOJS
1485
1699
  end
1486
1700
 
1487
1701
  def whitelist_asset_domains( response )
1488
- ASSET_EXTRACTORS.each do |regexp|
1489
- response.body.scan( regexp ).flatten.compact.each do |url|
1490
- self.class.add_asset_domain url
1702
+ synchronize do
1703
+ ASSET_EXTRACTORS.each do |regexp|
1704
+ response.body.scan( regexp ).flatten.compact.each do |url|
1705
+ next if !(domain = self.class.add_asset_domain( url ))
1706
+
1707
+ print_debug_level_2 "#{domain} from #{url} based on #{regexp.source}"
1708
+ end
1491
1709
  end
1492
1710
  end
1493
1711
  end
@@ -1504,11 +1722,15 @@ EOJS
1504
1722
  found_element = false
1505
1723
 
1506
1724
  if (json = JSON.from_request( @last_url, request ))
1725
+ print_debug_level_2 "Extracted JSON input:\n#{json.source}"
1726
+
1507
1727
  elements[:jsons] << json
1508
1728
  found_element = true
1509
1729
  end
1510
1730
 
1511
1731
  if !found_element && (xml = XML.from_request( @last_url, request ))
1732
+ print_debug_level_2 "Extracted XML input:\n#{xml.source}"
1733
+
1512
1734
  elements[:xmls] << xml
1513
1735
  found_element = true
1514
1736
  end
@@ -1539,13 +1761,30 @@ EOJS
1539
1761
  return
1540
1762
  end
1541
1763
 
1764
+ if (form = elements[:forms].last)
1765
+ print_debug_level_2 "Extracted form input:\n" <<
1766
+ "#{form.method.to_s.upcase} #{form.action} #{form.inputs}"
1767
+ end
1768
+
1542
1769
  el = elements.values.flatten
1543
1770
 
1771
+ if el.empty?
1772
+ print_debug_level_2 'No elements found.'
1773
+ return
1774
+ end
1775
+
1544
1776
  # Don't bother if the system in general has already seen the vectors.
1545
- return if el.empty? || !el.find { |e| !ElementFilter.include?( e ) }
1777
+ if el.empty? || !el.find { |e| !ElementFilter.include?( e ) }
1778
+ print_debug_level_2 'Ignoring, already seen.'
1779
+ return
1780
+ end
1546
1781
 
1547
1782
  begin
1548
- return if !el.find { |e| !skip_state?( e ) }
1783
+ if !el.find { |e| !skip_state?( e ) }
1784
+ print_debug_level_2 'Ignoring, already seen.'
1785
+ return
1786
+ end
1787
+
1549
1788
  el.each { |e| skip_state e.id }
1550
1789
  # This could be an orphaned HTTP request, without a job, if running in
1551
1790
  # BrowserCluster::Worker.
@@ -1565,21 +1804,25 @@ EOJS
1565
1804
  end
1566
1805
 
1567
1806
  def from_preloads( request, response )
1568
- return if !(preloaded = preloads.delete( request.url ))
1807
+ synchronize do
1808
+ return if !(preloaded = preloads.delete( request.url ))
1569
1809
 
1570
- copy_response_data( preloaded, response )
1571
- response.request = request
1572
- save_response( response ) if !preloaded.url.include?( request_token )
1810
+ copy_response_data( preloaded, response )
1811
+ response.request = request
1812
+ save_response( response ) if !preloaded.url.include?( request_token )
1573
1813
 
1574
- preloaded
1814
+ preloaded
1815
+ end
1575
1816
  end
1576
1817
 
1577
1818
  def from_cache( request, response )
1578
- return if !@cache.include?( request.url )
1819
+ synchronize do
1820
+ return if !@cache.include?( request.url )
1579
1821
 
1580
- copy_response_data( @cache[request.url], response )
1581
- response.request = request
1582
- save_response response
1822
+ copy_response_data( @cache[request.url], response )
1823
+ response.request = request
1824
+ save_response response
1825
+ end
1583
1826
  end
1584
1827
 
1585
1828
  def copy_response_data( source, destination )