contrast-agent 3.8.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 (500) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-format +5 -0
  3. data/.dockerignore +10 -0
  4. data/.gitignore +58 -0
  5. data/.gitmodules +6 -0
  6. data/.rspec +6 -0
  7. data/.simplecov +4 -0
  8. data/Gemfile +7 -0
  9. data/LICENSE.txt +12 -0
  10. data/Rakefile +15 -0
  11. data/exe/contrast_service +29 -0
  12. data/ext/build_funchook.rb +48 -0
  13. data/ext/cs__assess_active_record_named/cs__active_record_named.c +47 -0
  14. data/ext/cs__assess_active_record_named/cs__active_record_named.h +10 -0
  15. data/ext/cs__assess_active_record_named/extconf.rb +2 -0
  16. data/ext/cs__assess_array/cs__assess_array.c +38 -0
  17. data/ext/cs__assess_array/cs__assess_array.h +9 -0
  18. data/ext/cs__assess_array/extconf.rb +2 -0
  19. data/ext/cs__assess_basic_object/cs__assess_basic_object.c +50 -0
  20. data/ext/cs__assess_basic_object/cs__assess_basic_object.h +17 -0
  21. data/ext/cs__assess_basic_object/extconf.rb +2 -0
  22. data/ext/cs__assess_fiber_track/cs__assess_fiber_track.c +86 -0
  23. data/ext/cs__assess_fiber_track/cs__assess_fiber_track.h +34 -0
  24. data/ext/cs__assess_fiber_track/extconf.rb +2 -0
  25. data/ext/cs__assess_hash/cs__assess_hash.c +64 -0
  26. data/ext/cs__assess_hash/cs__assess_hash.h +24 -0
  27. data/ext/cs__assess_hash/extconf.rb +2 -0
  28. data/ext/cs__assess_kernel/cs__assess_kernel.c +36 -0
  29. data/ext/cs__assess_kernel/cs__assess_kernel.h +10 -0
  30. data/ext/cs__assess_kernel/extconf.rb +2 -0
  31. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +47 -0
  32. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +18 -0
  33. data/ext/cs__assess_marshal_module/extconf.rb +2 -0
  34. data/ext/cs__assess_module/cs__assess_module.c +78 -0
  35. data/ext/cs__assess_module/cs__assess_module.h +25 -0
  36. data/ext/cs__assess_module/extconf.rb +2 -0
  37. data/ext/cs__assess_regexp/cs__assess_regexp.c +48 -0
  38. data/ext/cs__assess_regexp/cs__assess_regexp.h +22 -0
  39. data/ext/cs__assess_regexp/extconf.rb +2 -0
  40. data/ext/cs__assess_regexp_track/cs__assess_regexp_track.c +63 -0
  41. data/ext/cs__assess_regexp_track/cs__assess_regexp_track.h +29 -0
  42. data/ext/cs__assess_regexp_track/extconf.rb +2 -0
  43. data/ext/cs__assess_string/cs__assess_string.c +38 -0
  44. data/ext/cs__assess_string/cs__assess_string.h +19 -0
  45. data/ext/cs__assess_string/extconf.rb +2 -0
  46. data/ext/cs__assess_string_interpolation26/cs__assess_string_interpolation26.c +31 -0
  47. data/ext/cs__assess_string_interpolation26/cs__assess_string_interpolation26.h +13 -0
  48. data/ext/cs__assess_string_interpolation26/extconf.rb +2 -0
  49. data/ext/cs__common/cs__common.c +60 -0
  50. data/ext/cs__common/cs__common.h +28 -0
  51. data/ext/cs__common/extconf.rb +20 -0
  52. data/ext/cs__contrast_patch/cs__contrast_patch.c +445 -0
  53. data/ext/cs__contrast_patch/cs__contrast_patch.h +196 -0
  54. data/ext/cs__contrast_patch/extconf.rb +2 -0
  55. data/ext/cs__protect_kernel/cs__protect_kernel.c +37 -0
  56. data/ext/cs__protect_kernel/cs__protect_kernel.h +11 -0
  57. data/ext/cs__protect_kernel/extconf.rb +2 -0
  58. data/ext/cs__scope/cs__scope.c +96 -0
  59. data/ext/cs__scope/cs__scope.h +33 -0
  60. data/ext/cs__scope/extconf.rb +2 -0
  61. data/ext/extconf_common.rb +49 -0
  62. data/funchook/LICENSE +360 -0
  63. data/funchook/Makefile +29 -0
  64. data/funchook/Makefile.in +29 -0
  65. data/funchook/README.md +121 -0
  66. data/funchook/appveyor.yml +42 -0
  67. data/funchook/autogen.sh +3 -0
  68. data/funchook/autom4te.cache/output.0 +4976 -0
  69. data/funchook/autom4te.cache/requests +78 -0
  70. data/funchook/autom4te.cache/traces.0 +364 -0
  71. data/funchook/config.guess +1530 -0
  72. data/funchook/config.log +490 -0
  73. data/funchook/config.status +1016 -0
  74. data/funchook/config.sub +1773 -0
  75. data/funchook/configure +4976 -0
  76. data/funchook/configure.ac +59 -0
  77. data/funchook/distorm/COPYING +26 -0
  78. data/funchook/distorm/MANIFEST +25 -0
  79. data/funchook/distorm/MANIFEST.in +4 -0
  80. data/funchook/distorm/README.md +12 -0
  81. data/funchook/distorm/disOps/disOps.py +795 -0
  82. data/funchook/distorm/disOps/x86db.py +404 -0
  83. data/funchook/distorm/disOps/x86header.py +247 -0
  84. data/funchook/distorm/disOps/x86sets.py +1664 -0
  85. data/funchook/distorm/examples/cs/TestdiStorm/Program.cs +79 -0
  86. data/funchook/distorm/examples/cs/TestdiStorm/Properties/AssemblyInfo.cs +36 -0
  87. data/funchook/distorm/examples/cs/TestdiStorm/TestdiStorm.csproj +69 -0
  88. data/funchook/distorm/examples/cs/distorm-net.sln +26 -0
  89. data/funchook/distorm/examples/cs/distorm-net/CodeInfo.cs +23 -0
  90. data/funchook/distorm/examples/cs/distorm-net/DecodedInst.cs +15 -0
  91. data/funchook/distorm/examples/cs/distorm-net/DecodedResult.cs +14 -0
  92. data/funchook/distorm/examples/cs/distorm-net/DecomposedInst.cs +36 -0
  93. data/funchook/distorm/examples/cs/distorm-net/DecomposedResult.cs +14 -0
  94. data/funchook/distorm/examples/cs/distorm-net/Opcodes.cs +1268 -0
  95. data/funchook/distorm/examples/cs/distorm-net/Opcodes.tt +37 -0
  96. data/funchook/distorm/examples/cs/distorm-net/Operand.cs +25 -0
  97. data/funchook/distorm/examples/cs/distorm-net/Properties/AssemblyInfo.cs +36 -0
  98. data/funchook/distorm/examples/cs/distorm-net/diStorm3.cs +411 -0
  99. data/funchook/distorm/examples/cs/distorm-net/distorm-net.csproj +80 -0
  100. data/funchook/distorm/examples/cs/readme +3 -0
  101. data/funchook/distorm/examples/ddk/README +48 -0
  102. data/funchook/distorm/examples/ddk/distorm.ini +11 -0
  103. data/funchook/distorm/examples/ddk/dummy.c +15 -0
  104. data/funchook/distorm/examples/ddk/main.c +91 -0
  105. data/funchook/distorm/examples/ddk/makefile +1 -0
  106. data/funchook/distorm/examples/ddk/sources +10 -0
  107. data/funchook/distorm/examples/java/Makefile +23 -0
  108. data/funchook/distorm/examples/java/distorm/src/Main.java +43 -0
  109. data/funchook/distorm/examples/java/distorm/src/diStorm3/CodeInfo.java +27 -0
  110. data/funchook/distorm/examples/java/distorm/src/diStorm3/DecodedInst.java +32 -0
  111. data/funchook/distorm/examples/java/distorm/src/diStorm3/DecodedResult.java +11 -0
  112. data/funchook/distorm/examples/java/distorm/src/diStorm3/DecomposedInst.java +89 -0
  113. data/funchook/distorm/examples/java/distorm/src/diStorm3/DecomposedResult.java +11 -0
  114. data/funchook/distorm/examples/java/distorm/src/diStorm3/OpcodeEnum.java +131 -0
  115. data/funchook/distorm/examples/java/distorm/src/diStorm3/Opcodes.java +1123 -0
  116. data/funchook/distorm/examples/java/distorm/src/diStorm3/Operand.java +24 -0
  117. data/funchook/distorm/examples/java/distorm/src/diStorm3/distorm3.java +41 -0
  118. data/funchook/distorm/examples/java/jdistorm.c +405 -0
  119. data/funchook/distorm/examples/java/jdistorm.h +40 -0
  120. data/funchook/distorm/examples/java/jdistorm.sln +20 -0
  121. data/funchook/distorm/examples/java/jdistorm.vcproj +208 -0
  122. data/funchook/distorm/examples/linux/Makefile +15 -0
  123. data/funchook/distorm/examples/linux/main.c +181 -0
  124. data/funchook/distorm/examples/tests/Makefile +15 -0
  125. data/funchook/distorm/examples/tests/main.cpp +42 -0
  126. data/funchook/distorm/examples/tests/main.py +66 -0
  127. data/funchook/distorm/examples/tests/test_distorm3.py +1672 -0
  128. data/funchook/distorm/examples/tests/tests.sln +20 -0
  129. data/funchook/distorm/examples/tests/tests.vcxproj +82 -0
  130. data/funchook/distorm/examples/tests/tests.vcxproj.filters +22 -0
  131. data/funchook/distorm/examples/win32/disasm.sln +25 -0
  132. data/funchook/distorm/examples/win32/disasm.vcxproj +201 -0
  133. data/funchook/distorm/examples/win32/disasm.vcxproj.filters +14 -0
  134. data/funchook/distorm/examples/win32/main.cpp +163 -0
  135. data/funchook/distorm/include/distorm.h +482 -0
  136. data/funchook/distorm/include/mnemonics.h +301 -0
  137. data/funchook/distorm/make/linux/Makefile +28 -0
  138. data/funchook/distorm/make/mac/Makefile +24 -0
  139. data/funchook/distorm/make/win32/cdistorm.vcxproj +239 -0
  140. data/funchook/distorm/make/win32/cdistorm.vcxproj.filters +80 -0
  141. data/funchook/distorm/make/win32/distorm.sln +25 -0
  142. data/funchook/distorm/make/win32/resource.h +14 -0
  143. data/funchook/distorm/make/win32/resource.rc +99 -0
  144. data/funchook/distorm/python/distorm3/__init__.py +957 -0
  145. data/funchook/distorm/python/distorm3/sample.py +51 -0
  146. data/funchook/distorm/setup.cfg +10 -0
  147. data/funchook/distorm/setup.py +266 -0
  148. data/funchook/distorm/src/config.h +169 -0
  149. data/funchook/distorm/src/decoder.c +641 -0
  150. data/funchook/distorm/src/decoder.h +33 -0
  151. data/funchook/distorm/src/distorm.c +413 -0
  152. data/funchook/distorm/src/instructions.c +597 -0
  153. data/funchook/distorm/src/instructions.h +463 -0
  154. data/funchook/distorm/src/insts.c +7939 -0
  155. data/funchook/distorm/src/insts.h +64 -0
  156. data/funchook/distorm/src/mnemonics.c +284 -0
  157. data/funchook/distorm/src/operands.c +1290 -0
  158. data/funchook/distorm/src/operands.h +28 -0
  159. data/funchook/distorm/src/prefix.c +368 -0
  160. data/funchook/distorm/src/prefix.h +64 -0
  161. data/funchook/distorm/src/textdefs.c +172 -0
  162. data/funchook/distorm/src/textdefs.h +57 -0
  163. data/funchook/distorm/src/wstring.c +47 -0
  164. data/funchook/distorm/src/wstring.h +35 -0
  165. data/funchook/distorm/src/x86defs.h +82 -0
  166. data/funchook/include/funchook.h +123 -0
  167. data/funchook/install-sh +527 -0
  168. data/funchook/src/Makefile +70 -0
  169. data/funchook/src/Makefile.in +70 -0
  170. data/funchook/src/__strerror.h +109 -0
  171. data/funchook/src/config.h +101 -0
  172. data/funchook/src/config.h.in +100 -0
  173. data/funchook/src/decoder.o +0 -0
  174. data/funchook/src/distorm.o +0 -0
  175. data/funchook/src/funchook.c +440 -0
  176. data/funchook/src/funchook.o +0 -0
  177. data/funchook/src/funchook_internal.h +155 -0
  178. data/funchook/src/funchook_io.c +182 -0
  179. data/funchook/src/funchook_io.h +64 -0
  180. data/funchook/src/funchook_io.o +0 -0
  181. data/funchook/src/funchook_syscall.S +134 -0
  182. data/funchook/src/funchook_syscall.o +0 -0
  183. data/funchook/src/funchook_unix.c +480 -0
  184. data/funchook/src/funchook_unix.o +0 -0
  185. data/funchook/src/funchook_windows.c +397 -0
  186. data/funchook/src/funchook_x86.c +622 -0
  187. data/funchook/src/funchook_x86.o +0 -0
  188. data/funchook/src/instructions.o +0 -0
  189. data/funchook/src/insts.o +0 -0
  190. data/funchook/src/libfunchook.so +0 -0
  191. data/funchook/src/mnemonics.o +0 -0
  192. data/funchook/src/operands.o +0 -0
  193. data/funchook/src/os_func.c +115 -0
  194. data/funchook/src/os_func.h +75 -0
  195. data/funchook/src/os_func.o +0 -0
  196. data/funchook/src/os_func_unix.c +94 -0
  197. data/funchook/src/os_func_unix.o +0 -0
  198. data/funchook/src/os_func_windows.c +32 -0
  199. data/funchook/src/prefix.o +0 -0
  200. data/funchook/src/printf_base.c +1688 -0
  201. data/funchook/src/printf_base.h +46 -0
  202. data/funchook/src/printf_base.o +0 -0
  203. data/funchook/src/textdefs.o +0 -0
  204. data/funchook/src/wstring.o +0 -0
  205. data/funchook/test/Makefile +43 -0
  206. data/funchook/test/Makefile.in +43 -0
  207. data/funchook/test/funchook_test +0 -0
  208. data/funchook/test/libfunchook_test.c +25 -0
  209. data/funchook/test/libfunchook_test.so +0 -0
  210. data/funchook/test/libfunchook_test2.c +18 -0
  211. data/funchook/test/suffix.list +600 -0
  212. data/funchook/test/test_main.c +430 -0
  213. data/funchook/test/test_main.o +0 -0
  214. data/funchook/test/x86_64_test.S +10 -0
  215. data/funchook/test/x86_64_test.o +0 -0
  216. data/funchook/test/x86_test.S +339 -0
  217. data/funchook/win32/config.h +1 -0
  218. data/funchook/win32/funchook.sln +52 -0
  219. data/funchook/win32/funchook.vcxproj +188 -0
  220. data/funchook/win32/funchook.vcxproj.filters +84 -0
  221. data/funchook/win32/funchook_test.vcxproj +170 -0
  222. data/funchook/win32/funchook_test.vcxproj.filters +22 -0
  223. data/funchook/win32/funchook_test_dll.vcxproj +184 -0
  224. data/funchook/win32/funchook_test_dll.vcxproj.filters +30 -0
  225. data/funchook/win32/funchook_test_exe.def +3 -0
  226. data/lib/contrast-agent.rb +8 -0
  227. data/lib/contrast.rb +57 -0
  228. data/lib/contrast/agent.rb +80 -0
  229. data/lib/contrast/agent/assess.rb +45 -0
  230. data/lib/contrast/agent/assess/adjusted_span.rb +25 -0
  231. data/lib/contrast/agent/assess/class_reverter.rb +82 -0
  232. data/lib/contrast/agent/assess/contrast_event.rb +398 -0
  233. data/lib/contrast/agent/assess/frozen_properties.rb +41 -0
  234. data/lib/contrast/agent/assess/insulator.rb +53 -0
  235. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +78 -0
  236. data/lib/contrast/agent/assess/policy/patcher.rb +85 -0
  237. data/lib/contrast/agent/assess/policy/policy.rb +116 -0
  238. data/lib/contrast/agent/assess/policy/policy_node.rb +289 -0
  239. data/lib/contrast/agent/assess/policy/policy_scanner.rb +44 -0
  240. data/lib/contrast/agent/assess/policy/preshift.rb +94 -0
  241. data/lib/contrast/agent/assess/policy/propagation_method.rb +260 -0
  242. data/lib/contrast/agent/assess/policy/propagation_node.rb +127 -0
  243. data/lib/contrast/agent/assess/policy/propagator.rb +35 -0
  244. data/lib/contrast/agent/assess/policy/propagator/append.rb +54 -0
  245. data/lib/contrast/agent/assess/policy/propagator/base.rb +37 -0
  246. data/lib/contrast/agent/assess/policy/propagator/center.rb +73 -0
  247. data/lib/contrast/agent/assess/policy/propagator/custom.rb +36 -0
  248. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +62 -0
  249. data/lib/contrast/agent/assess/policy/propagator/insert.rb +55 -0
  250. data/lib/contrast/agent/assess/policy/propagator/keep.rb +26 -0
  251. data/lib/contrast/agent/assess/policy/propagator/next.rb +42 -0
  252. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +50 -0
  253. data/lib/contrast/agent/assess/policy/propagator/remove.rb +76 -0
  254. data/lib/contrast/agent/assess/policy/propagator/replace.rb +27 -0
  255. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +38 -0
  256. data/lib/contrast/agent/assess/policy/propagator/select.rb +86 -0
  257. data/lib/contrast/agent/assess/policy/propagator/splat.rb +60 -0
  258. data/lib/contrast/agent/assess/policy/propagator/split.rb +49 -0
  259. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +169 -0
  260. data/lib/contrast/agent/assess/policy/propagator/trim.rb +81 -0
  261. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +79 -0
  262. data/lib/contrast/agent/assess/policy/source_method.rb +209 -0
  263. data/lib/contrast/agent/assess/policy/source_node.rb +62 -0
  264. data/lib/contrast/agent/assess/policy/trigger_method.rb +209 -0
  265. data/lib/contrast/agent/assess/policy/trigger_node.rb +198 -0
  266. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +77 -0
  267. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +31 -0
  268. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +40 -0
  269. data/lib/contrast/agent/assess/properties.rb +392 -0
  270. data/lib/contrast/agent/assess/rule.rb +18 -0
  271. data/lib/contrast/agent/assess/rule/base.rb +72 -0
  272. data/lib/contrast/agent/assess/rule/csrf.rb +66 -0
  273. data/lib/contrast/agent/assess/rule/csrf/csrf_action.rb +28 -0
  274. data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +69 -0
  275. data/lib/contrast/agent/assess/rule/csrf/csrf_watcher.rb +132 -0
  276. data/lib/contrast/agent/assess/rule/provider.rb +21 -0
  277. data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +62 -0
  278. data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +73 -0
  279. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +121 -0
  280. data/lib/contrast/agent/assess/rule/redos.rb +68 -0
  281. data/lib/contrast/agent/assess/rule/response_scanning_rule.rb +47 -0
  282. data/lib/contrast/agent/assess/rule/response_watcher.rb +36 -0
  283. data/lib/contrast/agent/assess/rule/watcher.rb +36 -0
  284. data/lib/contrast/agent/assess/tag.rb +151 -0
  285. data/lib/contrast/agent/at_exit_hook.rb +33 -0
  286. data/lib/contrast/agent/class_reopener.rb +195 -0
  287. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +26 -0
  288. data/lib/contrast/agent/deadzone/policy/policy.rb +57 -0
  289. data/lib/contrast/agent/disable_reaction.rb +24 -0
  290. data/lib/contrast/agent/exclusion_matcher.rb +190 -0
  291. data/lib/contrast/agent/feature_state.rb +379 -0
  292. data/lib/contrast/agent/inventory/policy/policy.rb +32 -0
  293. data/lib/contrast/agent/inventory/policy/trigger_node.rb +22 -0
  294. data/lib/contrast/agent/logger_manager.rb +116 -0
  295. data/lib/contrast/agent/middleware.rb +352 -0
  296. data/lib/contrast/agent/module_data.rb +16 -0
  297. data/lib/contrast/agent/patching/policy/after_load_patch.rb +37 -0
  298. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +58 -0
  299. data/lib/contrast/agent/patching/policy/method_policy.rb +94 -0
  300. data/lib/contrast/agent/patching/policy/module_policy.rb +116 -0
  301. data/lib/contrast/agent/patching/policy/patch.rb +312 -0
  302. data/lib/contrast/agent/patching/policy/patch_status.rb +192 -0
  303. data/lib/contrast/agent/patching/policy/patcher.rb +310 -0
  304. data/lib/contrast/agent/patching/policy/policy.rb +138 -0
  305. data/lib/contrast/agent/patching/policy/policy_node.rb +80 -0
  306. data/lib/contrast/agent/patching/policy/policy_unpatcher.rb +28 -0
  307. data/lib/contrast/agent/patching/policy/trigger_node.rb +81 -0
  308. data/lib/contrast/agent/protect/policy/policy.rb +37 -0
  309. data/lib/contrast/agent/protect/policy/trigger_node.rb +23 -0
  310. data/lib/contrast/agent/protect/rule.rb +58 -0
  311. data/lib/contrast/agent/protect/rule/base.rb +300 -0
  312. data/lib/contrast/agent/protect/rule/base_service.rb +88 -0
  313. data/lib/contrast/agent/protect/rule/cmd_injection.rb +156 -0
  314. data/lib/contrast/agent/protect/rule/csrf.rb +118 -0
  315. data/lib/contrast/agent/protect/rule/csrf/csrf_evaluator.rb +103 -0
  316. data/lib/contrast/agent/protect/rule/csrf/csrf_token_injector.rb +85 -0
  317. data/lib/contrast/agent/protect/rule/default_scanner.rb +300 -0
  318. data/lib/contrast/agent/protect/rule/deserialization.rb +193 -0
  319. data/lib/contrast/agent/protect/rule/http_method_tampering.rb +80 -0
  320. data/lib/contrast/agent/protect/rule/no_sqli.rb +101 -0
  321. data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +40 -0
  322. data/lib/contrast/agent/protect/rule/path_traversal.rb +143 -0
  323. data/lib/contrast/agent/protect/rule/sqli.rb +101 -0
  324. data/lib/contrast/agent/protect/rule/sqli/default_sql_scanner.rb +16 -0
  325. data/lib/contrast/agent/protect/rule/sqli/mysql_sql_scanner.rb +38 -0
  326. data/lib/contrast/agent/protect/rule/sqli/postgres_sql_scanner.rb +22 -0
  327. data/lib/contrast/agent/protect/rule/sqli/sqlite_sql_scanner.rb +19 -0
  328. data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +20 -0
  329. data/lib/contrast/agent/protect/rule/xss.rb +24 -0
  330. data/lib/contrast/agent/protect/rule/xxe.rb +120 -0
  331. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +82 -0
  332. data/lib/contrast/agent/railtie.rb +30 -0
  333. data/lib/contrast/agent/reaction_processor.rb +47 -0
  334. data/lib/contrast/agent/request.rb +493 -0
  335. data/lib/contrast/agent/request_context.rb +225 -0
  336. data/lib/contrast/agent/require_state.rb +61 -0
  337. data/lib/contrast/agent/response.rb +215 -0
  338. data/lib/contrast/agent/rewriter.rb +244 -0
  339. data/lib/contrast/agent/scope.rb +28 -0
  340. data/lib/contrast/agent/service_heartbeat.rb +37 -0
  341. data/lib/contrast/agent/settings_state.rb +148 -0
  342. data/lib/contrast/agent/socket_client.rb +125 -0
  343. data/lib/contrast/agent/thread.rb +26 -0
  344. data/lib/contrast/agent/tracepoint_hook.rb +51 -0
  345. data/lib/contrast/agent/version.rb +8 -0
  346. data/lib/contrast/api.rb +17 -0
  347. data/lib/contrast/api/.gitkeep +0 -0
  348. data/lib/contrast/api/connection_status.rb +49 -0
  349. data/lib/contrast/api/socket.rb +43 -0
  350. data/lib/contrast/api/speedracer.rb +206 -0
  351. data/lib/contrast/api/tcp_socket.rb +31 -0
  352. data/lib/contrast/api/unix_socket.rb +25 -0
  353. data/lib/contrast/common_agent_configuration.rb +86 -0
  354. data/lib/contrast/components/agent.rb +85 -0
  355. data/lib/contrast/components/app_context.rb +188 -0
  356. data/lib/contrast/components/assess.rb +67 -0
  357. data/lib/contrast/components/config.rb +135 -0
  358. data/lib/contrast/components/contrast_service.rb +113 -0
  359. data/lib/contrast/components/heap_dump.rb +34 -0
  360. data/lib/contrast/components/interface.rb +178 -0
  361. data/lib/contrast/components/inventory.rb +23 -0
  362. data/lib/contrast/components/logger.rb +92 -0
  363. data/lib/contrast/components/protect.rb +38 -0
  364. data/lib/contrast/components/sampling.rb +41 -0
  365. data/lib/contrast/components/scope.rb +106 -0
  366. data/lib/contrast/components/settings.rb +140 -0
  367. data/lib/contrast/config.rb +33 -0
  368. data/lib/contrast/config/agent_configuration.rb +24 -0
  369. data/lib/contrast/config/application_configuration.rb +27 -0
  370. data/lib/contrast/config/assess_configuration.rb +22 -0
  371. data/lib/contrast/config/assess_rules_configuration.rb +18 -0
  372. data/lib/contrast/config/base_configuration.rb +105 -0
  373. data/lib/contrast/config/default_value.rb +16 -0
  374. data/lib/contrast/config/exception_configuration.rb +21 -0
  375. data/lib/contrast/config/heap_dump_configuration.rb +23 -0
  376. data/lib/contrast/config/inventory_configuration.rb +20 -0
  377. data/lib/contrast/config/logger_configuration.rb +20 -0
  378. data/lib/contrast/config/protect_configuration.rb +20 -0
  379. data/lib/contrast/config/protect_rule_configuration.rb +37 -0
  380. data/lib/contrast/config/protect_rules_configuration.rb +30 -0
  381. data/lib/contrast/config/root_configuration.rb +26 -0
  382. data/lib/contrast/config/ruby_configuration.rb +39 -0
  383. data/lib/contrast/config/sampling_configuration.rb +22 -0
  384. data/lib/contrast/config/server_configuration.rb +23 -0
  385. data/lib/contrast/config/service_configuration.rb +22 -0
  386. data/lib/contrast/configuration.rb +214 -0
  387. data/lib/contrast/core_extensions/assess.rb +51 -0
  388. data/lib/contrast/core_extensions/assess/array.rb +58 -0
  389. data/lib/contrast/core_extensions/assess/assess_extension.rb +145 -0
  390. data/lib/contrast/core_extensions/assess/basic_object.rb +15 -0
  391. data/lib/contrast/core_extensions/assess/erb.rb +42 -0
  392. data/lib/contrast/core_extensions/assess/exec_trigger.rb +48 -0
  393. data/lib/contrast/core_extensions/assess/fiber.rb +125 -0
  394. data/lib/contrast/core_extensions/assess/hash.rb +22 -0
  395. data/lib/contrast/core_extensions/assess/kernel.rb +95 -0
  396. data/lib/contrast/core_extensions/assess/module.rb +14 -0
  397. data/lib/contrast/core_extensions/assess/regexp.rb +206 -0
  398. data/lib/contrast/core_extensions/assess/string.rb +75 -0
  399. data/lib/contrast/core_extensions/assess/tilt_template_trigger.rb +73 -0
  400. data/lib/contrast/core_extensions/delegator.rb +14 -0
  401. data/lib/contrast/core_extensions/eval_trigger.rb +52 -0
  402. data/lib/contrast/core_extensions/inventory.rb +22 -0
  403. data/lib/contrast/core_extensions/inventory/datastores.rb +37 -0
  404. data/lib/contrast/core_extensions/module.rb +42 -0
  405. data/lib/contrast/core_extensions/object.rb +27 -0
  406. data/lib/contrast/core_extensions/protect.rb +20 -0
  407. data/lib/contrast/core_extensions/protect/applies_command_injection_rule.rb +70 -0
  408. data/lib/contrast/core_extensions/protect/applies_deserialization_rule.rb +58 -0
  409. data/lib/contrast/core_extensions/protect/applies_no_sqli_rule.rb +81 -0
  410. data/lib/contrast/core_extensions/protect/applies_path_traversal_rule.rb +119 -0
  411. data/lib/contrast/core_extensions/protect/applies_sqli_rule.rb +63 -0
  412. data/lib/contrast/core_extensions/protect/applies_xxe_rule.rb +141 -0
  413. data/lib/contrast/core_extensions/protect/kernel.rb +30 -0
  414. data/lib/contrast/core_extensions/protect/psych.rb +7 -0
  415. data/lib/contrast/core_extensions/thread.rb +31 -0
  416. data/lib/contrast/internal_exception.rb +8 -0
  417. data/lib/contrast/rails_extensions/assess/action_controller_inheritance.rb +48 -0
  418. data/lib/contrast/rails_extensions/assess/active_record.rb +32 -0
  419. data/lib/contrast/rails_extensions/assess/active_record_named.rb +61 -0
  420. data/lib/contrast/rails_extensions/assess/configuration.rb +26 -0
  421. data/lib/contrast/rails_extensions/buffer.rb +30 -0
  422. data/lib/contrast/rails_extensions/rack.rb +45 -0
  423. data/lib/contrast/security_exception.rb +14 -0
  424. data/lib/contrast/sinatra_extensions/assess/cookie.rb +26 -0
  425. data/lib/contrast/sinatra_extensions/inventory/sinatra_base.rb +59 -0
  426. data/lib/contrast/tasks/service.rb +95 -0
  427. data/lib/contrast/utils/assess/sampling_util.rb +96 -0
  428. data/lib/contrast/utils/assess/tracking_util.rb +39 -0
  429. data/lib/contrast/utils/boolean_util.rb +33 -0
  430. data/lib/contrast/utils/cache.rb +69 -0
  431. data/lib/contrast/utils/class_util.rb +58 -0
  432. data/lib/contrast/utils/comment_range.rb +19 -0
  433. data/lib/contrast/utils/data_store_util.rb +23 -0
  434. data/lib/contrast/utils/duck_utils.rb +58 -0
  435. data/lib/contrast/utils/env_configuration_item.rb +52 -0
  436. data/lib/contrast/utils/environment_util.rb +152 -0
  437. data/lib/contrast/utils/freeze_util.rb +36 -0
  438. data/lib/contrast/utils/gemfile_reader.rb +191 -0
  439. data/lib/contrast/utils/hash_digest.rb +148 -0
  440. data/lib/contrast/utils/heap_dump_util.rb +113 -0
  441. data/lib/contrast/utils/invalid_configuration_util.rb +88 -0
  442. data/lib/contrast/utils/inventory_util.rb +126 -0
  443. data/lib/contrast/utils/io_util.rb +61 -0
  444. data/lib/contrast/utils/object_share.rb +117 -0
  445. data/lib/contrast/utils/operating_environment.rb +38 -0
  446. data/lib/contrast/utils/os.rb +49 -0
  447. data/lib/contrast/utils/path_util.rb +151 -0
  448. data/lib/contrast/utils/performs_logging.rb +152 -0
  449. data/lib/contrast/utils/preflight_util.rb +13 -0
  450. data/lib/contrast/utils/prevent_serialization.rb +52 -0
  451. data/lib/contrast/utils/rack_assess_session_cookie.rb +104 -0
  452. data/lib/contrast/utils/rails_assess_configuration.rb +95 -0
  453. data/lib/contrast/utils/random_util.rb +22 -0
  454. data/lib/contrast/utils/resource_loader.rb +23 -0
  455. data/lib/contrast/utils/ruby_ast_rewriter.rb +74 -0
  456. data/lib/contrast/utils/scope_util.rb +99 -0
  457. data/lib/contrast/utils/service_response_util.rb +116 -0
  458. data/lib/contrast/utils/service_sender_util.rb +98 -0
  459. data/lib/contrast/utils/sha256_builder.rb +69 -0
  460. data/lib/contrast/utils/sinatra_helper.rb +49 -0
  461. data/lib/contrast/utils/stack_trace_utils.rb +209 -0
  462. data/lib/contrast/utils/string_utils.rb +72 -0
  463. data/lib/contrast/utils/tag_util.rb +139 -0
  464. data/lib/contrast/utils/thread_tracker.rb +54 -0
  465. data/lib/contrast/utils/timer.rb +78 -0
  466. data/resources/assess/policy.json +1673 -0
  467. data/resources/csrf/inject.js +44 -0
  468. data/resources/deadzone/policy.json +55 -0
  469. data/resources/factory-bot-spec/spec_helper.rb +30 -0
  470. data/resources/inventory/policy.json +110 -0
  471. data/resources/protect/policy.json +417 -0
  472. data/resources/rubocops/kernel/catch_cop.rb +37 -0
  473. data/resources/rubocops/kernel/require_cop.rb +37 -0
  474. data/resources/rubocops/kernel/require_relative_cop.rb +33 -0
  475. data/resources/rubocops/module/autoload_cop.rb +37 -0
  476. data/resources/rubocops/module/const_defined_cop.rb +37 -0
  477. data/resources/rubocops/module/const_get_cop.rb +37 -0
  478. data/resources/rubocops/module/const_set_cop.rb +37 -0
  479. data/resources/rubocops/module/constants_cop.rb +37 -0
  480. data/resources/rubocops/module/name_cop.rb +37 -0
  481. data/resources/rubocops/object/class_cop.rb +37 -0
  482. data/resources/rubocops/object/freeze_cop.rb +37 -0
  483. data/resources/rubocops/object/frozen_cop.rb +37 -0
  484. data/resources/rubocops/object/is_a_cop.rb +37 -0
  485. data/resources/rubocops/object/method_cop.rb +37 -0
  486. data/resources/rubocops/object/respond_to_cop.rb +37 -0
  487. data/resources/rubocops/object/singleton_class_cop.rb +37 -0
  488. data/resources/rubocops/regexp/spelling_cop.rb +44 -0
  489. data/resources/rubocops/thread/new_cop.rb +39 -0
  490. data/resources/ruby-spec/ancestors_spec.rb +70 -0
  491. data/resources/ruby-spec/modulo_spec.rb +831 -0
  492. data/resources/ruby-spec/parameters_spec.rb +261 -0
  493. data/resources/ruby-spec/ruby_spec_spec_helper.rb +35 -0
  494. data/resources/test_marker.txt +1 -0
  495. data/ruby-agent.gemspec +129 -0
  496. data/service_executables/.gitkeep +0 -0
  497. data/service_executables/VERSION +1 -0
  498. data/service_executables/linux/contrast-service +0 -0
  499. data/service_executables/mac/contrast-service +0 -0
  500. metadata +945 -0
