http_mimic 0.4.0 → 0.5.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +117 -33
- data/lib/http_mimic/command_builder.rb +17 -1
- data/lib/http_mimic/configuration.rb +33 -0
- data/lib/http_mimic/downloader.rb +97 -0
- data/lib/http_mimic/module_methods.rb +39 -0
- data/lib/http_mimic/obscura.rb +158 -0
- data/lib/http_mimic/proxy_pool.rb +224 -0
- data/lib/http_mimic/request.rb +127 -11
- data/lib/http_mimic/spa_detector.rb +95 -0
- data/lib/http_mimic/version.rb +1 -1
- data/lib/http_mimic/waf/akamai_solver.rb +97 -39
- data/lib/http_mimic/waf/detector.rb +12 -0
- data/lib/http_mimic/waf/google_solver.rb +124 -0
- data/lib/http_mimic/waf.rb +3 -0
- data/lib/http_mimic.rb +27 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b0b4192818428818cca7cde26b4ce90d6fa847db6e80e2b63c163dd8171bdd9
|
|
4
|
+
data.tar.gz: de6a71df66c710a30e7cb8fcbe6976653ec6242a6d29fd6959016513da4513be
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b4ba6f1355da36e9f1064e62b1b988200f483a58b49524e3dd5350b0c99660d0b93ab650fc9ea8b3b7aa2204f41b7e2ca51c6b439128c267d7660be77b2b644
|
|
7
|
+
data.tar.gz: dd25b3fbf25dca0d935b910cb2b0981ab77d9dbfe45cfa5b522a99bbca7081016e6323af3fc59c389ef925c6df0b4d0bbf7321c7a93df5154d0d41e328962d76
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,70 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.3] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Automatic Free Proxy Pool (`auto_proxy`)**:
|
|
12
|
+
- Built-in `HttpMimic::ProxyPool` automatically gathers and caches hundreds of public HTTP proxies from maintained sources (`monosans`, `proxyscrape`, `TheSpeedX`).
|
|
13
|
+
- Disabled by default (`auto_proxy: false`) with per-request and global configuration opt-in.
|
|
14
|
+
- Transparent proxy failure retry: automatically detects dead proxies (exit codes 5, 7, 28, 35, 56 or connection errors), marks them dead in the pool, and transparently retries with fresh proxies up to `proxy_retries` (default: 3).
|
|
15
|
+
- Explicit manual `:proxy` option always takes precedence over `auto_proxy`.
|
|
16
|
+
- Seamless integration with Obscura and WAF solver handshakes.
|
|
17
|
+
- Added DSL helpers (`HttpMimic.auto_proxy`, `HttpMimic.proxy_pool`, `HttpMimic.refresh_proxies!`).
|
|
18
|
+
- **Streamlined Documentation**:
|
|
19
|
+
- Refined README with concise, high-level highlights focusing on core anti-detect capabilities and developer experience.
|
|
20
|
+
|
|
21
|
+
## [0.5.2] - 2026-09-05
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **Anti-Poisoning Failure Protection for CookieStore (`persist_on_failure`, `clear_on_failure`)**:
|
|
25
|
+
- `persist_on_failure` (default: `false`): Ensures failed/blocked requests (403, 401, retry statuses, or WAF challenge pages) do not save invalid or bot-flagged cookies to disk.
|
|
26
|
+
- `clear_on_failure` (default: `true`): Automatically purges stored host cookies when a request fails verification or is blocked by WAF, preventing poisoned sessions from breaking subsequent requests.
|
|
27
|
+
- Added class-level DSL and configuration support (`HttpMimic.persist_on_failure`, `HttpMimic.clear_on_failure`).
|
|
28
|
+
- **Two-Phase Telemetry Handshake in `AkamaiSolver`**:
|
|
29
|
+
- Automatic multi-round telemetry loop sending second-stage interaction sensor posts after initial cookie acquisition to flip `_abck` tokens from `~-1~` to verified `~0~`.
|
|
30
|
+
- **Intelligent Profile Fallback & WAF Protection**:
|
|
31
|
+
- `:auto` mode now rotates between distinct browser engines (`:impersonate` [Chrome], `:android`, `:ios`, `:firefox`) and skips plain `:curl` fallback on WAF targets to avoid IP flagging.
|
|
32
|
+
- Added `:firefox` and `:safari` profile support in `CommandBuilder`.
|
|
33
|
+
- Added Best Response Preservation: prevents successful/200 progress from being wiped out by later failed fallback attempts.
|
|
34
|
+
|
|
35
|
+
## [0.5.1] - 2026-08-30
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- **Two-Phase Cookie & State Pipeline (Tier 1 ➔ Tier 2)**:
|
|
39
|
+
- Automatically forward validated cookies (`NID`, `AEC`, `_abck`, `cf_clearance`) and custom headers from Tier 1 HTTP / WAF resolution directly into Obscura (`--cookie` flag) during `auto_render_spa` transitions.
|
|
40
|
+
- **Obscura Stealth BoringSSL Integration**:
|
|
41
|
+
- Downloader now defaults to `*-stealth.tar.gz` release assets, equipping Obscura with native BoringSSL Chrome TLS / JA4 fingerprint impersonation.
|
|
42
|
+
- **Upgraded Obscura to `anxgang/obscura` v0.2.3**:
|
|
43
|
+
- WebGL context, `REAL_FONT_METRICS` font measurements, and persistent Web Worker `importScripts` support.
|
|
44
|
+
|
|
45
|
+
## [0.5.0] - 2026-08-30
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- **Tier 2: Obscura Headless SPA Rendering Support**:
|
|
49
|
+
- Native integration with [`h4ckf0r0day/obscura`](https://github.com/h4ckf0r0day/obscura)—a lightweight (<100MB) Rust + V8 headless browser engine with built-in stealth anti-detection.
|
|
50
|
+
- On-demand automatic driver management (`HttpMimic.download_obscura!`, `HttpMimic.obscura_installed?`, `HttpMimic.obscura_path`) across macOS (ARM64/x86_64), Linux (x86_64/aarch64), and Windows.
|
|
51
|
+
- New rendering helpers: `HttpMimic.render(url, options = {})` and `HttpMimic.spa(url, options = {})`.
|
|
52
|
+
- **Automatic SPA Detection & Transition (`auto_render_spa`)**:
|
|
53
|
+
- Intelligent `HttpMimic::SpaDetector` detects unhydrated client-side SPA shells (React `#root`, Vue `#app`, Angular `<app-root>`, Google Dynamic SERP, `<noscript>` prompts).
|
|
54
|
+
- When `auto_render_spa: true` (or `config.auto_render_spa = true`) is enabled, `HttpMimic.get` automatically transitions from Tier 1 (HTTP) to Tier 2 (Obscura) to render the full dynamic DOM.
|
|
55
|
+
- Configurable options: `:wait_until` (`networkidle0`, `domcontentloaded`, `load`), `:timeout`, `:proxy`, `:eval`, `:dump`, `:selector`, `:user_agent`, and `:stealth`.
|
|
56
|
+
|
|
57
|
+
## [0.4.1] - 2026-08-30
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- **Google Search Guard & BotGuard VM Detection**:
|
|
61
|
+
- Added `HttpMimic::Waf::GoogleSolver` and `HttpMimic::Waf::Detector.google?` for detecting Google Search Guard (`knitsail`, `enablejs`, `SG_SS`) challenges.
|
|
62
|
+
- Added automatic Google Search referral context (`Referer: https://www.google.com/`, `sec-fetch-site: same-origin`) in `CommandBuilder#apply_navigation_headers`.
|
|
63
|
+
- **Authentic Hardware & WebGL Pipeline Emulation in QuickJS Context**:
|
|
64
|
+
- Full WebGL shader compilation and rasterization pipeline (`createShader`, `compileShader`, `getShaderPrecisionFormat`, `createProgram`, `linkProgram`, `readPixels` simulated gradient buffer).
|
|
65
|
+
- Dynamic font measurement (`REAL_FONT_METRICS`) with exact macOS Chrome `offsetWidth` / `offsetHeight` dimensions across 17 font families and 7 font sizes.
|
|
66
|
+
- Authentic Chromium `OfflineAudioContext` audio buffer rendering curve.
|
|
67
|
+
- Physics-based Cubic Bézier Spline mouse movement simulation with velocity decay, natural micro-jitter, and complete click event chains.
|
|
68
|
+
- Polyfills for `trustedTypes`, `sessionStorage`, `localStorage`, and `document.currentScript`.
|
|
69
|
+
- **Automatic Response Decompression**:
|
|
70
|
+
- Added `--compressed` flag to CommandBuilder to ensure automatic transparent decompression of `gzip`, `deflate`, `br` (Brotli), and `zstd` payloads.
|
|
71
|
+
|
|
8
72
|
## [0.4.0] - 2026-08-30
|
|
9
73
|
|
|
10
74
|
### Added
|
data/README.md
CHANGED
|
@@ -1,41 +1,27 @@
|
|
|
1
|
-
# HttpMimic
|
|
1
|
+
# HttpMimic 🎭
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
> **Next-Generation Anti-Detect HTTP Client for Ruby**
|
|
4
|
+
> An elegant, high-performance HTTP client combining authentic browser TLS/JA4 fingerprint impersonation, built-in anti-bot challenge solving, lightweight headless SPA rendering, and an automatic free proxy pool into an intuitive HTTParty-style API.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
`http_mimic` provides a complete, modern toolchain for accessing protected endpoints and web scraping:
|
|
7
|
+
- **🎭 Authentic Browser Fingerprints**: Simulates authentic Chrome, Safari (macOS & iOS), Android, and Firefox TLS 1.3 / HTTP/2 handshakes and JA3/JA4 signatures.
|
|
8
|
+
- **🛡️ Built-in Anti-Bot Defense**: Automatically detects and solves advanced WAF challenges at microsecond speeds without the overhead of heavy browsers.
|
|
9
|
+
- **⚡ Dual-Tier Engine with Headless SPA Support**: Fast HTTP-first pipeline with seamless escalation to an embedded, lightweight (<100MB) Rust+V8 headless engine (`auto_render_spa`) when dynamic DOM rendering is needed.
|
|
10
|
+
- **🌐 Automatic Free Proxy Pool (`auto_proxy`)**: Built-in, zero-dependency proxy pool with health tracking, auto-rotation, and transparent failure retry.
|
|
11
|
+
- **🍪 Resilient Session Management**: Host-based persistent cookie caching with automatic anti-poisoning protection to prevent tainted sessions.
|
|
12
|
+
- **📦 Zero-Setup Driver Management**: Automatically provisions and manages required native binaries across macOS, Linux, and Windows on demand.
|
|
13
|
+
- **🚀 Intuitive Ruby DSL**: Clean, idiomatic syntax supporting class mixins (`include HttpMimic`), direct module calls (`HttpMimic.get`), and reusable client instances.
|
|
7
14
|
|
|
8
15
|
---
|
|
9
16
|
|
|
10
|
-
## 🌟
|
|
11
|
-
|
|
12
|
-
- **
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- **
|
|
18
|
-
- Chrome support: `chrome131` (default), `chrome124`, `chrome120`, `chrome133a`, `chrome136`, `chrome142`, `chrome99-110`.
|
|
19
|
-
- Firefox support: `firefox135`, `firefox133`, `firefox144`, `firefox117`, `firefox109`, `firefox102`, `firefox98`.
|
|
20
|
-
- Safari support: `safari180`, `safari170`, `safari155`, `safari153`.
|
|
21
|
-
- Edge & Tor support: `edge101`, `edge99`, `tor145`.
|
|
22
|
-
- **HTTParty-Style API**:
|
|
23
|
-
- Direct module methods: `HttpMimic.get`, `HttpMimic.post`, etc.
|
|
24
|
-
- Class mixin via `include HttpMimic` (`base_uri`, `headers`, `default_params`, `default_timeout`, `impersonate`, `proxy`, `cookies`, `persist_cookies`).
|
|
25
|
-
- Reusable instance client: `HttpMimic::Client.new(...)`.
|
|
26
|
-
- **Zero Shell Injection Risk**:
|
|
27
|
-
- Executes commands with array arguments via `Open3.capture3(*cmd_array)`.
|
|
28
|
-
- Uses stdin streaming (`-d @-`) to safely handle large payloads without hitting OS command-line limits.
|
|
29
|
-
- **Smart Response Parsing**:
|
|
30
|
-
- HTTP status helpers: `response.code`, `response.success?`, `response.redirect?`, `response.client_error?`, `response.server_error?`.
|
|
31
|
-
- Case-insensitive header access: `response.headers['Content-Type']`.
|
|
32
|
-
- Automatic `Set-Cookie` header parsing: `response.cookies['session_id']`.
|
|
33
|
-
- Auto-parsed JSON with object delegation: `response['key']`, `response.parsed_response`.
|
|
34
|
-
- Complete 3xx redirect history tracking: `response.history`.
|
|
35
|
-
- **Comprehensive Request Options**:
|
|
36
|
-
- Supports `query`, `headers`, `json`, `body` (form data), `cookies`, `timeout`, `connect_timeout`, `proxy`, `basic_auth`, `digest_auth`, `bearer_token`, `insecure`, custom `curl_options`, and more.
|
|
37
|
-
- **Graceful Fallback**:
|
|
38
|
-
- If a specific binary is unavailable and auto-download is disabled, automatically falls back to system standard `curl`.
|
|
17
|
+
## 🌟 Highlights
|
|
18
|
+
|
|
19
|
+
- **Authentic TLS & HTTP/2 Impersonation**: Native support for `chrome131` (default), `chrome120-142`, `safari180` (macOS/iOS), `firefox135`, `edge101`, and `tor145`.
|
|
20
|
+
- **Intelligent Challenge Resolution**: Automatic WAF challenge mitigation and smart profile fallback to keep requests succeeding.
|
|
21
|
+
- **Client-Side SPA Hydration**: High-speed HTTP execution by default, with optional V8 DOM hydration for complex JavaScript apps (`auto_render_spa: true`).
|
|
22
|
+
- **Free Proxy Pool & Auto-Rotation**: Opt-in proxy management (`auto_proxy: true`) with automatic dead-proxy detection and retry.
|
|
23
|
+
- **Safe & Reliable**: Zero shell injection risk via `Open3.capture3(*cmd_array)`, safe stdin streaming for large payloads, and smart response parsing.
|
|
24
|
+
- **Flexible Integration**: Works seamlessly as a one-off client, a reusable instance, or a class-level DSL.
|
|
39
25
|
|
|
40
26
|
---
|
|
41
27
|
|
|
@@ -198,6 +184,8 @@ response = HttpMimic.get('https://example.com/items', persist_cookies: true)
|
|
|
198
184
|
class Scraper
|
|
199
185
|
include HttpMimic
|
|
200
186
|
persist_cookies true
|
|
187
|
+
# persist_on_failure false # (default: do not save cookies on 403 / verification failure)
|
|
188
|
+
# clear_on_failure true # (default: auto-clear cached cookies if verification fails)
|
|
201
189
|
end
|
|
202
190
|
|
|
203
191
|
# 3. Manual CookieStore helpers
|
|
@@ -206,6 +194,9 @@ HttpMimic.save_cookies('example.com', { session: '123' }) # Save cookies with de
|
|
|
206
194
|
HttpMimic.clear_cookies!('example.com') # Clear host or all cookies
|
|
207
195
|
```
|
|
208
196
|
|
|
197
|
+
> **🛡️ Anti-Poisoning Failure Protection:**
|
|
198
|
+
> By default, `persist_on_failure` is set to `false`, meaning cookies returned with HTTP error codes (e.g. 403, 401) or unverified WAF challenge pages are **never** persisted to disk. Furthermore, `clear_on_failure: true` automatically wipes tainted host cookies upon verification failure, preventing blocked sessions from poisoning subsequent requests.
|
|
199
|
+
|
|
209
200
|
---
|
|
210
201
|
|
|
211
202
|
## 🧭 Target-Specific Navigation Headers & Client Hints
|
|
@@ -247,6 +238,8 @@ HttpMimic.configure do |config|
|
|
|
247
238
|
# Host CookieStore Persistence (disabled by default)
|
|
248
239
|
config.persist_cookies = false # Auto-persist cookies per host
|
|
249
240
|
config.cookie_store_dir = File.expand_path('~/.http_mimic/cookies') # Directory for cookie store
|
|
241
|
+
config.persist_on_failure = false # Do not save cookies if request/verification fails
|
|
242
|
+
config.clear_on_failure = true # Purge cached host cookies on 403 / verification failure
|
|
250
243
|
|
|
251
244
|
# WAF challenge solving (enabled by default)
|
|
252
245
|
config.auto_solve_waf = true # Automatically solve detected WAF JS challenges
|
|
@@ -278,15 +271,106 @@ end
|
|
|
278
271
|
| `:body` | Hash / String | Form payload (Hash) or raw request body string |
|
|
279
272
|
| `:cookies` | Hash / String | Request cookies |
|
|
280
273
|
| `:persist_cookies` | Boolean | Automatically load and save cookies for the host across requests |
|
|
274
|
+
| `:persist_on_failure` | Boolean | Save cookies even if request or verification fails (default `false`) |
|
|
275
|
+
| `:clear_on_failure` | Boolean | Automatically clear stored cookies on verification failure / 403 / WAF block (default `true`) |
|
|
281
276
|
| `:cookie_jar` | String | Path to save cookies (`-c`) |
|
|
282
277
|
| `:cookie_file` | String | Path to read cookies (`-b`) |
|
|
283
278
|
| `:timeout` | Integer / Float | Maximum execution timeout in seconds (`--max-time`) |
|
|
284
279
|
| `:connect_timeout` | Integer / Float | Connection timeout in seconds (`--connect-timeout`) |
|
|
285
280
|
| `:proxy` | String | Proxy address (e.g., `'http://127.0.0.1:8888'`) |
|
|
281
|
+
| `:auto_proxy` | Boolean | Automatically fetch and rotate through a pool of free HTTP proxies (default: `false`) |
|
|
286
282
|
| `:basic_auth` | Hash | `{ username: 'admin', password: 'secret' }` |
|
|
287
283
|
| `:bearer_token` | String | Appends `Authorization: Bearer <token>` header |
|
|
288
284
|
| `:insecure` | Boolean | Disable SSL certificate verification (`-k`) |
|
|
289
285
|
| `:curl_options` | Array / String | Additional raw curl arguments (e.g., `['--http2', '--compressed']`) |
|
|
286
|
+
| `:auto_render_spa` | Boolean | Automatically detect unhydrated SPA shells and render via Obscura |
|
|
287
|
+
| `:render` | Symbol | `:spa` or `:obscura` to explicitly render page via Obscura |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## ⚡ Obscura SPA Rendering (Tier 2 Engine)
|
|
292
|
+
|
|
293
|
+
`http_mimic` provides native support for [`h4ckf0r0day/obscura`](https://github.com/h4ckf0r0day/obscura)—a lightweight, single-binary (<100MB) headless browser engine written in Rust with an embedded V8 JavaScript runtime and built-in stealth anti-detection capabilities.
|
|
294
|
+
|
|
295
|
+
Obscura is auto-downloaded on-demand into `~/.http_mimic/bin/obscura` when SPA rendering is requested.
|
|
296
|
+
|
|
297
|
+
### 1. Automatic SPA Detection & Rendering (`auto_render_spa`)
|
|
298
|
+
|
|
299
|
+
Similar to `auto_solve_waf`, when `auto_render_spa: true` is enabled, `HttpMimic` first sends a microsecond-fast HTTP request (Tier 1). If the response is detected as an unhydrated SPA shell (empty React `#root`, Vue `#app`, Angular `<app-root>`, Google Dynamic SERP, or `<noscript>` prompt), it automatically transitions to Obscura (Tier 2) to render the full DOM tree:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
# Auto-detects SPA shell and seamlessly renders with Obscura
|
|
303
|
+
response = HttpMimic.get('https://example.com/spa', auto_render_spa: true)
|
|
304
|
+
|
|
305
|
+
puts response.code # => 200
|
|
306
|
+
puts response.title # => Fully hydrated DOM title
|
|
307
|
+
puts response.body # => Fully rendered HTML with client-side injected DOM nodes
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 2. Direct SPA Rendering
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
# Direct SPA render
|
|
314
|
+
response = HttpMimic.render('https://example.com/spa')
|
|
315
|
+
|
|
316
|
+
# or via alias:
|
|
317
|
+
response = HttpMimic.spa('https://example.com/spa')
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Supported Options**:
|
|
321
|
+
- `:wait_until` - `'load'`, `'domcontentloaded'`, `'networkidle0'` (default: `'networkidle0'`)
|
|
322
|
+
- `:timeout` - execution deadline in seconds (default: 30)
|
|
323
|
+
- `:proxy` - proxy address (`socks5://...` or `http://...`)
|
|
324
|
+
- `:eval` - JavaScript expression to evaluate in page context
|
|
325
|
+
- `:dump` - output format: `'html'` (default), `'text'`, `'links'`, `'markdown'`, `'cookies'`
|
|
326
|
+
- `:stealth` - whether stealth anti-detection is enabled (default: `true`)
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 🌐 Automatic Free Proxy Pool (`auto_proxy`)
|
|
331
|
+
|
|
332
|
+
`http_mimic` provides a built-in, zero-dependency Free Proxy Pool manager. When enabled, `HttpMimic` automatically fetches and pools hundreds of free HTTP proxies from maintained public sources (monosans, ProxyScrape, TheSpeedX), automatically marks dead proxies, and transparently retries requests on network or proxy failure.
|
|
333
|
+
|
|
334
|
+
By default, `auto_proxy` is **`false`** so users can choose when to opt-in.
|
|
335
|
+
|
|
336
|
+
### 1. Per-Request Opt-in
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
# Automatically obtain a proxy from the pool and execute request
|
|
340
|
+
response = HttpMimic.get('https://httpbin.org/ip', auto_proxy: true)
|
|
341
|
+
|
|
342
|
+
# Explicit proxy always takes precedence over auto_proxy:
|
|
343
|
+
response = HttpMimic.get('https://httpbin.org/ip', auto_proxy: true, proxy: 'http://my-dedicated-proxy:8080')
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 2. Global Configuration
|
|
347
|
+
|
|
348
|
+
```ruby
|
|
349
|
+
HttpMimic.configure do |config|
|
|
350
|
+
config.auto_proxy = true # Enable free proxy pool by default
|
|
351
|
+
config.proxy_retries = 3 # Maximum proxy retry attempts on connection failure
|
|
352
|
+
config.proxy_pool_ttl = 1800 # Cache duration for fetched proxy list (seconds)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Or via class-level DSL:
|
|
356
|
+
HttpMimic.auto_proxy = true
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 3. Proxy Pool Management
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
# Check available pool size
|
|
363
|
+
HttpMimic.proxy_pool.size
|
|
364
|
+
|
|
365
|
+
# Sample a random proxy from the pool
|
|
366
|
+
HttpMimic.proxy_pool.sample # => "http://185.200.188.234:10001"
|
|
367
|
+
|
|
368
|
+
# Manually refresh the proxy pool
|
|
369
|
+
HttpMimic.refresh_proxies!
|
|
370
|
+
|
|
371
|
+
# Load custom proxies into the pool
|
|
372
|
+
HttpMimic.proxy_pool.load(['http://1.2.3.4:8080', 'http://5.6.7.8:3128'])
|
|
373
|
+
```
|
|
290
374
|
|
|
291
375
|
---
|
|
292
376
|
|
|
@@ -94,9 +94,10 @@ module HttpMimic
|
|
|
94
94
|
args = []
|
|
95
95
|
stdin_data = nil
|
|
96
96
|
|
|
97
|
-
# Base flags: silent mode, include HTTP headers
|
|
97
|
+
# Base flags: silent mode, include HTTP headers, auto-decompress responses
|
|
98
98
|
args << '-s'
|
|
99
99
|
args << '-i'
|
|
100
|
+
args << '--compressed'
|
|
100
101
|
|
|
101
102
|
# HTTP Method
|
|
102
103
|
if @method == 'HEAD'
|
|
@@ -247,6 +248,10 @@ module HttpMimic
|
|
|
247
248
|
(options[:ios_impersonate] || 'safari260_ios').to_s.downcase
|
|
248
249
|
when :mobile
|
|
249
250
|
(options[:mobile_impersonate] || 'chrome131_android').to_s.downcase
|
|
251
|
+
when :firefox
|
|
252
|
+
(options[:firefox_impersonate] || 'firefox135').to_s.downcase
|
|
253
|
+
when :safari
|
|
254
|
+
(options[:safari_impersonate] || 'safari180').to_s.downcase
|
|
250
255
|
else
|
|
251
256
|
(options[:impersonate] || config.default_impersonate).to_s.downcase
|
|
252
257
|
end
|
|
@@ -387,6 +392,17 @@ module HttpMimic
|
|
|
387
392
|
headers_to_send['accept'] ||= 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7'
|
|
388
393
|
headers_to_send['accept-language'] ||= 'en-US,en;q=0.9'
|
|
389
394
|
end
|
|
395
|
+
|
|
396
|
+
# Smart Search referral context (e.g. searching from Google homepage)
|
|
397
|
+
begin
|
|
398
|
+
parsed_uri = URI.parse(url)
|
|
399
|
+
if parsed_uri && parsed_uri.host =~ /google\./i && parsed_uri.path =~ /\/search/i
|
|
400
|
+
headers_to_send['referer'] ||= "#{parsed_uri.scheme || 'https'}://#{parsed_uri.host}/"
|
|
401
|
+
headers_to_send['sec-fetch-site'] ||= 'same-origin'
|
|
402
|
+
end
|
|
403
|
+
rescue StandardError
|
|
404
|
+
# ignore
|
|
405
|
+
end
|
|
390
406
|
end
|
|
391
407
|
|
|
392
408
|
def find_in_download_dir(candidates)
|
|
@@ -30,16 +30,31 @@ module HttpMimic
|
|
|
30
30
|
attr_accessor :qjs_version
|
|
31
31
|
attr_accessor :qjs_github_repo
|
|
32
32
|
|
|
33
|
+
# Obscura headless SPA engine settings
|
|
34
|
+
attr_accessor :obscura_version
|
|
35
|
+
attr_accessor :obscura_github_repo
|
|
36
|
+
attr_accessor :obscura_path
|
|
37
|
+
attr_accessor :auto_render_spa
|
|
38
|
+
|
|
33
39
|
# WAF & JS challenge solving settings
|
|
34
40
|
attr_accessor :auto_solve_waf
|
|
35
41
|
|
|
36
42
|
# Shared Host-based CookieStore settings
|
|
37
43
|
attr_accessor :persist_cookies
|
|
38
44
|
attr_accessor :cookie_store_dir
|
|
45
|
+
attr_accessor :persist_on_failure
|
|
46
|
+
attr_accessor :clear_on_failure
|
|
39
47
|
|
|
40
48
|
# Modern Navigation Headers simulation
|
|
41
49
|
attr_accessor :navigation_headers
|
|
42
50
|
|
|
51
|
+
# Automatic Free Proxy Pool settings
|
|
52
|
+
attr_accessor :auto_proxy
|
|
53
|
+
attr_accessor :proxy_sources
|
|
54
|
+
attr_accessor :proxy_retries
|
|
55
|
+
attr_accessor :proxy_pool_ttl
|
|
56
|
+
attr_accessor :proxy_timeout
|
|
57
|
+
|
|
43
58
|
def initialize
|
|
44
59
|
@default_impersonate = 'chrome131'
|
|
45
60
|
@default_mobile_impersonate = 'safari180_ios'
|
|
@@ -68,15 +83,33 @@ module HttpMimic
|
|
|
68
83
|
@qjs_version = 'v0.16.2'
|
|
69
84
|
@qjs_github_repo = 'quickjs-ng/quickjs'
|
|
70
85
|
|
|
86
|
+
# Obscura defaults (Lightweight Rust+V8 headless SPA engine)
|
|
87
|
+
# Temporarily points to 'anxgang/obscura' (v0.2.3) for Web Worker importScripts support.
|
|
88
|
+
# Will switch back to 'h4ckf0r0day/obscura' once upstream Pull Request is merged.
|
|
89
|
+
# @obscura_github_repo = 'h4ckf0r0day/obscura'
|
|
90
|
+
@obscura_version = 'v0.2.3'
|
|
91
|
+
@obscura_github_repo = 'anxgang/obscura'
|
|
92
|
+
@obscura_path = nil
|
|
93
|
+
@auto_render_spa = false
|
|
94
|
+
|
|
71
95
|
# WAF challenge solving defaults
|
|
72
96
|
@auto_solve_waf = true
|
|
73
97
|
|
|
74
98
|
# Shared Host CookieStore defaults
|
|
75
99
|
@persist_cookies = false
|
|
76
100
|
@cookie_store_dir = File.expand_path('~/.http_mimic/cookies')
|
|
101
|
+
@persist_on_failure = false
|
|
102
|
+
@clear_on_failure = true
|
|
77
103
|
|
|
78
104
|
# Navigation Headers defaults
|
|
79
105
|
@navigation_headers = true
|
|
106
|
+
|
|
107
|
+
# Automatic Free Proxy Pool defaults
|
|
108
|
+
@auto_proxy = false
|
|
109
|
+
@proxy_sources = nil
|
|
110
|
+
@proxy_retries = 3
|
|
111
|
+
@proxy_pool_ttl = 1800
|
|
112
|
+
@proxy_timeout = 5
|
|
80
113
|
end
|
|
81
114
|
end
|
|
82
115
|
end
|
|
@@ -16,6 +16,9 @@ module HttpMimic
|
|
|
16
16
|
DEFAULT_QJS_REPO = 'quickjs-ng/quickjs'
|
|
17
17
|
DEFAULT_QJS_VERSION = 'v0.16.2'
|
|
18
18
|
|
|
19
|
+
DEFAULT_OBSCURA_REPO = 'h4ckf0r0day/obscura'
|
|
20
|
+
DEFAULT_OBSCURA_VERSION = 'v0.2.1'
|
|
21
|
+
|
|
19
22
|
class DownloadError < HttpMimic::Error; end
|
|
20
23
|
class UnsupportedPlatformError < HttpMimic::Error; end
|
|
21
24
|
|
|
@@ -165,6 +168,100 @@ module HttpMimic
|
|
|
165
168
|
File.join(dir, '.qjs_version')
|
|
166
169
|
end
|
|
167
170
|
|
|
171
|
+
def download_obscura!(version: nil, install_dir: nil, repo: nil, force: false)
|
|
172
|
+
target_version = normalize_version(version || HttpMimic.configuration.obscura_version || DEFAULT_OBSCURA_VERSION)
|
|
173
|
+
target_dir = File.expand_path(install_dir || HttpMimic.configuration.install_dir)
|
|
174
|
+
target_repo = repo || HttpMimic.configuration.obscura_github_repo || DEFAULT_OBSCURA_REPO
|
|
175
|
+
|
|
176
|
+
FileUtils.mkdir_p(target_dir)
|
|
177
|
+
|
|
178
|
+
dest_binary = File.join(target_dir, binary_name_for_platform('obscura'))
|
|
179
|
+
|
|
180
|
+
if !force && obscura_installed?(version: target_version, install_dir: target_dir)
|
|
181
|
+
log_info("obscura #{target_version} is already installed in #{target_dir}")
|
|
182
|
+
return dest_binary
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
asset_name = obscura_platform_asset
|
|
186
|
+
download_url = "https://github.com/#{target_repo}/releases/download/#{target_version}/#{asset_name}"
|
|
187
|
+
|
|
188
|
+
log_info("Downloading Obscura (#{target_version}) [#{asset_name}]...")
|
|
189
|
+
archive_data = fetch_binary(download_url)
|
|
190
|
+
|
|
191
|
+
log_info("Extracting Obscura archive to #{target_dir}...")
|
|
192
|
+
extract_tar_gz(archive_data, target_dir)
|
|
193
|
+
|
|
194
|
+
# Ensure executable permissions on extracted binaries
|
|
195
|
+
%w[obscura obscura-worker].each do |name|
|
|
196
|
+
p = File.join(target_dir, binary_name_for_platform(name))
|
|
197
|
+
File.chmod(0755, p) if File.exist?(p)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
File.write(obscura_version_file_path(target_dir), target_version)
|
|
201
|
+
|
|
202
|
+
log_info("Obscura #{target_version} installation complete! (#{dest_binary})")
|
|
203
|
+
dest_binary
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def obscura_installed?(version: nil, install_dir: nil)
|
|
207
|
+
target_dir = File.expand_path(install_dir || HttpMimic.configuration.install_dir)
|
|
208
|
+
bin = File.join(target_dir, binary_name_for_platform('obscura'))
|
|
209
|
+
return false unless File.file?(bin) && File.executable?(bin)
|
|
210
|
+
|
|
211
|
+
if version
|
|
212
|
+
target_version = normalize_version(version)
|
|
213
|
+
v_file = obscura_version_file_path(target_dir)
|
|
214
|
+
return false unless File.file?(v_file)
|
|
215
|
+
return File.read(v_file).strip == target_version
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
true
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def obscura_path(install_dir: nil)
|
|
222
|
+
return HttpMimic.configuration.obscura_path if HttpMimic.configuration.obscura_path && File.executable?(HttpMimic.configuration.obscura_path)
|
|
223
|
+
|
|
224
|
+
target_dir = File.expand_path(install_dir || HttpMimic.configuration.install_dir)
|
|
225
|
+
candidate = File.join(target_dir, binary_name_for_platform('obscura'))
|
|
226
|
+
return candidate if File.file?(candidate) && File.executable?(candidate)
|
|
227
|
+
|
|
228
|
+
# Fallback to system PATH
|
|
229
|
+
sys_bin = `which obscura 2>/dev/null`.strip
|
|
230
|
+
return sys_bin if !sys_bin.empty? && File.executable?(sys_bin)
|
|
231
|
+
|
|
232
|
+
nil
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def obscura_platform_asset
|
|
236
|
+
os = host_os
|
|
237
|
+
cpu = host_cpu
|
|
238
|
+
|
|
239
|
+
case os
|
|
240
|
+
when :macos
|
|
241
|
+
case cpu
|
|
242
|
+
when :arm64 then 'obscura-aarch64-macos-stealth.tar.gz'
|
|
243
|
+
when :x86_64 then 'obscura-x86_64-macos-stealth.tar.gz'
|
|
244
|
+
else
|
|
245
|
+
raise UnsupportedPlatformError, "Unsupported macOS CPU architecture for Obscura: #{cpu}"
|
|
246
|
+
end
|
|
247
|
+
when :linux
|
|
248
|
+
case cpu
|
|
249
|
+
when :x86_64 then 'obscura-x86_64-linux-stealth.tar.gz'
|
|
250
|
+
when :aarch64, :arm64 then 'obscura-aarch64-linux-stealth.tar.gz'
|
|
251
|
+
else
|
|
252
|
+
raise UnsupportedPlatformError, "Unsupported Linux CPU architecture for Obscura: #{cpu}"
|
|
253
|
+
end
|
|
254
|
+
when :windows
|
|
255
|
+
'obscura-x86_64-windows-stealth.zip'
|
|
256
|
+
else
|
|
257
|
+
raise UnsupportedPlatformError, "Unsupported operating system for Obscura: #{RbConfig::CONFIG['host_os']}"
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def obscura_version_file_path(dir)
|
|
262
|
+
File.join(dir, '.obscura_version')
|
|
263
|
+
end
|
|
264
|
+
|
|
168
265
|
def binary_path(name, install_dir: nil)
|
|
169
266
|
target_dir = File.expand_path(install_dir || HttpMimic.configuration.install_dir)
|
|
170
267
|
candidate = File.join(target_dir, binary_name_for_platform(name))
|
|
@@ -35,6 +35,24 @@ module HttpMimic
|
|
|
35
35
|
default_options[:proxy] = proxy_str
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def auto_proxy(enabled = nil)
|
|
39
|
+
return default_options[:auto_proxy] if enabled.nil?
|
|
40
|
+
default_options[:auto_proxy] = enabled
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def proxy_pool
|
|
44
|
+
ProxyPool.instance
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def proxy_sources(sources = nil)
|
|
48
|
+
return configuration.proxy_sources if sources.nil?
|
|
49
|
+
configuration.proxy_sources = sources
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def refresh_proxies!(force: true)
|
|
53
|
+
ProxyPool.refresh!(force: force)
|
|
54
|
+
end
|
|
55
|
+
|
|
38
56
|
def cookies(c = nil)
|
|
39
57
|
return default_options[:cookies] if c.nil?
|
|
40
58
|
default_options[:cookies] ||= {}
|
|
@@ -46,6 +64,21 @@ module HttpMimic
|
|
|
46
64
|
default_options[:persist_cookies] = enabled
|
|
47
65
|
end
|
|
48
66
|
|
|
67
|
+
def persist_on_failure(enabled = nil)
|
|
68
|
+
return default_options[:persist_on_failure] if enabled.nil?
|
|
69
|
+
default_options[:persist_on_failure] = enabled
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def clear_on_failure(enabled = nil)
|
|
73
|
+
return default_options[:clear_on_failure] if enabled.nil?
|
|
74
|
+
default_options[:clear_on_failure] = enabled
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def auto_render_spa(enabled = nil)
|
|
78
|
+
return default_options[:auto_render_spa] if enabled.nil?
|
|
79
|
+
default_options[:auto_render_spa] = enabled
|
|
80
|
+
end
|
|
81
|
+
|
|
49
82
|
def mode(m = nil)
|
|
50
83
|
return default_options[:mode] if m.nil?
|
|
51
84
|
default_options[:mode] = m
|
|
@@ -93,6 +126,12 @@ module HttpMimic
|
|
|
93
126
|
request(:options, url, options)
|
|
94
127
|
end
|
|
95
128
|
|
|
129
|
+
def render(url, options = {})
|
|
130
|
+
merged = default_options.merge(options)
|
|
131
|
+
Obscura.render(url, merged)
|
|
132
|
+
end
|
|
133
|
+
alias spa render
|
|
134
|
+
|
|
96
135
|
def request(method, url, options = {})
|
|
97
136
|
merged = default_options.merge(options)
|
|
98
137
|
|