「URL 判定」で本当にやりたいことは何か
まず、「URL 判定」と聞くと、
「正規表現で http から始まるかどうかをチェックする」
みたいなイメージを持ちがちですが、それだとだいたい痛い目を見ます。
業務で本当にやりたいのは、だいたい次のようなことです。
「ユーザーが入力した文字列が、“URLとして扱ってよさそうか”をざっくり判定したい」
「明らかにおかしいもの(aaaa とか)は弾きたい」
ここで大事なのは、“完璧な URL 判定”を目指さないことです。
URL の仕様はかなり複雑なので、正規表現だけで完全にカバーしようとすると、ほぼ確実に破綻します。
そこで、JavaScript ならではの「いい感じの落としどころ」を使います。
正規表現より先に「URL クラス」を使う発想を持つ
URL コンストラクタで「パースできるか」を見る
モダンな JavaScript には URL という組み込みクラスがあります。
これを使うと、「その文字列が URL としてパースできるか」を簡単にチェックできます。
function isUrl(value) {
if (value == null) return false;
const s = String(value).trim();
if (s === "") return false;
try {
const url = new URL(s);
return url.protocol === "http:" || url.protocol === "https:";
} catch {
return false;
}
}
JavaScript重要なポイントを噛み砕きます。
null / undefined は即 false。
前後の空白を削って、空文字なら false。new URL(s) で「URL として解釈できるか」を試す。
解釈できなければ例外が飛ぶので、catch して false を返す。
解釈できた場合でも、http: と https: だけを許可する(javascript: などは危険なので弾く)。
これで、「http/https の URL として妥当かどうか」を、かなりいい感じに判定できます。
動きのイメージ
isUrl("https://example.com"); // true
isUrl("http://example.com/path"); // true
isUrl("ftp://example.com"); // false(ftp は許可していない)
isUrl("javascript:alert(1)"); // false(危険なので弾く)
isUrl("not a url"); // false
isUrl(" https://example.com "); // true(前後の空白は無視)
JavaScript「URL クラスでパースできるか」を基準にしているので、
自前で複雑な正規表現を書くより、ずっと安全で現実的です。
「URL クラスが使えない環境」や「ざっくりチェック」用の簡易版
最低限の「それっぽさ」だけを見る正規表現
どうしても URL クラスが使えない環境(古いブラウザなど)や、
「とりあえずざっくりチェックしたいだけ」という場合は、
シンプルな正規表現で妥協するのも一つの手です。
function isUrlLoose(value) {
if (value == null) return false;
const s = String(value).trim();
if (s === "") return false;
const re = /^https?:\/\/[^\s/$.?#].[^\s]*$/i;
return re.test(s);
}
JavaScriptこの正規表現の意味をざっくり説明すると、
^https?:// … http:// または https:// で始まる[^\s/$.?#]. … その後に「空白や一部の記号以外の文字」が続く[^\s]* … 以降は空白以外の文字が続いてもよい$ … 文字列の終わりまで
という感じで、「http/https で始まっていて、最低限それっぽい形」を見ています。
ただし、これはあくまで「ゆるいチェック」です。
本当に厳密にやりたいときは、やはり URL クラスを使うほうが良いです。
業務での具体的な使いどころ
フロントエンドの入力チェック
例えば、「Web サイトの URL を入力してください」というフォームがあるとします。
const url = urlInput.value;
if (!isUrl(url)) {
showError("URL の形式が正しくありません。http:// または https:// から始まる URL を入力してください。");
}
JavaScriptここでのポイントは、
「形式が正しいからといって、その URL が本当に存在するとは限らない」
「形式が少し変でも、実際には有効な URL の可能性もある」
ということを理解したうえで、
「入力ミスを減らすためのガイド」として使うことです。
バックエンド側の軽いバリデーション
API で URL を受け取るときにも、
最低限の形式チェックをしておくと、
明らかにおかしいデータが DB に入るのを防げます。
function validateUrlOrThrow(value) {
if (!isUrl(value)) {
throw new Error("Invalid URL format");
}
}
JavaScriptただし、「その URL に実際にアクセスできるかどうか」は別問題なので、
必要なら別のプロセス(疎通確認など)で扱います。
セキュリティ的に絶対に意識してほしいポイント
「URL なら何でも OK」にはしない
URL クラスを使うと、javascript: や data: などのスキームもパースできます。
new URL("javascript:alert(1)");
JavaScriptこれをそのまま「有効な URL」とみなしてしまうと、
XSS(クロスサイトスクリプティング)などの危険につながります。
だからこそ、さっきの isUrl では、
url.protocol === "http:" || url.protocol === "https:"
JavaScriptというチェックを入れていました。
「URL としてパースできるか」と
「安全なスキームかどうか」は別問題です。
画面にリンクとして出す、window.open で開く、
などの用途では、必ずスキームを絞り込んでください。
設計として意識してほしいこと
「判定」と「正規化」と「利用」を分ける
URL 周りの処理は、だいたい次の3段階に分けられます。
文字列としての入力を受け取る
URL として妥当かどうかを判定する(isUrl)
実際に使う(リンクとして表示する、リダイレクトに使うなど)
これを一つの関数にごちゃっと詰め込むと、
「この関数、何をどこまでやるんだっけ?」と分かりづらくなります。
なので、
isUrl … 「http/https の URL として妥当かどうか」を判定するisUrlLoose … 「ざっくり URL っぽいかどうか」を見る簡易版
実際の利用(リンク生成など)は別のレイヤーで行う
というふうに、役割ごとに関数を分けておくと、
テストもしやすいし、仕様変更にも強くなります。
「どのレベルの厳しさが必要か」をチームで決める
URL 判定の厳しさは、プロダクトごとに違います。
社内ツールで、信頼できるユーザーしか使わないのか。
一般公開サービスで、誰でも入力できるのか。
URL をそのまま画面にリンクとして出すのか。
これによって、
isUrl で http/https だけ許可するisUrlLoose でざっくりチェックするだけにする
そもそも URL 判定はせず、後段でだけ使う
など、設計が変わってきます。
ちょっとだけ手を動かしてみる
コンソールで、次のあたりを試してみてください。
isUrl("https://example.com");
isUrl("http://example.com/path");
isUrl("ftp://example.com");
isUrl("javascript:alert(1)");
isUrl("not a url");
isUrlLoose("https://example.com");
isUrlLoose("http://example.com/path");
isUrlLoose("not a url");
JavaScript「どこからが true で、どこからが false になるか」を体で感じてみてください。
そのうえで、自分のプロジェクトに
export function isUrl(value) { ... }
export function isUrlLoose(value) { ... }
JavaScriptのようなユーティリティを一つ置いて、
「URL っぽいかどうかを見たいときは、必ずここを通す」
というルールにしてみてください。
それができた瞬間、あなたの検証処理は
「その場しのぎの正規表現」から
「意図を持って設計された URL 判定ユーティリティ」に、一段レベルアップします。
