Skip to content

Conversation

@schlessera
Copy link
Member

Pull Request Type

This is a:

  • Bug fix
  • New feature
  • Documentation improvement
  • Code quality improvement

Context

This PR adds support for binding specific hostnames to specific IP addresses, bypassing DNS resolution.

This is useful for:

  • Testing against specific server instances
  • Load balancing and failover scenarios
  • Connecting to servers via specific IPs while preserving the original hostname in the Host header
  • Development environments where DNS may not be configured
  • Preventing rebinding attacks

Detailed Description

New HostBindings utility class

A value object that validates and stores hostname-to-IP mappings. Key features:

  • Security by default: IP addresses are validated using filter_var(FILTER_VALIDATE_IP) to prevent hostname injection attacks. Accepts both IPv4 and IPv6 addresses (including private/localhost ranges).
  • Opt-out mechanism: For pre-validated inputs or non-standard formats, validation can be skipped via HostBindings::SKIP_IP_VALIDATION (use with caution).
  • Normalization: Whitespace is trimmed from IP addresses.

Methods:

  • has_host($host) - Check if a host has a mapping
  • get_first_ip_for_host($host) - Get the first IP for a host (throws UnknownHost or MissingIpAddress if not available)
  • get_all_ips_for_host($host) - Get all IPs for a host (throws UnknownHost if not found)

New exceptions

  • UnknownHost - Thrown when requesting a host that isn't in the bindings
  • MissingIpAddress - Thrown when a host has no IP addresses configured

New HOST_BINDINGS capability

Added to the Capability interface to allow transport capability testing.

Transport integration

Curl transport (Transport\Curl):

  • Uses CURLOPT_CONNECT_TO (cURL 7.49.0+) as the preferred method
  • Falls back to CURLOPT_RESOLVE (cURL 7.21.3+) for older cURL versions
  • Falls back to URL rewriting for HTTP (not HTTPS) on very old cURL versions
  • Properly handles IPv6 addresses by wrapping them in square brackets
  • Preserves the original hostname in the Host header

Fsockopen transport (Transport\Fsockopen):

  • Replaces the connection host with the mapped IP address
  • Original hostname is preserved in the Host header

Usage

The host_bindings option accepts either an array (with automatic IP validation) or a pre-constructed HostBindings object:

// Using an array (IPs are validated)
$response = Requests::get('https://example.com/api', [], [
    'host_bindings' => [
        'example.com' => ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'],
    ],
]);

// Using a HostBindings object (for advanced control)
$bindings = new HostBindings(
    ['example.com' => ['custom-value']],
    HostBindings::SKIP_IP_VALIDATION
);
$response = Requests::get('https://example.com/api', [], [
    'host_bindings' => $bindings,
]);

Includes

  • Comprehensive unit tests for HostBindings class (constructor validation, all methods)
  • Unit tests for new exceptions
  • Integration tests in Transport\BaseTestCase covering both transports
  • Documentation in Requests::request() docblock

@schlessera schlessera requested a review from jrfnl December 12, 2025 17:43
@schlessera schlessera changed the title Redo initial implementation Add Host Bindings Feature Dec 12, 2025
@schlessera schlessera changed the title Add Host Bindings Feature Add Host Bindings feature Dec 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants