Requests icon indicating copy to clipboard operation
Requests copied to clipboard

Add Host Bindings feature

Open schlessera opened this issue 1 month ago • 0 comments

Pull Request Type

This is a:

  • [ ] Bug fix
  • [x] 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 avatar Dec 12 '25 17:12 schlessera