URL Allowlist Validator
Checks whether a URL belongs to an approved scheme, host, and path policy.
Function signature
ogValidateAllowedUrl(url, allowed_hosts = array(), allowed_schemes = array('https'), options = array())
Categories
- Forms and Validation
Parameters
urlCandidate URL to validate.allowed_hostsApproved host names. Empty means any public host is allowed.allowed_schemesApproved schemes. Defaults to https only.optionsOptional path_prefixes and allow_private_ip flags. Recognized keys: `allow_private_ip`, `path_prefixes`.Return value
Short public-safe status message.
- valid
- url
- scheme
- host
- path
Compatibility
Existing function name and call order preserved; metadata signature corrected to source.
Minimum PHP version: 7.4
Security notes
Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets and internal paths out of public output.
Code
<?php
/*
* Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
* Free for personal and internal use. Paid project use requires visible credit
* to Jeffery L. Paris. Corporate use requires a paid license fee unless a
* separate written license states otherwise.
*/
/**
* Checks whether a URL belongs to an approved scheme, host, and path policy.
*
* Primary use case: Webhook callbacks, redirects, imported links.
* Typical inputs: candidate URL, allowed host list, scheme list.
* Typical output: validated URL or empty string.
*
* Implementation note: Reject localhost/private IP targets for server-side fetches.
*
* @param string $url Candidate URL to validate.
* @param array $allowed_hosts Approved host names. Empty means any public host is allowed.
* @param array $allowed_schemes Approved schemes. Defaults to https only.
* @param array $options Optional path_prefixes and allow_private_ip flags.
* @return array Structured validation result.
*/
function ogValidateAllowedUrl($url, $allowed_hosts = array(), $allowed_schemes = array('https'), $options = array()) {
$result = array(
'success' => false,
'message' => '',
'data' => array(
'url' => '',
'scheme' => '',
'host' => '',
'path' => '',
'valid' => false
)
);
$url = trim((string)$url);
if (!is_array($allowed_hosts)) {
$allowed_hosts = array();
}
if (!is_array($allowed_schemes) || empty($allowed_schemes)) {
$allowed_schemes = array('https');
}
if (!is_array($options)) {
$options = array();
}
if (empty($url)) {
$result['message'] = 'Missing URL.';
return $result;
}
if (preg_match('/[\x00-\x1F\x7F]/', $url)) {
$result['message'] = 'URL contains control characters.';
return $result;
}
if (strpos($url, '//') === 0) {
$result['message'] = 'Protocol-relative URLs are not allowed here.';
return $result;
}
$parts = parse_url($url);
if (empty($parts) || empty($parts['scheme']) || empty($parts['host'])) {
$result['message'] = 'URL must include a scheme and host.';
return $result;
}
$scheme = strtolower($parts['scheme']);
$host = strtolower($parts['host']);
$path = '/';
if (!empty($parts['path'])) {
$path = $parts['path'];
}
$approved_schemes = array();
foreach ($allowed_schemes as $allowed_scheme) {
$allowed_scheme = strtolower(trim((string)$allowed_scheme));
if (!empty($allowed_scheme)) {
$approved_schemes[] = $allowed_scheme;
}
}
if (!in_array($scheme, $approved_schemes, true)) {
$result['message'] = 'URL scheme is not approved.';
return $result;
}
if (!empty($allowed_hosts)) {
$host_allowed = false;
foreach ($allowed_hosts as $allowed_host) {
$allowed_host = strtolower(trim((string)$allowed_host));
if (!empty($allowed_host) && $host === $allowed_host) {
$host_allowed = true;
}
}
if (!$host_allowed) {
$result['message'] = 'URL host is not approved.';
return $result;
}
}
$allow_private_ip = false;
if (!empty($options['allow_private_ip'])) {
$allow_private_ip = true;
}
if (!$allow_private_ip && filter_var($host, FILTER_VALIDATE_IP)) {
$public_ip = filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if ($public_ip === false) {
$result['message'] = 'Private or reserved IP targets are not allowed.';
return $result;
}
}
if (!$allow_private_ip) {
if ($host === 'localhost' || substr($host, -10) === '.localhost') {
$result['message'] = 'Localhost targets are not allowed.';
return $result;
}
}
if (!empty($options['path_prefixes']) && is_array($options['path_prefixes'])) {
$path_allowed = false;
foreach ($options['path_prefixes'] as $prefix) {
$prefix = (string)$prefix;
if (!empty($prefix) && strpos($path, $prefix) === 0) {
$path_allowed = true;
}
}
if (!$path_allowed) {
$result['message'] = 'URL path is not approved.';
return $result;
}
}
$result['success'] = true;
$result['message'] = 'URL validated.';
$result['data'] = array(
'url' => $url,
'scheme' => $scheme,
'host' => $host,
'path' => $path,
'valid' => true
);
return $result;
}