httpx-patched 1.6.2.1

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 (336) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +191 -0
  3. data/README.md +162 -0
  4. data/doc/release_notes/0_0_1.md +7 -0
  5. data/doc/release_notes/0_0_2.md +9 -0
  6. data/doc/release_notes/0_0_3.md +9 -0
  7. data/doc/release_notes/0_0_4.md +7 -0
  8. data/doc/release_notes/0_0_5.md +5 -0
  9. data/doc/release_notes/0_10_0.md +66 -0
  10. data/doc/release_notes/0_10_1.md +37 -0
  11. data/doc/release_notes/0_10_2.md +5 -0
  12. data/doc/release_notes/0_11_0.md +74 -0
  13. data/doc/release_notes/0_11_1.md +5 -0
  14. data/doc/release_notes/0_11_2.md +5 -0
  15. data/doc/release_notes/0_11_3.md +5 -0
  16. data/doc/release_notes/0_12_0.md +55 -0
  17. data/doc/release_notes/0_13_0.md +58 -0
  18. data/doc/release_notes/0_13_1.md +5 -0
  19. data/doc/release_notes/0_13_2.md +9 -0
  20. data/doc/release_notes/0_14_0.md +79 -0
  21. data/doc/release_notes/0_14_1.md +7 -0
  22. data/doc/release_notes/0_14_2.md +6 -0
  23. data/doc/release_notes/0_14_3.md +5 -0
  24. data/doc/release_notes/0_14_4.md +5 -0
  25. data/doc/release_notes/0_14_5.md +11 -0
  26. data/doc/release_notes/0_15_0.md +53 -0
  27. data/doc/release_notes/0_15_1.md +8 -0
  28. data/doc/release_notes/0_15_2.md +9 -0
  29. data/doc/release_notes/0_15_3.md +5 -0
  30. data/doc/release_notes/0_15_4.md +5 -0
  31. data/doc/release_notes/0_16_0.md +93 -0
  32. data/doc/release_notes/0_16_1.md +5 -0
  33. data/doc/release_notes/0_17_0.md +49 -0
  34. data/doc/release_notes/0_18_0.md +69 -0
  35. data/doc/release_notes/0_18_1.md +12 -0
  36. data/doc/release_notes/0_18_2.md +10 -0
  37. data/doc/release_notes/0_18_3.md +7 -0
  38. data/doc/release_notes/0_18_4.md +14 -0
  39. data/doc/release_notes/0_18_5.md +10 -0
  40. data/doc/release_notes/0_18_6.md +5 -0
  41. data/doc/release_notes/0_18_7.md +5 -0
  42. data/doc/release_notes/0_19_0.md +39 -0
  43. data/doc/release_notes/0_19_1.md +5 -0
  44. data/doc/release_notes/0_19_2.md +7 -0
  45. data/doc/release_notes/0_19_3.md +6 -0
  46. data/doc/release_notes/0_19_4.md +14 -0
  47. data/doc/release_notes/0_19_5.md +13 -0
  48. data/doc/release_notes/0_19_6.md +5 -0
  49. data/doc/release_notes/0_19_7.md +5 -0
  50. data/doc/release_notes/0_19_8.md +5 -0
  51. data/doc/release_notes/0_1_0.md +9 -0
  52. data/doc/release_notes/0_20_0.md +36 -0
  53. data/doc/release_notes/0_20_1.md +5 -0
  54. data/doc/release_notes/0_20_2.md +7 -0
  55. data/doc/release_notes/0_20_3.md +6 -0
  56. data/doc/release_notes/0_20_4.md +17 -0
  57. data/doc/release_notes/0_20_5.md +3 -0
  58. data/doc/release_notes/0_21_0.md +96 -0
  59. data/doc/release_notes/0_21_1.md +12 -0
  60. data/doc/release_notes/0_22_0.md +13 -0
  61. data/doc/release_notes/0_22_1.md +11 -0
  62. data/doc/release_notes/0_22_2.md +5 -0
  63. data/doc/release_notes/0_22_3.md +55 -0
  64. data/doc/release_notes/0_22_4.md +6 -0
  65. data/doc/release_notes/0_22_5.md +6 -0
  66. data/doc/release_notes/0_23_0.md +42 -0
  67. data/doc/release_notes/0_23_1.md +5 -0
  68. data/doc/release_notes/0_23_2.md +5 -0
  69. data/doc/release_notes/0_23_3.md +6 -0
  70. data/doc/release_notes/0_23_4.md +5 -0
  71. data/doc/release_notes/0_24_0.md +48 -0
  72. data/doc/release_notes/0_24_1.md +12 -0
  73. data/doc/release_notes/0_24_2.md +12 -0
  74. data/doc/release_notes/0_24_3.md +12 -0
  75. data/doc/release_notes/0_24_4.md +18 -0
  76. data/doc/release_notes/0_24_5.md +6 -0
  77. data/doc/release_notes/0_24_6.md +5 -0
  78. data/doc/release_notes/0_24_7.md +10 -0
  79. data/doc/release_notes/0_2_0.md +5 -0
  80. data/doc/release_notes/0_2_1.md +16 -0
  81. data/doc/release_notes/0_3_0.md +12 -0
  82. data/doc/release_notes/0_3_1.md +6 -0
  83. data/doc/release_notes/0_4_0.md +51 -0
  84. data/doc/release_notes/0_4_1.md +3 -0
  85. data/doc/release_notes/0_5_0.md +15 -0
  86. data/doc/release_notes/0_5_1.md +14 -0
  87. data/doc/release_notes/0_6_0.md +5 -0
  88. data/doc/release_notes/0_6_1.md +6 -0
  89. data/doc/release_notes/0_6_2.md +6 -0
  90. data/doc/release_notes/0_6_3.md +13 -0
  91. data/doc/release_notes/0_6_4.md +21 -0
  92. data/doc/release_notes/0_6_5.md +22 -0
  93. data/doc/release_notes/0_6_6.md +19 -0
  94. data/doc/release_notes/0_6_7.md +5 -0
  95. data/doc/release_notes/0_7_0.md +46 -0
  96. data/doc/release_notes/0_8_0.md +27 -0
  97. data/doc/release_notes/0_8_1.md +8 -0
  98. data/doc/release_notes/0_8_2.md +7 -0
  99. data/doc/release_notes/0_9_0.md +38 -0
  100. data/doc/release_notes/1_0_0.md +60 -0
  101. data/doc/release_notes/1_0_1.md +5 -0
  102. data/doc/release_notes/1_0_2.md +7 -0
  103. data/doc/release_notes/1_1_0.md +32 -0
  104. data/doc/release_notes/1_1_1.md +17 -0
  105. data/doc/release_notes/1_1_2.md +12 -0
  106. data/doc/release_notes/1_1_3.md +18 -0
  107. data/doc/release_notes/1_1_4.md +6 -0
  108. data/doc/release_notes/1_1_5.md +12 -0
  109. data/doc/release_notes/1_2_0.md +49 -0
  110. data/doc/release_notes/1_2_1.md +6 -0
  111. data/doc/release_notes/1_2_2.md +10 -0
  112. data/doc/release_notes/1_2_3.md +16 -0
  113. data/doc/release_notes/1_2_4.md +8 -0
  114. data/doc/release_notes/1_2_5.md +7 -0
  115. data/doc/release_notes/1_2_6.md +13 -0
  116. data/doc/release_notes/1_3_0.md +18 -0
  117. data/doc/release_notes/1_3_1.md +17 -0
  118. data/doc/release_notes/1_3_2.md +6 -0
  119. data/doc/release_notes/1_3_3.md +5 -0
  120. data/doc/release_notes/1_3_4.md +6 -0
  121. data/doc/release_notes/1_4_0.md +43 -0
  122. data/doc/release_notes/1_4_1.md +19 -0
  123. data/doc/release_notes/1_4_2.md +20 -0
  124. data/doc/release_notes/1_4_3.md +11 -0
  125. data/doc/release_notes/1_4_4.md +14 -0
  126. data/doc/release_notes/1_5_0.md +126 -0
  127. data/doc/release_notes/1_5_1.md +6 -0
  128. data/doc/release_notes/1_6_0.md +50 -0
  129. data/doc/release_notes/1_6_1.md +17 -0
  130. data/doc/release_notes/1_6_2.md +11 -0
  131. data/lib/httpx/adapters/datadog.rb +359 -0
  132. data/lib/httpx/adapters/faraday.rb +303 -0
  133. data/lib/httpx/adapters/sentry.rb +121 -0
  134. data/lib/httpx/adapters/webmock.rb +175 -0
  135. data/lib/httpx/altsvc.rb +163 -0
  136. data/lib/httpx/base64.rb +27 -0
  137. data/lib/httpx/buffer.rb +61 -0
  138. data/lib/httpx/callbacks.rb +35 -0
  139. data/lib/httpx/chainable.rb +106 -0
  140. data/lib/httpx/connection/http1.rb +399 -0
  141. data/lib/httpx/connection/http2.rb +468 -0
  142. data/lib/httpx/connection.rb +954 -0
  143. data/lib/httpx/domain_name.rb +145 -0
  144. data/lib/httpx/errors.rb +111 -0
  145. data/lib/httpx/extensions.rb +59 -0
  146. data/lib/httpx/headers.rb +176 -0
  147. data/lib/httpx/io/ssl.rb +163 -0
  148. data/lib/httpx/io/tcp.rb +239 -0
  149. data/lib/httpx/io/udp.rb +62 -0
  150. data/lib/httpx/io/unix.rb +71 -0
  151. data/lib/httpx/io.rb +11 -0
  152. data/lib/httpx/loggable.rb +56 -0
  153. data/lib/httpx/options.rb +463 -0
  154. data/lib/httpx/parser/http1.rb +186 -0
  155. data/lib/httpx/plugins/auth/basic.rb +20 -0
  156. data/lib/httpx/plugins/auth/digest.rb +102 -0
  157. data/lib/httpx/plugins/auth/ntlm.rb +35 -0
  158. data/lib/httpx/plugins/auth/socks5.rb +22 -0
  159. data/lib/httpx/plugins/auth.rb +25 -0
  160. data/lib/httpx/plugins/aws_sdk_authentication.rb +111 -0
  161. data/lib/httpx/plugins/aws_sigv4.rb +239 -0
  162. data/lib/httpx/plugins/basic_auth.rb +29 -0
  163. data/lib/httpx/plugins/brotli.rb +50 -0
  164. data/lib/httpx/plugins/callbacks.rb +127 -0
  165. data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
  166. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
  167. data/lib/httpx/plugins/circuit_breaker.rb +147 -0
  168. data/lib/httpx/plugins/content_digest.rb +204 -0
  169. data/lib/httpx/plugins/cookies/cookie.rb +174 -0
  170. data/lib/httpx/plugins/cookies/jar.rb +95 -0
  171. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +143 -0
  172. data/lib/httpx/plugins/cookies.rb +107 -0
  173. data/lib/httpx/plugins/digest_auth.rb +67 -0
  174. data/lib/httpx/plugins/expect.rb +120 -0
  175. data/lib/httpx/plugins/fiber_concurrency.rb +195 -0
  176. data/lib/httpx/plugins/follow_redirects.rb +233 -0
  177. data/lib/httpx/plugins/grpc/call.rb +63 -0
  178. data/lib/httpx/plugins/grpc/grpc_encoding.rb +90 -0
  179. data/lib/httpx/plugins/grpc/message.rb +55 -0
  180. data/lib/httpx/plugins/grpc.rb +282 -0
  181. data/lib/httpx/plugins/h2c.rb +127 -0
  182. data/lib/httpx/plugins/internal_telemetry.rb +107 -0
  183. data/lib/httpx/plugins/ntlm_auth.rb +62 -0
  184. data/lib/httpx/plugins/oauth.rb +183 -0
  185. data/lib/httpx/plugins/persistent.rb +82 -0
  186. data/lib/httpx/plugins/proxy/http.rb +184 -0
  187. data/lib/httpx/plugins/proxy/socks4.rb +135 -0
  188. data/lib/httpx/plugins/proxy/socks5.rb +194 -0
  189. data/lib/httpx/plugins/proxy/ssh.rb +94 -0
  190. data/lib/httpx/plugins/proxy.rb +349 -0
  191. data/lib/httpx/plugins/push_promise.rb +81 -0
  192. data/lib/httpx/plugins/query.rb +35 -0
  193. data/lib/httpx/plugins/rate_limiter.rb +55 -0
  194. data/lib/httpx/plugins/response_cache/file_store.rb +140 -0
  195. data/lib/httpx/plugins/response_cache/store.rb +33 -0
  196. data/lib/httpx/plugins/response_cache.rb +333 -0
  197. data/lib/httpx/plugins/retries.rb +230 -0
  198. data/lib/httpx/plugins/ssrf_filter.rb +145 -0
  199. data/lib/httpx/plugins/stream.rb +183 -0
  200. data/lib/httpx/plugins/stream_bidi.rb +315 -0
  201. data/lib/httpx/plugins/upgrade/h2.rb +64 -0
  202. data/lib/httpx/plugins/upgrade.rb +86 -0
  203. data/lib/httpx/plugins/webdav.rb +86 -0
  204. data/lib/httpx/plugins/xml.rb +76 -0
  205. data/lib/httpx/pmatch_extensions.rb +33 -0
  206. data/lib/httpx/pool.rb +190 -0
  207. data/lib/httpx/punycode.rb +22 -0
  208. data/lib/httpx/request/body.rb +158 -0
  209. data/lib/httpx/request.rb +328 -0
  210. data/lib/httpx/resolver/entry.rb +30 -0
  211. data/lib/httpx/resolver/https.rb +256 -0
  212. data/lib/httpx/resolver/multi.rb +102 -0
  213. data/lib/httpx/resolver/native.rb +547 -0
  214. data/lib/httpx/resolver/resolver.rb +173 -0
  215. data/lib/httpx/resolver/system.rb +255 -0
  216. data/lib/httpx/resolver.rb +189 -0
  217. data/lib/httpx/response/body.rb +242 -0
  218. data/lib/httpx/response/buffer.rb +115 -0
  219. data/lib/httpx/response.rb +304 -0
  220. data/lib/httpx/selector.rb +282 -0
  221. data/lib/httpx/session.rb +612 -0
  222. data/lib/httpx/session_extensions.rb +30 -0
  223. data/lib/httpx/timers.rb +133 -0
  224. data/lib/httpx/transcoder/body.rb +43 -0
  225. data/lib/httpx/transcoder/chunker.rb +115 -0
  226. data/lib/httpx/transcoder/deflate.rb +37 -0
  227. data/lib/httpx/transcoder/form.rb +68 -0
  228. data/lib/httpx/transcoder/gzip.rb +71 -0
  229. data/lib/httpx/transcoder/json.rb +71 -0
  230. data/lib/httpx/transcoder/multipart/decoder.rb +141 -0
  231. data/lib/httpx/transcoder/multipart/encoder.rb +120 -0
  232. data/lib/httpx/transcoder/multipart/mime_type_detector.rb +78 -0
  233. data/lib/httpx/transcoder/multipart/part.rb +35 -0
  234. data/lib/httpx/transcoder/multipart.rb +31 -0
  235. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  236. data/lib/httpx/transcoder/utils/deflater.rb +75 -0
  237. data/lib/httpx/transcoder.rb +91 -0
  238. data/lib/httpx/utils.rb +75 -0
  239. data/lib/httpx/version.rb +5 -0
  240. data/lib/httpx.rb +66 -0
  241. data/sig/altsvc.rbs +33 -0
  242. data/sig/buffer.rbs +27 -0
  243. data/sig/callbacks.rbs +15 -0
  244. data/sig/chainable.rbs +55 -0
  245. data/sig/connection/http1.rbs +85 -0
  246. data/sig/connection/http2.rbs +116 -0
  247. data/sig/connection.rbs +169 -0
  248. data/sig/domain_name.rbs +17 -0
  249. data/sig/errors.rbs +69 -0
  250. data/sig/headers.rbs +49 -0
  251. data/sig/httpx.rbs +27 -0
  252. data/sig/io/ssl.rbs +27 -0
  253. data/sig/io/tcp.rbs +72 -0
  254. data/sig/io/udp.rbs +25 -0
  255. data/sig/io/unix.rbs +26 -0
  256. data/sig/io.rbs +3 -0
  257. data/sig/loggable.rbs +17 -0
  258. data/sig/options.rbs +202 -0
  259. data/sig/parser/http1.rbs +59 -0
  260. data/sig/plugins/auth/basic.rbs +17 -0
  261. data/sig/plugins/auth/digest.rbs +25 -0
  262. data/sig/plugins/auth/ntlm.rbs +20 -0
  263. data/sig/plugins/auth/socks5.rbs +18 -0
  264. data/sig/plugins/auth.rbs +13 -0
  265. data/sig/plugins/aws_sdk_authentication.rbs +43 -0
  266. data/sig/plugins/aws_sigv4.rbs +78 -0
  267. data/sig/plugins/basic_auth.rbs +15 -0
  268. data/sig/plugins/brotli.rbs +22 -0
  269. data/sig/plugins/callbacks.rbs +38 -0
  270. data/sig/plugins/circuit_breaker.rbs +71 -0
  271. data/sig/plugins/compression.rbs +57 -0
  272. data/sig/plugins/content_digest.rbs +51 -0
  273. data/sig/plugins/cookies/cookie.rbs +55 -0
  274. data/sig/plugins/cookies/jar.rbs +26 -0
  275. data/sig/plugins/cookies/set_cookie_parser.rbs +22 -0
  276. data/sig/plugins/cookies.rbs +28 -0
  277. data/sig/plugins/digest_auth.rbs +21 -0
  278. data/sig/plugins/expect.rbs +15 -0
  279. data/sig/plugins/fiber_concurrency.rbs +51 -0
  280. data/sig/plugins/follow_redirects.rbs +47 -0
  281. data/sig/plugins/grpc/call.rbs +23 -0
  282. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  283. data/sig/plugins/grpc/message.rbs +17 -0
  284. data/sig/plugins/grpc.rbs +65 -0
  285. data/sig/plugins/h2c.rbs +27 -0
  286. data/sig/plugins/ntlm_auth.rbs +21 -0
  287. data/sig/plugins/oauth.rbs +68 -0
  288. data/sig/plugins/persistent.rbs +14 -0
  289. data/sig/plugins/proxy/http.rbs +30 -0
  290. data/sig/plugins/proxy/socks4.rbs +37 -0
  291. data/sig/plugins/proxy/socks5.rbs +49 -0
  292. data/sig/plugins/proxy/ssh.rbs +18 -0
  293. data/sig/plugins/proxy.rbs +70 -0
  294. data/sig/plugins/push_promise.rbs +23 -0
  295. data/sig/plugins/query.rbs +18 -0
  296. data/sig/plugins/rate_limiter.rbs +13 -0
  297. data/sig/plugins/response_cache/file_store.rbs +19 -0
  298. data/sig/plugins/response_cache/store.rbs +13 -0
  299. data/sig/plugins/response_cache.rbs +86 -0
  300. data/sig/plugins/retries.rbs +66 -0
  301. data/sig/plugins/ssrf_filter.rbs +26 -0
  302. data/sig/plugins/stream.rbs +54 -0
  303. data/sig/plugins/stream_bidi.rbs +68 -0
  304. data/sig/plugins/upgrade/h2.rbs +9 -0
  305. data/sig/plugins/upgrade.rbs +29 -0
  306. data/sig/plugins/webdav.rbs +23 -0
  307. data/sig/plugins/xml.rbs +37 -0
  308. data/sig/pool.rbs +51 -0
  309. data/sig/punycode.rbs +5 -0
  310. data/sig/request/body.rbs +34 -0
  311. data/sig/request.rbs +88 -0
  312. data/sig/resolver/entry.rbs +13 -0
  313. data/sig/resolver/https.rbs +45 -0
  314. data/sig/resolver/multi.rbs +32 -0
  315. data/sig/resolver/native.rbs +74 -0
  316. data/sig/resolver/resolver.rbs +64 -0
  317. data/sig/resolver/system.rbs +34 -0
  318. data/sig/resolver.rbs +48 -0
  319. data/sig/response/body.rbs +52 -0
  320. data/sig/response/buffer.rbs +23 -0
  321. data/sig/response.rbs +103 -0
  322. data/sig/selector.rbs +68 -0
  323. data/sig/session.rbs +104 -0
  324. data/sig/timers.rbs +54 -0
  325. data/sig/transcoder/body.rbs +24 -0
  326. data/sig/transcoder/chunker.rbs +49 -0
  327. data/sig/transcoder/deflate.rbs +12 -0
  328. data/sig/transcoder/form.rbs +34 -0
  329. data/sig/transcoder/gzip.rbs +27 -0
  330. data/sig/transcoder/json.rbs +28 -0
  331. data/sig/transcoder/multipart.rbs +103 -0
  332. data/sig/transcoder/utils/body_reader.rbs +15 -0
  333. data/sig/transcoder/utils/deflater.rbs +28 -0
  334. data/sig/transcoder.rbs +43 -0
  335. data/sig/utils.rbs +19 -0
  336. metadata +518 -0
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ class Socks5Error < ProxyError; end
5
+
6
+ module Plugins
7
+ module Proxy
8
+ module Socks5
9
+ VERSION = 5
10
+ NOAUTH = 0
11
+ PASSWD = 2
12
+ NONE = 0xff
13
+ CONNECT = 1
14
+ IPV4 = 1
15
+ DOMAIN = 3
16
+ IPV6 = 4
17
+ SUCCESS = 0
18
+
19
+ Error = Socks5Error
20
+
21
+ class << self
22
+ def load_dependencies(*)
23
+ require_relative "../auth/socks5"
24
+ end
25
+
26
+ def extra_options(options)
27
+ options.merge(supported_proxy_protocols: options.supported_proxy_protocols + %w[socks5])
28
+ end
29
+ end
30
+
31
+ module ConnectionMethods
32
+ def call
33
+ super
34
+
35
+ return unless @options.proxy && @options.proxy.uri.scheme == "socks5"
36
+
37
+ case @state
38
+ when :connecting,
39
+ :negotiating,
40
+ :authenticating
41
+ consume
42
+ end
43
+ end
44
+
45
+ def connecting?
46
+ super || @state == :authenticating || @state == :negotiating
47
+ end
48
+
49
+ def interests
50
+ if @state == :connecting || @state == :authenticating || @state == :negotiating
51
+ return @write_buffer.empty? ? :r : :w
52
+ end
53
+
54
+ super
55
+ end
56
+
57
+ private
58
+
59
+ def handle_transition(nextstate)
60
+ return super unless @options.proxy && @options.proxy.uri.scheme == "socks5"
61
+
62
+ case nextstate
63
+ when :connecting
64
+ return unless @state == :idle
65
+
66
+ @io.connect
67
+ return unless @io.connected?
68
+
69
+ @write_buffer << Packet.negotiate(@options.proxy)
70
+ __socks5_proxy_connect
71
+ when :authenticating
72
+ return unless @state == :connecting
73
+
74
+ @write_buffer << Packet.authenticate(@options.proxy)
75
+ when :negotiating
76
+ return unless @state == :connecting || @state == :authenticating
77
+
78
+ req = @pending.first
79
+ request_uri = req.uri
80
+ @write_buffer << Packet.connect(request_uri)
81
+ when :connected
82
+ return unless @state == :negotiating
83
+
84
+ @parser = nil
85
+ end
86
+ log(level: 1) { "SOCKS5: #{nextstate}: #{@write_buffer.to_s.inspect}" } unless nextstate == :open
87
+ super
88
+ end
89
+
90
+ def __socks5_proxy_connect
91
+ @parser = SocksParser.new(@write_buffer, @options)
92
+ @parser.on(:packet, &method(:__socks5_on_packet))
93
+ transition(:negotiating)
94
+ end
95
+
96
+ def __socks5_on_packet(packet)
97
+ case @state
98
+ when :connecting
99
+ version, method = packet.unpack("CC")
100
+ __socks5_check_version(version)
101
+ case method
102
+ when PASSWD
103
+ transition(:authenticating)
104
+ nil
105
+ when NONE
106
+ __on_socks5_error("no supported authorization methods")
107
+ else
108
+ transition(:negotiating)
109
+ end
110
+ when :authenticating
111
+ _, status = packet.unpack("CC")
112
+ return transition(:negotiating) if status == SUCCESS
113
+
114
+ __on_socks5_error("socks authentication error: #{status}")
115
+ when :negotiating
116
+ version, reply, = packet.unpack("CC")
117
+ __socks5_check_version(version)
118
+ __on_socks5_error("socks5 negotiation error: #{reply}") unless reply == SUCCESS
119
+ req = @pending.first
120
+ request_uri = req.uri
121
+ @io = ProxySSL.new(@io, request_uri, @options) if request_uri.scheme == "https"
122
+ transition(:connected)
123
+ throw(:called)
124
+ end
125
+ end
126
+
127
+ def __socks5_check_version(version)
128
+ __on_socks5_error("invalid SOCKS version (#{version})") if version != 5
129
+ end
130
+
131
+ def __on_socks5_error(message)
132
+ ex = Error.new(message)
133
+ ex.set_backtrace(caller)
134
+ on_error(ex)
135
+ throw(:called)
136
+ end
137
+ end
138
+
139
+ class SocksParser
140
+ include HTTPX::Callbacks
141
+
142
+ def initialize(buffer, options)
143
+ @buffer = buffer
144
+ @options = options
145
+ end
146
+
147
+ def close; end
148
+
149
+ def consume(*); end
150
+
151
+ def empty?
152
+ true
153
+ end
154
+
155
+ def <<(packet)
156
+ emit(:packet, packet)
157
+ end
158
+ end
159
+
160
+ module Packet
161
+ module_function
162
+
163
+ def negotiate(parameters)
164
+ methods = [NOAUTH]
165
+ methods << PASSWD if parameters.can_authenticate?
166
+ methods.unshift(methods.size)
167
+ methods.unshift(VERSION)
168
+ methods.pack("C*")
169
+ end
170
+
171
+ def authenticate(parameters)
172
+ parameters.authenticate
173
+ end
174
+
175
+ def connect(uri)
176
+ packet = [VERSION, CONNECT, 0].pack("C*")
177
+ begin
178
+ ip = IPAddr.new(uri.host)
179
+
180
+ ipcode = ip.ipv6? ? IPV6 : IPV4
181
+
182
+ packet << [ipcode].pack("C") << ip.hton
183
+ rescue IPAddr::InvalidAddressError
184
+ packet << [DOMAIN, uri.host.bytesize, uri.host].pack("CCA*")
185
+ end
186
+ packet << [uri.port].pack("n")
187
+ packet
188
+ end
189
+ end
190
+ end
191
+ end
192
+ register_plugin :"proxy/socks5", Proxy::Socks5
193
+ end
194
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httpx/plugins/proxy"
4
+
5
+ module HTTPX
6
+ module Plugins
7
+ module Proxy
8
+ module SSH
9
+ class << self
10
+ def load_dependencies(*)
11
+ require "net/ssh/gateway"
12
+ end
13
+ end
14
+
15
+ module OptionsMethods
16
+ private
17
+
18
+ def option_proxy(value)
19
+ Hash[value]
20
+ end
21
+ end
22
+
23
+ module InstanceMethods
24
+ def request(*args, **options)
25
+ raise ArgumentError, "must perform at least one request" if args.empty?
26
+
27
+ requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
28
+
29
+ request = requests.first or return super
30
+
31
+ request_options = request.options
32
+
33
+ return super unless request_options.proxy
34
+
35
+ ssh_options = request_options.proxy
36
+ ssh_uris = ssh_options.delete(:uri)
37
+ ssh_uri = URI.parse(ssh_uris.shift)
38
+
39
+ return super unless ssh_uri.scheme == "ssh"
40
+
41
+ ssh_username = ssh_options.delete(:username)
42
+ ssh_options[:port] ||= ssh_uri.port || 22
43
+ if request_options.debug
44
+ ssh_options[:verbose] = request_options.debug_level == 2 ? :debug : :info
45
+ end
46
+
47
+ request_uri = URI(requests.first.uri)
48
+ @_gateway = Net::SSH::Gateway.new(ssh_uri.host, ssh_username, ssh_options)
49
+ begin
50
+ @_gateway.open(request_uri.host, request_uri.port) do |local_port|
51
+ io = build_gateway_socket(local_port, request_uri, request_options)
52
+ super(*args, **options.merge(io: io))
53
+ end
54
+ ensure
55
+ @_gateway.shutdown!
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def build_gateway_socket(port, request_uri, options)
62
+ case request_uri.scheme
63
+ when "https"
64
+ ctx = OpenSSL::SSL::SSLContext.new
65
+ ctx_options = SSL::TLS_OPTIONS.merge(options.ssl)
66
+ ctx.set_params(ctx_options) unless ctx_options.empty?
67
+ sock = TCPSocket.open("localhost", port)
68
+ io = OpenSSL::SSL::SSLSocket.new(sock, ctx)
69
+ io.hostname = request_uri.host
70
+ io.sync_close = true
71
+ io.connect
72
+ io.post_connection_check(request_uri.host) if ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
73
+ io
74
+ when "http"
75
+ TCPSocket.open("localhost", port)
76
+ else
77
+ raise TypeError, "unexpected scheme: #{request_uri.scheme}"
78
+ end
79
+ end
80
+ end
81
+
82
+ module ConnectionMethods
83
+ # should not coalesce connections here, as the IP is the IP of the proxy
84
+ def coalescable?(*)
85
+ return super unless @options.proxy
86
+
87
+ false
88
+ end
89
+ end
90
+ end
91
+ end
92
+ register_plugin :"proxy/ssh", Proxy::SSH
93
+ end
94
+ end
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ class ProxyError < ConnectionError; end
5
+
6
+ module Plugins
7
+ #
8
+ # This plugin adds support for proxies. It ships with support for:
9
+ #
10
+ # * HTTP proxies
11
+ # * HTTPS proxies
12
+ # * Socks4/4a proxies
13
+ # * Socks5 proxies
14
+ #
15
+ # https://gitlab.com/os85/httpx/wikis/Proxy
16
+ #
17
+ module Proxy
18
+ class ProxyConnectionError < ProxyError; end
19
+
20
+ PROXY_ERRORS = [TimeoutError, IOError, SystemCallError, Error].freeze
21
+
22
+ class << self
23
+ def configure(klass)
24
+ klass.plugin(:"proxy/http")
25
+ klass.plugin(:"proxy/socks4")
26
+ klass.plugin(:"proxy/socks5")
27
+ end
28
+
29
+ def extra_options(options)
30
+ options.merge(supported_proxy_protocols: [])
31
+ end
32
+
33
+ def subplugins
34
+ {
35
+ retries: ProxyRetries,
36
+ }
37
+ end
38
+ end
39
+
40
+ class Parameters
41
+ attr_reader :uri, :username, :password, :scheme, :no_proxy
42
+
43
+ def initialize(uri: nil, scheme: nil, username: nil, password: nil, no_proxy: nil, **extra)
44
+ @no_proxy = Array(no_proxy) if no_proxy
45
+ @uris = Array(uri)
46
+ uri = @uris.first
47
+
48
+ @username = username
49
+ @password = password
50
+
51
+ @ns = 0
52
+
53
+ if uri
54
+ @uri = uri.is_a?(URI::Generic) ? uri : URI(uri)
55
+ @username ||= @uri.user
56
+ @password ||= @uri.password
57
+ end
58
+
59
+ @scheme = scheme
60
+
61
+ return unless @uri && @username && @password
62
+
63
+ @authenticator = nil
64
+ @scheme ||= infer_default_auth_scheme(@uri)
65
+
66
+ return unless @scheme
67
+
68
+ @authenticator = load_authenticator(@scheme, @username, @password, **extra)
69
+ end
70
+
71
+ def shift
72
+ # TODO: this operation must be synchronized
73
+ @ns += 1
74
+ @uri = @uris[@ns]
75
+
76
+ return unless @uri
77
+
78
+ @uri = URI(@uri) unless @uri.is_a?(URI::Generic)
79
+
80
+ scheme = infer_default_auth_scheme(@uri)
81
+
82
+ return unless scheme != @scheme
83
+
84
+ @scheme = scheme
85
+ @username = username || @uri.user
86
+ @password = password || @uri.password
87
+ @authenticator = load_authenticator(scheme, @username, @password)
88
+ end
89
+
90
+ def can_authenticate?(*args)
91
+ return false unless @authenticator
92
+
93
+ @authenticator.can_authenticate?(*args)
94
+ end
95
+
96
+ def authenticate(*args)
97
+ return unless @authenticator
98
+
99
+ @authenticator.authenticate(*args)
100
+ end
101
+
102
+ def ==(other)
103
+ case other
104
+ when Parameters
105
+ @uri == other.uri &&
106
+ @username == other.username &&
107
+ @password == other.password &&
108
+ @scheme == other.scheme
109
+ when URI::Generic, String
110
+ proxy_uri = @uri.dup
111
+ proxy_uri.user = @username
112
+ proxy_uri.password = @password
113
+ other_uri = other.is_a?(URI::Generic) ? other : URI.parse(other)
114
+ proxy_uri == other_uri
115
+ else
116
+ super
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def infer_default_auth_scheme(uri)
123
+ case uri.scheme
124
+ when "socks5"
125
+ uri.scheme
126
+ when "http", "https"
127
+ "basic"
128
+ end
129
+ end
130
+
131
+ def load_authenticator(scheme, username, password, **extra)
132
+ auth_scheme = scheme.to_s.capitalize
133
+
134
+ require_relative "auth/#{scheme}" unless defined?(Authentication) && Authentication.const_defined?(auth_scheme, false)
135
+
136
+ Authentication.const_get(auth_scheme).new(username, password, **extra)
137
+ end
138
+ end
139
+
140
+ # adds support for the following options:
141
+ #
142
+ # :proxy :: proxy options defining *:uri*, *:username*, *:password* or
143
+ # *:scheme* (i.e. <tt>{ uri: "http://proxy" }</tt>)
144
+ module OptionsMethods
145
+ private
146
+
147
+ def option_proxy(value)
148
+ value.is_a?(Parameters) ? value : Parameters.new(**Hash[value])
149
+ end
150
+
151
+ def option_supported_proxy_protocols(value)
152
+ raise TypeError, ":supported_proxy_protocols must be an Array" unless value.is_a?(Array)
153
+
154
+ value.map(&:to_s)
155
+ end
156
+ end
157
+
158
+ module InstanceMethods
159
+ def find_connection(request_uri, selector, options)
160
+ return super unless options.respond_to?(:proxy)
161
+
162
+ if (next_proxy = request_uri.find_proxy)
163
+ return super(request_uri, selector, options.merge(proxy: Parameters.new(uri: next_proxy)))
164
+ end
165
+
166
+ proxy = options.proxy
167
+
168
+ return super unless proxy
169
+
170
+ next_proxy = proxy.uri
171
+
172
+ raise ProxyError, "Failed to connect to proxy" unless next_proxy
173
+
174
+ raise ProxyError,
175
+ "#{next_proxy.scheme}: unsupported proxy protocol" unless options.supported_proxy_protocols.include?(next_proxy.scheme)
176
+
177
+ if (no_proxy = proxy.no_proxy)
178
+ no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
179
+
180
+ # TODO: setting proxy to nil leaks the connection object in the pool
181
+ return super(request_uri, selector, options.merge(proxy: nil)) unless URI::Generic.use_proxy?(request_uri.host, next_proxy.host,
182
+ next_proxy.port, no_proxy)
183
+ end
184
+
185
+ super(request_uri, selector, options.merge(proxy: proxy))
186
+ end
187
+
188
+ private
189
+
190
+ def fetch_response(request, selector, options)
191
+ response = request.response # in case it goes wrong later
192
+
193
+ begin
194
+ response = super
195
+
196
+ if response.is_a?(ErrorResponse) && proxy_error?(request, response, options)
197
+ options.proxy.shift
198
+
199
+ # return last error response if no more proxies to try
200
+ return response if options.proxy.uri.nil?
201
+
202
+ log { "failed connecting to proxy, trying next..." }
203
+ request.transition(:idle)
204
+ send_request(request, selector, options)
205
+ return
206
+ end
207
+ response
208
+ rescue ProxyError
209
+ # may happen if coupled with retries, and there are no more proxies to try, in which case
210
+ # it'll end up here
211
+ response
212
+ end
213
+ end
214
+
215
+ def proxy_error?(_request, response, options)
216
+ return false unless options.proxy
217
+
218
+ error = response.error
219
+ case error
220
+ when NativeResolveError
221
+ proxy_uri = URI(options.proxy.uri)
222
+
223
+ unresolved_host = error.host
224
+
225
+ # failed resolving proxy domain
226
+ unresolved_host == proxy_uri.host
227
+ when ResolveError
228
+ proxy_uri = URI(options.proxy.uri)
229
+
230
+ error.message.end_with?(proxy_uri.to_s)
231
+ when ProxyConnectionError
232
+ # timeout errors connecting to proxy
233
+ true
234
+ else
235
+ false
236
+ end
237
+ end
238
+ end
239
+
240
+ module ConnectionMethods
241
+ using URIExtensions
242
+
243
+ def initialize(*)
244
+ super
245
+ return unless @options.proxy
246
+
247
+ # redefining the connection origin as the proxy's URI,
248
+ # as this will be used as the tcp peer ip.
249
+ @proxy_uri = URI(@options.proxy.uri)
250
+ end
251
+
252
+ def peer
253
+ @proxy_uri || super
254
+ end
255
+
256
+ def connecting?
257
+ return super unless @options.proxy
258
+
259
+ super || @state == :connecting || @state == :connected
260
+ end
261
+
262
+ def call
263
+ super
264
+
265
+ return unless @options.proxy
266
+
267
+ case @state
268
+ when :connecting
269
+ consume
270
+ end
271
+ rescue *PROXY_ERRORS => e
272
+ if connecting?
273
+ error = ProxyConnectionError.new(e.message)
274
+ error.set_backtrace(e.backtrace)
275
+ raise error
276
+ end
277
+
278
+ raise e
279
+ end
280
+
281
+ def reset
282
+ return super unless @options.proxy
283
+
284
+ @state = :open
285
+
286
+ super
287
+ # emit(:close)
288
+ end
289
+
290
+ private
291
+
292
+ def initialize_type(uri, options)
293
+ return super unless options.proxy
294
+
295
+ "tcp"
296
+ end
297
+
298
+ def connect
299
+ return super unless @options.proxy
300
+
301
+ case @state
302
+ when :idle
303
+ transition(:connecting)
304
+ when :connected
305
+ transition(:open)
306
+ end
307
+ end
308
+
309
+ def handle_transition(nextstate)
310
+ return super unless @options.proxy
311
+
312
+ case nextstate
313
+ when :closing
314
+ # this is a hack so that we can use the super method
315
+ # and it'll think that the current state is open
316
+ @state = :open if @state == :connecting
317
+ end
318
+ super
319
+ end
320
+
321
+ def purge_after_closed
322
+ super
323
+ @io = @io.proxy_io if @io.respond_to?(:proxy_io)
324
+ end
325
+ end
326
+
327
+ module ProxyRetries
328
+ module InstanceMethods
329
+ def retryable_error?(ex)
330
+ super || ex.is_a?(ProxyConnectionError)
331
+ end
332
+ end
333
+ end
334
+ end
335
+ register_plugin :proxy, Proxy
336
+ end
337
+
338
+ class ProxySSL < SSL
339
+ attr_reader :proxy_io
340
+
341
+ def initialize(tcp, request_uri, options)
342
+ @proxy_io = tcp
343
+ @io = tcp.to_io
344
+ super(request_uri, tcp.addresses, options)
345
+ @hostname = request_uri.host
346
+ @state = :connected
347
+ end
348
+ end
349
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin adds support for HTTP/2 Push responses.
7
+ #
8
+ # In order to benefit from this, requests are sent one at a time, so that
9
+ # no push responses are received after corresponding request has been sent.
10
+ #
11
+ # https://gitlab.com/os85/httpx/wikis/Server-Push
12
+ #
13
+ module PushPromise
14
+ def self.extra_options(options)
15
+ options.merge(http2_settings: { settings_enable_push: 1 },
16
+ max_concurrent_requests: 1)
17
+ end
18
+
19
+ module ResponseMethods
20
+ def pushed?
21
+ @__pushed
22
+ end
23
+
24
+ def mark_as_pushed!
25
+ @__pushed = true
26
+ end
27
+ end
28
+
29
+ module InstanceMethods
30
+ private
31
+
32
+ def promise_headers
33
+ @promise_headers ||= {}
34
+ end
35
+
36
+ def on_promise(parser, stream)
37
+ stream.on(:promise_headers) do |h|
38
+ __on_promise_request(parser, stream, h)
39
+ end
40
+ stream.on(:headers) do |h|
41
+ __on_promise_response(parser, stream, h)
42
+ end
43
+ end
44
+
45
+ def __on_promise_request(parser, stream, h)
46
+ log(level: 1, color: :yellow) do
47
+ # :nocov:
48
+ h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
49
+ # :nocov:
50
+ end
51
+ headers = @options.headers_class.new(h)
52
+ path = headers[":path"]
53
+ authority = headers[":authority"]
54
+
55
+ request = parser.pending.find { |r| r.authority == authority && r.path == path }
56
+ if request
57
+ request.merge_headers(headers)
58
+ promise_headers[stream] = request
59
+ parser.pending.delete(request)
60
+ parser.streams[request] = stream
61
+ request.transition(:done)
62
+ else
63
+ stream.refuse
64
+ end
65
+ end
66
+
67
+ def __on_promise_response(parser, stream, h)
68
+ request = promise_headers.delete(stream)
69
+ return unless request
70
+
71
+ parser.__send__(:on_stream_headers, stream, request, h)
72
+ response = request.response
73
+ response.mark_as_pushed!
74
+ stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])
75
+ stream.on(:close, &parser.method(:on_stream_close).curry(3)[stream, request])
76
+ end
77
+ end
78
+ end
79
+ register_plugin(:push_promise, PushPromise)
80
+ end
81
+ end