PHPでURLかどうかを判定し処理を分岐する方法|filter_varの注意点とhttp/https限定

ユーザー入力の検証や、テキスト中のリンク処理などで、文字列がURLかどうかを判定したい場面はよくあります。PHPでは filter_var() で手軽に判定できますが、「スキームが必須」「javascript:// なども通る」といった知っておくべき落とし穴があります。この記事では基本の判定から、安全に http/https だけに絞る方法までまとめます。

この記事の結論:URL判定は filter_var($url, FILTER_VALIDATE_URL) が基本です。ただし形式しか見ないため、javascript:// なども通ります。ユーザー入力をリンクに使うなら、parse_url() でスキームを取り出し http / https だけに限定すると安全です。
スポンサーリンク

filter_varで判定する(基本)

もっとも簡単なのが filter_var()FILTER_VALIDATE_URL を指定する方法です。URLとして妥当なら値を、そうでなければ false を返します。

filter_varでURL判定
<?php
$url = "http://example.com";

if (filter_var($url, FILTER_VALIDATE_URL)) {
    echo "URLです";
} else {
    echo "URLではありません";
}
filter_var() が見るのは形式だけです。そのURLが実際に存在するか・アクセスできるかは判定しません。到達性まで確認したい場合は、別途 get_headers() やcURLでアクセスを試みます。

知っておきたいfilter_varの落とし穴

入力例 FILTER_VALIDATE_URL 備考
http://example.com 有効 正常
example.com 無効 スキーム(http://)が必須
javascript://alert(1) 有効 危険スキームも通る
ftp://example.com 有効 http以外も通る
https://日本語.jp 無効 日本語ドメイン(IDN)は通らない
「URLとして妥当=安全」ではありません。filter_var()javascript://data: なども形式的にURLとみなすことがあります。判定結果をそのまま <a href> に出すと、javascript: によるXSSの温床になります。ユーザー入力は次の方法で http / https に限定しましょう。

http / https だけに安全に絞り込む

parse_url() でスキームを取り出し、http / https のときだけ許可するようにします。リンクとして出力する用途では、この一手間が重要です。

http/httpsのみ許可する判定
<?php
function isSafeHttpUrl(string $url): bool {
    if (filter_var($url, FILTER_VALIDATE_URL) === false) {
        return false;
    }
    $scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME));
    return in_array($scheme, ["http", "https"], true);
}

var_dump(isSafeHttpUrl("https://example.com")); // true
var_dump(isSafeHttpUrl("javascript://alert(1)")); // false
var_dump(isSafeHttpUrl("ftp://example.com"));   // false
URLの各部(scheme・host・path・query)を取り出したいときも parse_url() が便利です。メールアドレスの判定はメールアドレスかどうかを判定し処理を分岐する方法で同様に解説しています。

正規表現(preg_match)で判定する

独自のルールで判定したい場合は preg_match() も使えます。ただし正規表現の作り方次第で filter_var() と判定が食い違う点に注意してください(例: スキームを任意にすると example.com も通ってしまう)。

preg_matchでhttp/httpsのURLを判定
<?php
$url = "http://example.com/path";
$pattern = "~^https?://[^\s/$.?#][^\s]*$~i";

if (preg_match($pattern, $url)) {
    echo "URLです";
} else {
    echo "URLではありません";
}
正規表現での文字列パターン判定全般は文字列内の特定の文字やパターンを確認する方法を参照してください。

判定結果で処理を分岐する

判定結果に応じて、リンク化・リダイレクト・エラー表示などに分岐します。

URLなら安全にリンク化、違えばそのまま表示
<?php
if (isSafeHttpUrl($input)) {
    // htmlspecialchars でエスケープしてから出力
    $safe = htmlspecialchars($input, ENT_QUOTES, "UTF-8");
    echo "<a href=\"{$safe}\" rel=\"noopener\" target=\"_blank\">{$safe}</a>";
} else {
    echo htmlspecialchars($input, ENT_QUOTES, "UTF-8");
}
判定後にページ遷移させたい場合の方法はリダイレクトを行う方法まとめで解説しています。

よくある質問(FAQ)

Qexample.com(http://なし)がURL判定で弾かれます。
AFILTER_VALIDATE_URLスキーム(http:// など)が必須だからです。スキームなしも許可したい場合は、事前に http:// を補ってから判定するなどの工夫が必要です。
Qfilter_varで通ったURLをそのままリンクにして大丈夫ですか?
A危険です。javascript://data: なども形式的に通るため、そのまま href に出すとXSSの恐れがあります。parse_url() でスキームを取り出し、http / https のみ許可し、出力時は htmlspecialchars() でエスケープしてください。
Q日本語ドメイン(IDN)が判定できません。
Afilter_var() は非ASCIIのドメインを通しません。日本語ドメインを扱うなら、idn_to_ascii() でPunycode(例: xn--…)に変換してから判定します。
QURLが実在するかも確認したいです。
Afilter_var() は形式しか見ません。実在・到達性を確認するには get_headers() やcURLで実際にアクセスし、ステータスコードを確認します(タイムアウトやSSRF対策にも配慮してください)。

まとめ

PHPでのURL判定のポイントを整理します。

  • 基本は filter_var($url, FILTER_VALIDATE_URL)
  • スキーム必須example.com は無効)
  • javascript:// なども通るため、リンク用途は http / https に限定
  • 限定は parse_url() でスキームを取り出して in_array で判定
  • 日本語ドメインは idn_to_ascii() で変換、出力は htmlspecialchars()

関連として、メールアドレスの判定文字列パターンの確認リダイレクトを行う方法もあわせて読むと、入力検証と分岐処理に強くなれます。