Validar uma URL com Regexp e PHP

Primeiro uma expressão regular para validar o formato e extrair algumas informações importantes da URL. Depois usar esses valores extraídos para deixar a entrada de URL mais flexível e também fazer uma validação mais rigorosa.

A Expressão Regular

#^([^/]*)//([^:/]+)(?::(\d+))?(.*)#

^([^/]*) captura do início até antes da primeira barra. O alvo é o protocolo: http, https. Note que o “*” torna essa informação opcional, sendo assim, uma URL que começa com “//” passaria nessa validação.

// é obrigatório e não é extraído, não está entre parênteses. Não precisamos das barras como valor, só precisamos ter certeza que elas existem.

([^:/]+) captura tudo até os “:” ou “/”. Dois pontos porque na URL pode ter a porta de conexão e barra porque é o inicio do caminho do arquivo. Nesta parte esperamos encontrar o domínio ou um endereço IP.

(?::(\d+))? captura a porta, que é opcional. Caso ela não tenha sido definida na URL, vamos ter uma string vazia nessa posição.

(.*) captura o restante depois da porta, domínio ou IP. Isso inclui a primeira barra. Essa parte representa o caminho do arquivo no servidor.

O PHP

Com a expressão regular pronta, preg_match faz o papel de validar e retornar as partes extraídas em $matches.

function is_url($str) {
	if (!preg_match('#^([^/]*)//([^:/]+)(?::(\d+))?(.*)#', $str, $matches))
		return false;
	
	// mais validações
	// ...
	
	return true;
}

Os índices de $matches representam:

$matches[0] a string original
$matches[1] o protocolo
$matches[2] o domínio
$matches[3] a porta
$matches[4] o caminho

Com esses valores em mãos, podemos criar mais validações e também completar uma URL que tenha partes faltando, como o protocolo que é opcional.

function is_url(&$str) {
	if (!preg_match('#^([^/]*)//([^:/]+)(?::(\d+))?(.*)#', $str, $matches))
		return false;
	
	list(, $protocol, $host, $port, $path) = $matches;
	
	// protocolo
	if (!$procotol) {
		$procotol = 'http:';
		// adiciona o protocolo
		$str = $protocol . $str;
	}
	elseif (!in_array($protocol, ['http:', 'https:'])) {
		return false;
	}
	
	// mais validações
	// ...
	
	return true;
}

Um detalhe é que a URL precisa iniciar com pelo menos “//”. Podemos contornar isso e deixar as barras também opcionais. Sendo assim, URLs como site.com.br e site.com.br/diretorio passarão no teste.

function is_url(&$str) {
	// torna opcional a URL iniciar com http:// e //
	if (strpos($str, '/') === false || strpos($str, '/') > strpos($str, '.'))
		$str = 'http://'. $str;
	
	if (!preg_match('#^([^/]*)//([^:/]+)(?::(\d+))?(.*)#', $str, $matches))
		return false;
	
	list(, $protocol, $host, $port, $path) = $matches;
	
	// protocolo
	if (!$procotol) {
		$procotol = 'http:';
		// adiciona o protocolo
		$str = $protocol . $str;
	}
	elseif (!in_array($protocol, ['http:', 'https:'])) {
		return false;
	}
	
	// mais validações
	// ...
	
	return true;
}

strpos($url, '/') === false se não ter nenhuma barra na string, como “site.com.br”. Ou se tiver barra, mas ela estiver depois de ponto strpos($str, '/') > strpos($str, '.') para “site.com.br/caminho”. Nessas duas situações o protocolo é inserido no começo.

Se você quiser ou tiver que ir mais longe, você pode fazer uma conexão socket e verificar se o endereço responde.

function is_url(&$str) {
	// torna opcional a URL iniciar com http:// e //
	if (strpos($str, '/') === false || strpos($str, '/') > strpos($str, '.'))
		$str = 'http://'. $str;
	
	if (!preg_match('#^([^/]*)//([^:/]+)(?::(\d+))?(.*)#', $str, $matches))
		return false;
	
	list(, $protocol, $host, $port, $path) = $matches;
	
	// check and fix url parts
	if ($protocol == '') {
		$protocol = 'http:';
		$str = $protocol . $str;
	}
	
	if ($port == '')
		$port = $protocol == 'https:' ? 443 : 80;
	
	if ($path == '')
		$str .= $path = '/';
	
	// abre uma conexão socket
	$con = @fsockopen(($port == 443 ? 'ssl://' : '') . $host, $port, $ern, $ers);
	
	// erro na conexão
	if (!$con)
		return false;
	
	// cabeçalho de solicitação
	$request = [
		"GET {$path} HTTP/1.1",
		"Host: {$host}",
		"Connection: Close",
	];
	
	// envia a solicitação
	fwrite($con, implode(PHP_EOL, $request) . PHP_EOL);
	
	// recebe o status da resposta HTTP  (200, 404, 500)
	$status = substr(fgets($con), 9, 3);
	
	// encerra a conexão
	fclose($con);
	
	if ($status != 200)
		return false;
	
	return true;
}

Por outro lado, se não precisa aceitar todas essas variações é conveniente fixar na expressão regular o protocolo e validar apenas o formato.

#^https?://[^:/]+(?::\d+)?.*#

You may also like...