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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b0a9fff1b74c98ea24c294a491b00ba57e270d24e6c0bb7a669e6d2db264d8a
4
- data.tar.gz: e11d3ae615b92abcfedda8547120290d82020acd82230353a127cc97dfc844f6
3
+ metadata.gz: 1b0b4192818428818cca7cde26b4ce90d6fa847db6e80e2b63c163dd8171bdd9
4
+ data.tar.gz: de6a71df66c710a30e7cb8fcbe6976653ec6242a6d29fd6959016513da4513be
5
5
  SHA512:
6
- metadata.gz: 5e0a93bb67bb84582e24df07ea4d51d291a53f0b48ab77ad0bd4a63b666bede4b493bbb405ad34d76602e086eafd76a455668c2a491488d83dd2c0be1060fe66
7
- data.tar.gz: c616cafefeb23d78af7320beab5ddc3b8f79aafda440fd1fac889f5a5990417b31716105e8c74cab566e6c0ed6eca04a82a397ddc4242eb59caed519e52739de
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
- `http_mimic` is a Ruby HTTP Client gem built on top of `Open3.capture3` and `curl-impersonate`.
4
- It provides an elegant, concise, and intuitive **HTTParty-style** API while leveraging [`lexiforest/curl-impersonate`](https://github.com/lexiforest/curl-impersonate) to simulate authentic Chrome, Firefox, Safari, Edge, and Tor TLS / HTTP2 fingerprints (JA3, JA4, Akamai) and handshakes.
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
- It also includes **Webdrivers-like automatic driver management**, automatically downloading and managing `curl-impersonate` binaries across macOS, Linux, and Windows without manual setup.
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
- ## 🌟 Features
11
-
12
- - **Webdrivers-Style Driver Management**:
13
- - Automatically downloads and unpacks official binaries from [`lexiforest/curl-impersonate`](https://github.com/lexiforest/curl-impersonate) to `~/.http_mimic/bin`.
14
- - **Automatic platform & architecture detection** (macOS ARM/Intel, Linux x86_64/aarch64/musl, Windows x86_64/arm64, FreeBSD).
15
- - Zero configuration required—installs on first request automatically.
16
- - Manual driver management helpers: `HttpMimic.download_driver!`, `HttpMimic.driver_installed?`, `HttpMimic::Downloader.available_binaries`.
17
- - **Authentic Browser Handshakes & Fingerprints**:
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