How to represent a URI host in PHP

PHP URI Host
When working with web applications, it is important to have a clear understanding of the meaning of URI host.

A host is typically defined as a string of alphanumeric characters, optionally separated by dots (.) which is usually either an IP address, a registered domain name (with optional subdomain), or local network names such as localhost or myserver.local.

The host does not include the port (e.g. :80 or :443) and does not include the scheme (e.g. http or https).
The entire host name (including dots) must not exceed 255 (2^8-1) characters.
See: tools.ietf.org section 2.1

When working with web addresses, hyphens are also allowed, e.g. "my-site.com".

PHP and .NET differ slightly in their definition of host. Specifically, the $_SERVER global array in PHP has a key HTTP_HOST which stores a value that includes the port, if it does not match the default port for the scheme.

The HTTP_HOST is actually just a http header, which is set by your browser when you visit a website. In such a way, by using Postman or similar tools, you can easily modify the "Host" header and fool the server into believing that the user is accessing another site.

As a web developer, you need to be aware of canonical hosts. A malicious attacker may easily spoof the HTTP_HOST to a fake name, which is why the key SERVER_NAME (which is set by the server) is more reliable. However, the SERVER_NAME key does not include the subdomain, unless the web server configuration is defined primarily to include the subdomain.

If you are hosting a website in Apache, you may wish to modify your "apache2.conf" file to include UseCanonicalName On.
This prevents the HTTP_HOST value (Host http header) from being manipulated by a malicious attacker.
Instead, the attacker will received a Forbidden response.

In .NET, the URI Host is defined as the "DNS host name or IP address of the server".
See: docs.microsoft.com

I have developed a Host class in PHP which checks the validity of the host, and allows easy access to the top level domain, domain name and subdomain.


<?php

namespace ACA\Text\URI
{
/**
* An object representing a URI host.
* A domain and an optional subdomain
* @author Antony Charles Allen
* @since 21st April 2019
*/
class Host
{
public const HOST_FIRST_CHAR_REGEX = '[a-z0-9]';

private const REGEX_TLD_XX_XX = '/\.([a-z]{2,3}\.[a-z]{2})$/i';
private const REGEX_TLD_XXX = '/\.([a-z]{2,})$/i';

private string $subdomain = '';
private string $domain;
private string $tld = '';


function __construct(string $host)
{
$original = $host;

$host = strtolower($host);

if (preg_match('/^('.self::HOST_FIRST_CHAR_REGEX.'([a-z0-9-]*[a-z0-9]+)?)\.([a-z]{2,})$/i', $host, $matches))
{
$this->domain = $matches[1];
$this->tld = $matches[3];
return;
}

if (preg_match(self::REGEX_TLD_XX_XX, $host, $matches)) // .co.uk, .com.ua
{
$this->tld = $matches[1];

$host = preg_replace(self::REGEX_TLD_XX_XX, '', $host, 1);
}
else if (preg_match(self::REGEX_TLD_XXX, $host, $matches)) // .com, .eu, .local, .website
{
$this->tld = $matches[1];

$host = preg_replace(self::REGEX_TLD_XXX, '', $host, 1);
}

if (preg_match('/^(([a-z0-9\.-]+)\.)?('.self::HOST_FIRST_CHAR_REGEX.'[a-z0-9-]*[a-z0-9])$/i', $host, $matches))
{
if (array_key_exists(2, $matches)) $this->subdomain = $matches[2];

$this->domain = $matches[3];

return;
}

throw new \InvalidArgumentException("Unable to process remaining name [$host] in host [$original]");
}


public function __toString() : string
{
$str = '';

if ($this->subdomain) $str .= $this->subdomain . '.';

$str .= $this->domain;

if ($this->tld) $str .= '.'.$this->tld;

return $str;
}


/**
* The subdomain, e.g. www
* Otherwise a blank string
* @return string
*/
public function Subdomain() : string
{
return $this->subdomain;
}


/**
* The registered domain name, e.g. unicms.co.uk
* @return string
*/
public function Domain() : string
{
$str = $this->domain;

if ($this->tld) $str .= '.' . $this->tld;

return $str;
}
}
}


Please feel free to use this code. It is also a necessary reference class for the System.Uri class that I wrote in another post.

I will come back and update this class at some point to include the ability to use an IP address as a host.

Happy coding!
Hey you! I need your help!

Thanks for reading! All the content on this site is free, but I need your help to spread the word. Please support me by:

  1. Sharing my page on Facebook
  2. Tweeting my page on Twitter
  3. Posting my page on LinkedIn
  4. Bookmarking this site and returning in the near future
Thank you!