@@ -0,0 +1,225 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ cs__scoped_require 'contrast/utils/timer'
5
+ cs__scoped_require 'contrast/agent/request'
6
+ cs__scoped_require 'contrast/agent/response'
7
+ cs__scoped_require 'contrast/utils/comment_range'
8
+ cs__scoped_require 'contrast/utils/inventory_util'
9
+ cs__scoped_require 'contrast/components/interface'
10
+
11
+ module Contrast
12
+ module Agent
13
+ # This class acts to encapsulate information about the currently executed
14
+ # request, making it available to the Agent for the duration of the request
15
+ # in a standardized and normalized format which the Agent understands.
16
+ class RequestContext
17
+ include Contrast::Components::Interface
18
+ access_component :logging, :analysis, :scope, :contrast_service
19
+
20
+ EMPTY_INPUT_ANALYSIS_PB = Contrast::Api::Settings::InputAnalysis.new
21
+
22
+ attr_reader :timer,
23
+ :speedracer_input_analysis,
24
+ :request,
25
+ :response,
26
+ :activity,
27
+ :server_activity,
28
+ :route,
29
+ :observed_route
30
+
31
+ def initialize rack_request, app_loaded = true
32
+ with_contrast_scope do
33
+ # all requests get a timer
34
+ @timer = Contrast::Utils::Timer.new
35
+
36
+ # instantiate helper for request and response
37
+ @request = Contrast::Agent::Request.new(rack_request)
38
+
39
+ @activity = Contrast::Api::Dtm::Activity.new
40
+ @activity.http_request = request.dtm
41
+
42
+ @server_activity = Contrast::Api::Dtm::ServerActivity.new
43
+
44
+ @observed_route = Contrast::Api::Dtm::ObservedRoute.new
45
+
46
+ # build analyzer
47
+ @do_not_track = false
48
+ @speedracer_input_analysis = EMPTY_INPUT_ANALYSIS_PB
49
+
50
+ # flag to indicate whether the app is fully loaded
51
+ @app_loaded = !!app_loaded
52
+
53
+ # generic holder for properties that can be set throughout this request
54
+ @_properties = {}
55
+
56
+ @sample = true
57
+
58
+ @sample_request, @sample_response = Contrast::Utils::Assess::SamplingUtil.instance.sample?(@request) if ASSESS.enabled?
59
+
60
+ @sample_response &&= ASSESS.scan_response?
61
+
62
+ append_route_coverage(Contrast::Utils::PathUtil.get_route(@request))
63
+ end
64
+ end
65
+
66
+ def app_loaded?
67
+ @app_loaded
68
+ end
69
+
70
+ def analyze?
71
+ @sample_request || @sample_response
72
+ end
73
+
74
+ def analyze_request?
75
+ @sample_request
76
+ end
77
+
78
+ def analyze_response?
79
+ @sample_response
80
+ end
81
+
82
+ # Convert the discovered route for this request to appropriate forms and
83
+ # disseminate it to those locations where it is necessary for our route
84
+ # coverage and finding vulnerability discovery features to function.
85
+ def append_route_coverage route
86
+ return unless route
87
+
88
+ # For our findings
89
+ @route = route
90
+
91
+ # For SR findings
92
+ @activity.routes << route
93
+
94
+ # For TS routes
95
+ @observed_route.signature = route.route
96
+ @observed_route.verb = route.verb
97
+ @observed_route.session_id = Contrast::Agent::FeatureState.instance.current_session_id
98
+
99
+ # TODO: SPEED-273: deprecate when SR handles this. ContrastUI API currently does not allow empty url, so we have to provide a default
100
+ @observed_route.url = route.url.empty? ? Contrast::Utils::ObjectShare::SLASH : route.url
101
+ end
102
+
103
+ # Collect the results for the given rule with the given action
104
+ #
105
+ # @param rule [String] the id of the rule to which the results apply
106
+ # @param response_type [Symbol] the result of the response, matching a
107
+ # value of Contrast::Api::Dtm::AttackResult::ResponseType
108
+ # @return [Array<Contrast::Api::Dtm::AttackResult>]
109
+ def results_for rule, response_type = nil
110
+ if response_type.nil?
111
+ activity.results.select { |r| r.rule_id == rule }
112
+ else
113
+ activity.results.select { |r| r.rule_id == rule && r.response == response_type }
114
+ end
115
+ end
116
+
117
+ def service_extract_request
118
+ return false if @do_not_track
119
+
120
+ service_response = CONTRAST_SERVICE.send_message(@activity.http_request)
121
+ return false unless service_response
122
+
123
+ handle_protect_state(service_response)
124
+ ia = service_response.input_analysis
125
+ if ia
126
+ logger.debug(nil, "Analysis from Contrast Service: evaluations=#{ ia.results.length }")
127
+ logger.debug(nil, "IA=#{ ia.inspect }")
128
+ @speedracer_input_analysis = ia
129
+ else
130
+ logger.debug(nil, 'Analysis from Contrast Service was empty.')
131
+ false
132
+ end
133
+ rescue Contrast::SecurityException
134
+ raise
135
+ rescue StandardError => e
136
+ logger.warn(e, 'Unable to extract Contrast Service information from request')
137
+ false
138
+ end
139
+
140
+ # NOTE: this method is only used as a backstop if Speedracer sends Input Evaluations
141
+ # when the protect state indicates a security exception should be thrown. This method
142
+ # ensures that the attack reports are generated. Normally these should be generated on
143
+ # Speedracer for any attacks detected during prefilter.
144
+ def handle_protect_state agent_settings
145
+ return unless agent_settings
146
+ return unless agent_settings.protect_state
147
+
148
+ @uuid = agent_settings.protect_state.uuid
149
+ @do_not_track = true unless agent_settings.protect_state.track_request
150
+
151
+ return unless agent_settings.protect_state.security_exception
152
+
153
+ # If Contrast Service has NOT handled the input analysis, handle them here
154
+ build_attack_results(agent_settings)
155
+
156
+ msg = agent_settings.protect_state.security_message
157
+ logger.warn(nil, 'Contrast Service said to block this request')
158
+ raise Contrast::SecurityException.new(nil, (msg || 'Blocking suspicious behavior'))
159
+ end
160
+
161
+ # append anything we've learned to the request seen message
162
+ # this is the sum-total of all inventory information that has
163
+ # been accumulated since the last request
164
+ def extract_after rack_response
165
+ @response = Contrast::Agent::Response.new(rack_response)
166
+ activity.http_response = @response.dtm if @sample_response
167
+ rescue StandardError => e
168
+ logger.error(e, 'Unable to extract information after request')
169
+ end
170
+
171
+ def add_property key, value
172
+ @_properties[key] = value
173
+ end
174
+
175
+ def get_property key
176
+ @_properties[key]
177
+ end
178
+
179
+ def reset_activity
180
+ @activity = Contrast::Api::Dtm::Activity.new(http_request: request.dtm)
181
+ @server_activity = Contrast::Api::Dtm::ServerActivity.new # it doesn't look like this is ever actually used?
182
+ @observed_route = Contrast::Api::Dtm::ObservedRoute.new
183
+ end
184
+
185
+ private
186
+
187
+ # Generate attack results directly from any evaluations on the
188
+ # agent settings object.
189
+ def build_attack_results agent_settings
190
+ return unless agent_settings&.input_analysis&.results&.any?
191
+
192
+ attack_results_by_rule = {}
193
+ agent_settings.input_analysis.results.each do |ia_result|
194
+ rule_id = ia_result.rule_id
195
+ rule = PROTECT.rule(rule_id)
196
+ next unless rule
197
+
198
+ logger.debug(nil, "Building attack result from Contrast Service input analysis: result=#{ ia_result.inspect }")
199
+
200
+ attack_result = if rule.mode == :BLOCK
201
+ # special case for rules (like reflected xss)
202
+ # that used to have an infilter / block
203
+ # mode but now are just block at perimeter
204
+ rule.build_attack_with_match(
205
+ self,
206
+ ia_result,
207
+ attack_results_by_rule[rule_id],
208
+ ia_result.value)
209
+ else
210
+ rule.build_attack_without_match(
211
+ self,
212
+ ia_result,
213
+ attack_results_by_rule[rule_id])
214
+ end
215
+ attack_results_by_rule[rule_id] = attack_result
216
+ end
217
+
218
+ attack_results_by_rule.each_pair do |_, attack_result|
219
+ logger.debug(nil, "Blocking for #{ attack_result.rule_id }")
220
+ activity.results << attack_result
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Agent
6
+ # Thread local way to access the current RequireState. Used as a convenient
7
+ # wrapper to ensure specific key is used and that we handle the case when
8
+ # we try to enter a previously unset require scope.
9
+ # (Note that 'require scopes' track nested requires. This is distinct
10
+ # from 'contrast scope', which tracks instrumentation.)
11
+ module RequireStates
12
+ class << self
13
+ KEY = :thread_local_contrast_require_scope
14
+
15
+ def enter
16
+ scope = current_scope
17
+ unless current_scope
18
+ scope = RequireState.new
19
+ Thread.current[KEY] = scope
20
+ end
21
+ scope.enter
22
+ end
23
+
24
+ def exit
25
+ current_scope.exit
26
+ end
27
+
28
+ def current_scope
29
+ Thread.current[KEY]
30
+ end
31
+
32
+ def status
33
+ current_scope&.scope.to_s
34
+ end
35
+
36
+ def in_scope?
37
+ scope = current_scope
38
+ scope && scope.scope > 1
39
+ end
40
+ end
41
+ end
42
+
43
+ # Simple counter class for tracking how deep in nested requires / file
44
+ # load operations we currently are.
45
+ class RequireState
46
+ attr_reader :scope
47
+
48
+ def initialize
49
+ @scope = 0
50
+ end
51
+
52
+ def enter
53
+ @scope += 1
54
+ end
55
+
56
+ def exit
57
+ @scope -= 1
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,215 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ cs__scoped_require 'resolv'
5
+ cs__scoped_require 'timeout'
6
+
7
+ cs__scoped_require 'contrast/utils/object_share'
8
+ cs__scoped_require 'contrast/utils/string_utils'
9
+ cs__scoped_require 'contrast/utils/comment_range'
10
+ cs__scoped_require 'contrast/utils/hash_digest'
11
+ cs__scoped_require 'contrast/components/interface'
12
+
13
+ module Contrast
14
+ module Agent
15
+ # This class is the Contrast representation of the Rack::Response object. It
16
+ # provides access to the original Rack::Response object as well as extracts
17
+ # data in a format that the Agent expects, caching those transformations in
18
+ # order to avoid repeatedly creating Strings & thrashing GC.
19
+ class Response
20
+ include Contrast::Components::Interface
21
+ access_component :logging
22
+
23
+ extend Forwardable
24
+
25
+ attr_reader :rack_response
26
+
27
+ def initialize rack_response
28
+ @rack_response = rack_response
29
+ @is_array = !rack_response.is_a?(Rack::Response)
30
+ end
31
+
32
+ def document_type
33
+ case content_type
34
+ when /xml/i
35
+ :XML
36
+ when /json/i
37
+ :JSON
38
+ when /html/i
39
+ :NORMAL
40
+ end
41
+ end
42
+
43
+ # B/c the response can change, we can't memoize this :(
44
+ def dtm
45
+ context_response = Contrast::Api::Dtm::HttpResponse.new
46
+ context_response.response_code = response_code.to_i
47
+ headers.each_pair do |key, value|
48
+ k = Contrast::Utils::StringUtils.force_utf8(key)
49
+ v = Contrast::Utils::StringUtils.force_utf8(value)
50
+ context_response.response_headers[k] = v
51
+ end
52
+ context_response.parsed_response_headers = true
53
+
54
+ context_response.response_body = Contrast::Utils::StringUtils.force_utf8(body)
55
+ context_response.parsed_response_body = true
56
+
57
+ doc_type = document_type
58
+ context_response.document_type = doc_type if doc_type
59
+
60
+ context_response
61
+ end
62
+
63
+ def response_code
64
+ return unless @rack_response
65
+
66
+ @is_array ? @rack_response[0].to_i : @rack_response.status
67
+ end
68
+
69
+ def headers
70
+ return unless @rack_response
71
+
72
+ if @is_array
73
+ @rack_response[1]
74
+ else
75
+ @rack_response.headers
76
+ end
77
+ end
78
+
79
+ # Header keys upcased and any underscores replaced with dashes
80
+ # We cannot memoize this, b/c response headers can change during the
81
+ # request lifecycle.
82
+ def normalized_headers
83
+ hash = {}
84
+ headers.each_pair do |header_name, header_value|
85
+ hash[Contrast::Utils::StringUtils.normalized_key(header_name)] = header_value
86
+ end
87
+ hash
88
+ end
89
+
90
+ CONTENT_TYPE_HEADER = 'CONTENT-TYPE'.cs__freeze
91
+ def content_type
92
+ return unless @rack_response
93
+
94
+ if @is_array
95
+ normalized_headers[CONTENT_TYPE_HEADER]
96
+ else
97
+ @rack_response.content_type
98
+ end
99
+ end
100
+
101
+ def header key
102
+ return unless @rack_response
103
+
104
+ if @is_array
105
+ normalized_headers[Contrast::Utils::StringUtils.normalized_key(key)]
106
+ else
107
+ @rack_response.get_header(key)
108
+ end
109
+ end
110
+
111
+ def set_header key, value
112
+ return unless @rack_response
113
+
114
+ if @is_array
115
+ Rack::Utils.set_cookie_header!(@rack_response[1], key, value)
116
+ elsif @rack_response.is_a?(Rack::Response)
117
+ @rack_response.set_header(key, value)
118
+ end
119
+ end
120
+
121
+ # The response body can change during the request lifecycle
122
+ # We should not extract it out as a variable here, or we'll miss those
123
+ # changes. (headdesk)
124
+ def body
125
+ return unless @rack_response
126
+
127
+ if @is_array
128
+ extract_body(@rack_response[2])
129
+ elsif Contrast::Utils::DuckUtils.quacks_to?(@rack_response, :body)
130
+ extract_body(@rack_response.body)
131
+ end
132
+ end
133
+
134
+ def update_body body_string
135
+ return unless @rack_response
136
+
137
+ successfully_updated_body = true
138
+ if @is_array
139
+ if @rack_response[2].is_a?(Rack::BodyProxy)
140
+ successfully_updated_body = update_rack_body_proxy(body_string, true)
141
+ else
142
+ @rack_response[2] = [body_string]
143
+ end
144
+ elsif @rack_response.body.is_a?(Rack::BodyProxy)
145
+ successfully_updated_body = update_rack_body_proxy(body_string)
146
+ else
147
+ @rack_response.body = body_string
148
+ end
149
+ update_content_length(body_string.bytesize) if successfully_updated_body
150
+ end
151
+
152
+ # Set the length header for this response. This value should be set ot the
153
+ # bytesize, NOT THE LENGTH, of the response body. Otherwise, we may get
154
+ # got by the Lint thing.
155
+ CONTENT_LENGTH_HEADER = 'Content-Length'.cs__freeze
156
+ def update_content_length length
157
+ headers[CONTENT_LENGTH_HEADER] = length.to_s
158
+ end
159
+
160
+ private
161
+
162
+ HTTP_PREFIX = /^[Hh][Tt][Tt][Pp][_-]/i.cs__freeze
163
+
164
+ def update_rack_body_proxy body_string, is_array = false
165
+ top_body_proxy = is_array ? @rack_response[2] : @rack_response
166
+ parent_body_proxy = top_body_proxy
167
+ until (next_body = parent_body_proxy.instance_variable_get(:@body)).cs__class != Rack::BodyProxy
168
+ parent_body_proxy = next_body
169
+ end
170
+
171
+ if next_body.cs__class.to_s == 'ActionDispatch::Response::RackBody'
172
+ modified_response = next_body.instance_variable_defined?(:@response) ? next_body.instance_variable_get(:@response) : nil
173
+ if modified_response
174
+ modified_response.body = body_string
175
+ next_body.instance_variable_set(:@response, modified_response)
176
+ end
177
+ elsif next_body.is_a?(Array) && next_body[0].cs__class.to_s == 'ActionView::OutputBuffer'
178
+ new_body = ActionView::OutputBuffer.new(body_string)
179
+ next_body[0] = new_body
180
+ else
181
+ logger.warn("Detected unsupported Rack::BodyProxy internal response class #{ next_body.cs__class }")
182
+ return false
183
+ end
184
+ true
185
+ end
186
+
187
+ def extract_body body
188
+ return nil unless body
189
+
190
+ if defined?(Rack::File) && body.is_a?(Rack::File)
191
+ # not sure what to do in this situation, so don't do anything.
192
+ nil
193
+ elsif Contrast::Utils::DuckUtils.quacks_to?(body, :each)
194
+ acc = []
195
+ body.each { |tmp| acc << read_or_string(tmp) }
196
+ acc.compact.join(Contrast::Utils::ObjectShare::NEW_LINE)
197
+ else
198
+ read_or_string(body)
199
+ end
200
+ end
201
+
202
+ def read_or_string obj
203
+ return nil unless obj
204
+
205
+ if Contrast::Utils::DuckUtils.quacks_to?(obj, :read)
206
+ tmp = obj.read
207
+ obj.rewind
208
+ tmp
209
+ else
210
+ obj.to_s
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end