Apache URL書き換えルール

Apache URL書き換えルール

以下はApacheのmod_rewriteモジュールに関する技術的な解説です。 目次

  • 1、概要
  • 2、動作フロー
  • 3、URL書き換えディレクティブ
  • 1)、URL書き換えディレクティブの基本構造
  • 2)、RewriteRule パターン 置換文字列 [フラグ]
  • 3)、RewriteCond テスト文字列 条件パターン [フラグ]
  • 4)、Rewrite時のサーバー変数(主なもののみ)
  • 5)、基本的な正規表現ルール
  • 4、実例解説

1、概要

Apacheの書き換え機能、すなわちmod_rewriteモジュール機能は、Apacheのモジュールの一つです。この機能は非常に強力で、URLのすべての部分を操作できます。

これにより、URLを書き換えてユーザーに簡潔で分かりやすいURLを提供し、ユーザーがアクセスした際にmod_rewriteモジュール機能を通じて実際のリソースパスに変換することができます。mod_rewriteで実現できる機能は他にも多数あり、例えば実アドレスの隠蔽、URLリダイレクト、ドメイン転送、リンク盗用防止、アクセスリソースタイプの制限などがあります。

2、動作フロー

mod_rewriteモジュールは実行時に2つのフックプログラムを使用します。

最初のものはURLからファイル名への変換フックです。アクセスがApacheサーバーに到達すると、サーバーは対応するホスト(または仮想ホスト)を特定し、この時点でmod_rewriteモジュールが動作を開始します。モジュールはまずサーバーグローバルで設定されたmod_rewriteモジュールのディレクティブを処理し、次にユーザーが指定したディレクティブに基づいて書き換えを行います。

2つ目はURLの修正フックです。この段階では、mod_rewriteモジュールは非グローバルな設定を処理します。例えば、ディレクトリ内の.htaccessファイル内の設定などです。しかし、この時点ではすでにURLの変換(URLからファイル名への変換)が完了しているため、ディレクトリレベルのURLの書き換え操作は行えません。ただし、mod_rewriteモジュールは変換済みのURLを再びURLの状態に戻し、ディレクトリレベルのURL書き換えを続行します。(mod_rewriteモジュールは、読み取り後のリクエスト段階のコールバック関数を使用して、リクエストのループ処理を再開します)

Rewriteモジュールルールセットの処理

mod_rewriteがこれら2つのAPI段階で実行を開始すると、設定構造に設定された(サーバー起動時に作成されたサーバーレベルのもの、またはディレクトリ走査時に収集されたディレクトリレベルのもの)ルールセットを読み取り、次にURL書き換えエンジンを起動して(1つ以上の条件を持つ)ルールセットを処理します。サーバーレベルのものでもディレクトリレベルのものでも、同じURL書き換えエンジンで処理されますが、最終結果の処理方法が異なります。

ルールセット内のルールの順序は非常に重要です。なぜなら、書き換えエンジンは特殊な順序で処理を行うためです:各ルール(RewriteRuleディレクティブ)を順番に処理し、条件に一致するルールが現れると、既存のルール条件(RewriteCondディレクティブ)を後戻りして処理することがあります。歴史的な理由により、条件ルールは前置されているため、制御フローはやや複雑になります。詳細は図-1を参照してください。

図より、URLはまず各ルールのパターンと一致します。一致しない場合、mod_rewriteは即座にそのルールの処理を中止し、次のルールの処理に移ります。一致した場合、mod_rewriteは対応するルール条件を探します。条件が存在しない場合、単に置換文字列で構築された新しい値でURLを置換し、他のルールの処理を続けます。しかし、条件が存在する場合、内部ループを開始し、リストされた順序で順次処理を開始します。ルール条件の処理は異なります:URLはパターンと一致するのではなく、まず変数の展開、後方参照、マッピングテーブルの検索などの手順でTestString文字列を構築し、それをCondPatternと一致させます。一致しない場合、条件セット全体と対応するルールは失敗します。一致した場合、次の条件の処理に進み、すべての条件が完了するまで続けます。すべての条件が一致した場合、SubstitutionでURLを置換し、処理を続けます。(この部分は訳者:金歩国による引用です)

トップに戻る#### 3、URL書き換えディレクティブ

最も単純な書き換えディレクティブは、想像以上にシンプルです!

2つの手順で完了できます。まずRewriteEngineでmod_rewriteモジュール機能を有効にし、次にRewriteRuleでURL書き換えルールを定義します。

1)、URL書き換えディレクティブの基本構造

1 ---------------------------------------------------------------
2 RewriteEngine on   # mod_rewriteモジュール機能を有効化
3 RewriteBase パス     # 基準URL(aliasでエイリアスを設定する場合は必要)
4 RewriteCond テスト文字列 条件パターン [フラグ]      # 書き換え条件(複数可)
5 RewriteRule パターン 置換文字列 [フラグ]          # 書き換えルール
6 ----------------------------------------------------------------
7 # 4、5行は複数指定可能
8 # [フラグで終了しない場合は]順番にRewriteRuleを実行
9 ## 上記は一般的なディレクティブ、稀なものは各自で調査が必要

2)、RewriteRule パターン 置換文字列 [フラグ]

1、パターンは現在のURLに対するPerl互換の正規表現です。現在のURLとは、そのルールが有効になる時点でのURLの値です。これはリクエスト時のURLとは異なる可能性がありますなぜなら、以前に他のRewriteRuleまたはaliasディレクティブによって変更された可能性があるためです。

2、置換文字列は、URLがパターンと一致した場合に使用される代替文字列です。

  • パターンに対する後方参照$N(N=0~9)を使用でき、正規表式中のN番目の括弧内の内容を表します
  • 最後に一致したRewriteCondに対する後方参照%N(N=0~9)を使用でき、最後に一致したRewriteCondのN番目の括弧内の内容を表します
  • サーバー変数%{VARNAME}
  • マッピング関数呼び出し${mapname:key|default} (RewriteMapディレクティブで定義されたマッピングを補助的に使用)

3、[フラグ]はフラグで、複数ある場合はカンマで区切ります。

フラグ(ネットから引用):

redirect|R [=コード] (強制リダイレクト redirect)

http://thishost[:thisport]/で始まる置換文字列は、外部リダイレクトを強制実行します。コードが指定されていない場合、HTTP応答コード302(一時的な移動)が生成されます。300-400の範囲内の他の応答コードを使用する必要がある場合は、この値を指定するだけで済みます。また、次のいずれかのシンボル名を使用することもできます: temp(デフォルト)、permanent、seeother。これを使用して、規范化されたURLをクライアントに返すことができます。例えば、"~"を"/u/"に書き換えたり、"/u/user"にスラッシュを追加したりします。

注意: このマークを使用する場合、置換フィールドが有効なURLであることを確認してください!そうでない場合、無効な場所を指すことになります!そして、このマーク自体はURLにhttp://thishost[:thisport]/の接頭辞を追加するだけであり、書き換え操作は続行されることを忘れないでください。通常、書き換え操作を即座に停止して直ちにリダイレクトしたい場合は、'L'マークも使用する必要があります。

forbidden|F (URLを禁止にする forbidden)

現在のURLを禁止に強制し、即座にHTTP応答コード403(禁止)を返します。このマークを使用すると、いくつかのRewriteCondsをリンクして、特定のURLを条件付きでブロックできます。

gone|G(URLを廃止にする gone)

現在のURLを廃止に強制し、即座にHTTP応答コード410(廃止)を返します。このマークを使用すると、ページが廃止され存在しないことを示すことができます。

proxy|P (プロキシに強制する proxy)

このマークは、置換コンポーネントを内部でプロキシリクエストに強制し、すぐに(つまり、書き換えルールの処理が即座に中断され)処理をプロキシモジュールに移譲します。置換文字列が有効である(一般的にはhttp://hostnameで始まる)Apacheプロキシモジュールが処理できるURIであることを確認する必要があります。このマークを使用すると、一部のリモートコンポーネントをローカルサーバー名前空間にマッピングでき、これによりProxyPassディレクティブの機能が強化されます。

注意: この機能を使用するには、プロキシモジュールがApacheサーバーにコンパイルされている必要があります。確信が持てない場合は、"httpd -l"の出力にmod_proxy.cがあるか確認してください。存在する場合、mod_rewriteはこの機能を使用できます。存在しない場合は、mod_proxyを有効にして"httpd"プログラムを再コンパイルする必要があります。

last|L (最後のルール last)

書き換え操作を即座に停止し、他の書き換えルールを適用しません。これはPerlのlastコマンドまたはC言語のbreakコマンドに対応します。このマークは、現在書き換えられたURLがその後のルールによって書き換えられるのを防ぎます。例えば、ルートパスのURL('/')を実際に存在するURL(例えば'/e/www/')に書き換えるために使用できます。

next|N (次のラウンドを再実行 next round)

書き換え操作を再実行します(最初のルールから再開)。このとき、再度処理されるURLは元のURLではなく、最後の書き換えルールで処理されたURLです。これはPerlのnextコマンドまたはC言語のcontinueコマンドに対応します。このマークは書き換え操作を再開し、即座にループの先頭に戻ります。しかし、無限ループを作成しないように注意してください!

chain|C (次のルールに連鎖させる chained)

このマークは現在のルールを次のルール(それ自体が後続のルールと連鎖できるもの)に連鎖させます。次のような効果が生まれます: ルールが一致した場合、通常は後続のルールの処理が続行されます(つまり、このマークは効果を持ちません);ルールが一致しない場合、後続の連鎖されたルールは無視されます。例えば、外部リダイレクトを実行する際に、ディレクトリレベルのルールセットでは、".www"(ここでは".www"があってはならない)を削除する必要があるかもしれません。

type|T=MIME-type(MIMEタイプを強制する type)

ターゲットファイルのMIMEタイプをMIME-typeに強制します。例えば、mod_aliasのScriptAliasディレクティブを模倣するために、マッピングされたディレクトリ内のすべてのファイルのMIMEタイプを"application/x-httpd-cgi"に内部で強制するために使用できます。

nosubreq|NS (内部サブリクエストの処理をスキップ no internal sub-request)

現在のリクエストが内部サブリクエストである場合、このマークは書き換えエンジンにその書き換えルールをスキップさせます。例えば、mod_includeが可能なディレクトリデフォルトファイル(index.xxx)を検索しようとすると、Apacheは内部でサブリクエストを生成します。サブリクエストでは、必ずしも有用ではない場合があり、ルールセット全体が有効だと、エラーを引き起こすさえあります。そのため、このマークを使用して特定のルールを除外できます。

次の原則に従ってください: もしCGIスクリプトのURL接頭辞を使用して、それらがCGIスクリプトによって処理されるように強制し、サブリクエスト処理のエラー率(またはオーバーヘッド)が高い場合、このマークを使用できます。

nocase|NC (大文字小文字を無視する no case)

パターンで大文字小文字を無視させます。つまり、パターンが現在のURLと一致する際に、'A-Z'と'a-z'に違いがないことを意味します。

qsappend|QSA (クエリ文字列を追加する query string append)

このマークは、置換文字列を単純に置換するのではなく、既存の置換文字列にクエリ文字列を追加するように書き換えエンジンに強制します。書き換えルールを使用してクエリ文字列に情報を追加する必要がある場合に使用できます。

noescape|NE (出力でURIをエスケープしない no URI escaping)

このマークは、mod_rewriteが書き換え結果に通常のURIエスケープルールを適用するのを防ぎます。通常、特殊文字('%'、'$'; ';'など)は等価の16進エンコーディングにエスケープされます。このマークはそのようなエスケープを防ぎ、出力にパーセント記号などの記号を許可します。例えば:

RewriteRule /foo/(.*) /bar?arg=P1=$1 [R,NE] は '/foo/zed' を安全なリクエスト '/bar?arg=P1=zed' にリダイレクトさせます。

passthrough|PT (次のプロセッサに渡す pass through)

このマークは、書き換えエンジンに内部構造request_recのuriフィールドをfilenameフィールドの値に設定させます。これは、他のURIからファイル名への変換器(Alias、ScriptAlias、Redirectなど)の出力を後処理できるようにするための小さな変更です。その意味を説明する例:mod_rewriteの書き換えエンジンを使用して/abcを/defに書き換え、次にmod_aliasを使用して/defを/ghiに変換するには、次のようにします:

RewriteRule ^/abc(.*) /def$1 [PT]Alias /def /ghi PTマークを省略してもmod_rewriteは正常に動作します。つまり、URIからファイル名への変換器としてAPIを使用して、uri=/abc/...をfilename=/def/...に書き換えることができます。しかし、後続のmod_aliasがURIからファイル名への変換を試行すると失敗します。

注意: 異なるURIからファイル名への変換器モジュールを混合して使用する必要がある場合は、このマークを使用する必要があります。mod_aliasとmod_rewriteを混合使用する典型的な例です。

For Apache hackers

もし現在のApache APIにURIからファイル名へのフックに加えて、ファイル名からファイル名へのフックがある場合、このマークは不要です!しかし、そのようなフックがない場合、このマークは唯一の解決策です。Apacheグループはこの問題について議論し、Apache 2.0バージョンでこのフックを追加する予定です。

skip|S=num (後続のルールをスキップする skip)

このマークは、書き換えエンジンに現在一致したルールの後続のnum個のルールをスキップさせます。これは疑似if-then-else構造を実現できます:最後のルールがthen節であり、スキップされたskip=N個のルールがelse節です。(これは'chain|C'マークとは異なります!)

env|E=VAR:VAL (環境変数を設定する environment variable)

このマークは環境変数VARの値をVALに設定します。VALには拡張可能な後方参照の正規表現$Nと%Nを含めることができます。このマークは複数回使用して複数の変数を設定できます。これらの変数はその後の多くの場合で間接的に参照できますが、通常はXSSI(via)またはCGI(例:$ENV{'VAR'})で、また後続のRewriteCondディレクティブのパターンで%{ENV:VAR}を使用して参照できます。これを使用してURLから情報を抽出して記憶することができます。

cookie|CO=NAME:VAL:domain[:lifetime[:path]] (クッキーを設定する)

これはクライアントブラウザにクッキーを設定します。クッキーの名前はNAMEで、その値はVALです。domainフィールドはそのクッキーのドメインです(例えば'.apache.org')。オプションのlifetimeはクッキーの有効期間(分)です。オプションのpathはクッキーのパスです。

3)、RewriteCond テスト文字列 条件パターン [フラグ]

RewriteCondディレクティブはルール条件を定義します。RewriteRuleディレクティブの前に1つまたは複数のrewritecondディレクティブが存在することができ、自身のテンプレートが一致成功し、これらの条件も満たされた場合(つまりRewriteRuleのパターンが一致成功した場合)、ルール条件が現在のURL処理に適用されます。

1、テスト文字列はプレーンテキストの文字列です

  • パターンに対する後方参照$N(N=0~9)を使用でき、RewriteCondに続くRewriteRuleの正規表式中のN番目の括弧内の内容を表します
  • 後方参照%N(N=0~9)を使用でき、RewriteCondの条件パターン中のN番目の括弧内の内容を表します
  • サーバー変数%{VARNAME}

2、条件パターンは条件パターンで、現在のインスタンスのテスト文字列に適用される正規表現です。つまり、テスト文字列と条件パターンの条件が一致します。一致した場合はRewriteCondの値はTrueで、一致しない場合はFalseです。

次の特殊変数を使用できます('!'を使用して反転できます):

'>CondPattern' (より大きい) 条件パターンを通常の文字列として扱い、それとテスト文字列を比較し、テスト文字列の文字が条件パターンより大きい場合に真となります。

'=CondPattern' (等しい) 条件パターンを通常の文字列として扱い、それとテスト文字列を比較し、テスト文字列が条件パターンと完全に一致する場合に真となります。条件パターンが""(二つの引用符が隣接)の場合、テスト文字列が空文字列である場合に真となります。

'-d' (ディレクトリか) テスト文字列をディレクトリ名として扱い、それが存在し、かつディレクトリであるかどうかをチェックします。

'-f' (通常ファイルか) テスト文字列をファイル名として扱い、それが存在し、かつ通常ファイルであるかどうかをチェックします。

'-s' (長さが0でない通常ファイルか) テスト文字列をファイル名として扱い、それが存在し、かつ長さが0より大きい通常ファイルであるかどうかをチェックします。

'-l' (シンボリックリンクか) テスト文字列をファイル名として扱い、それが存在し、かつシンボリックリンクであるかどうかをチェックします。

'-F' (サブリクエストでファイルがアクセス可能か) テスト文字列が有効なファイルであり、サーバー全体の現在の設定によるアクセス制御でアクセス可能かどうかをチェックします。このチェックは内部サブリクエストで行われるため、この機能を使用する際はサーバーのパフォーマンス低下に注意してください。

'-U' (サブリクエストでURLが存在するか) テスト文字列が有効なURLであり、サーバー全体の現在の設定によるアクセス制御でアクセス可能かどうかをチェックします。このチェックは内部サブリクエストで行われるため、この機能を使用する際はサーバーのパフォーマンス低下に注意してください。

3、[フラグ]は3番目のパラメータで、複数のフラグはカンマで区切ります。

'nocase|NC' (大文字小文字を区別しない) 拡張されたテスト文字列と条件パターンで、比較時にテキストの大文字小文字を区別しません。注意、このフラグはファイルシステムとサブリクエストチェックには影響しません。

'ornext|OR' (次の条件とのOR関係を設定する) デフォルトでは、2つの条件間はAND関係ですが、このフラグを使用して関係をORに変更します。

トップに戻る#### 4)、Rewrite時のサーバー変数(主なもののみ)

HTTP headers:HTTP_USER_AGENT, HTTP_REFERER, HTTP_COOKIE, HTTP_HOST, HTTP_ACCEPT

connection & request:REMOTE_ADDR, QUERY_STRING

server internals:DOCUMENT_ROOT, SERVER_PORT, SERVER_PROTOCOL

system stuff: TIME_YEAR, TIME_MON, TIME_DAY

5)、基本的な正規表現ルール

. 任意の1文字に一致

[chars] 文字列charsに一致

[^chars] 文字列charsに一致しない

text1|text2 選択可能な文字列:text1またはtext2

? 0から1文字に一致

  • 0から複数文字に一致
  • 1から複数文字に一致

^ 文字列の開始記号

$ 文字列の終了記号

\n エスケープ文字記号

【注意】:1世代のApacheはURLにスラッシュを必要としますが、2世代のApacheでは許可しないため、^/?を使用します。

4、実例解説

例1(簡単な例):

(.htaccessでルールを書き換える)

RewriteEngine ON 
RewriteRule  ^user/([a-zA-Z0-9_-]+)/?$ user.php?id=$1

^:入力の先頭 user/で始まるリクエストURL

([a-zA-Z0-9_-]+):すべての英数字とアンダースコア、ハイフンを抽出し、$1に渡す

/?:オプションのスラッシュ

$:終了記号

置換先:user.php?id=*

注意:一部のApache(具体的なバージョンは忘れました)は短縮モード w+ => [a-zA-Z_-] と互換性がありません。

例2(IEとOperaブラウザのアクセスを禁止):

RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} MSIE [NC,OR]
RewriteCond %{HTTP_USER_AGENT} Opera [NC]
RewriteRule ^.* - [F,L]       # '-'はURLを置換しないことを意味します

例3(不正なパスの場合にホームページにリダイレクト):

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]

例4(リンク盗用防止):

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://(.+.)?mysite.com/ [NC]       # 自ドメインからのリクエストかどうかを判断
RewriteCond %{HTTP_REFERER} !^$                     #{HTTP_REFERER}が空でないこと
RewriteRule .*.(jpe?g|gif|bmp|png)$ /images/nohotlink.jpg [L]      # 警告画像を返す

例5(アクセスURLのディレクトリ名を変更):

つまり、実際のディレクトリ名を隠蔽します

RewriteEngine On
RewriteRule ^/?old_dir/([a-z\.]+)$ new_dir/$1 [R=301,L]
# new_dirが実際のディレクトリ

例6(ファイル拡張子のないリンクを作成):

RewriteEngine On
RewriteCond %{REQUEST_FILENAME}.php -f # 指定された拡張子のファイルが存在するか確認
RewriteRule ^/?([a-zA-Z0-9]+)$ $1.php [L]
RewriteCond %{REQUEST_FILENAME}.html -f # 指定された拡張子のファイルが存在するか確認
RewriteRule ^/?([a-zA-Z0-9]+)$ $1.html [L]

例7(画像のみの表示を許可):

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !^.*\.(gif|jpg|jpeg|png|swf)$
RewriteRule .*$ - [F,L]

例8(ファイルが存在しない場合に404にリダイレクト):

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !f
RewriteCond %{REQUEST_FILENAME} !d
RewriteRule .? /404.php [L]

例9 (エントリーファイルのリダイレクト)URLを書き換えるが、ブラウザには反映させない

通常の書き換えでは、書き換え後のURLがブラウザに反映されます。プロキシ書き換えを使用すると、この問題を回避できます。

この例は、私の実際のプロジェクトでのケースです。

当時の要件は次の通りでした。

当時のPHPプロジェクトはFuelPHPフレームワークを使用しており、ほとんどのURLリクエスト(CSS/JS/画像などの通常のリソースリクエストを除く)がエントリーファイルindex.phpで処理されていました。一部のリクエストにはBasic認証が必要で、一部は不要だったため、index.phpをコピーしてindex2.phpを作成しました。つまり、2つのエントリーファイルがありました。その中でindex.phpにはBasic認証が必要で、index2.phpには不要でした。index.phpはデフォルトのエントリーファイルで、URLでindex.phpを省略した場合、デフォルトでindex.phpで処理されます。

index.phpで処理されるURL: http://localhost/aa/bb (http://localhost/index.php/aa/bbと同等)

index2.phpで処理されるURL:http://localhost/index2.php/cc/dd

当時の要件は、URLからindex2.phpを削除し、かつBasic認証不要にすることでした。つまり、index.phpをエントリーファイルとして使用してはなりませんでした。そのため、Apacheの書き換え機能を使用しました。index2.phpを削除したURLリクエストをindex2.phpに書き戻します。しかし、通常の書き換えでは、ブラウザのURLにindex2.phpが表示されてしまうため、プロキシ([P])の方法を使用して、書き換え後のURLがブラウザに反映されないようにしました。

書き換え不要なURLを条件としてリストアップし、条件以外を書き換える

RewriteEngine on RewriteCond %{REQUEST_URI} !^/file/.* # 通常のリソースリクエストはエントリーファイルを経由せず、書き換え不要。http://localhost/file/test.txt

RewriteCond %{REQUEST_URI} !^/sync/.* # 通常のリソースリクエストはエントリーファイルを経由せず、書き換え不要。http://localhost/sync/test.txt RewriteCond %{REQUEST_URI} !^/assets/.* # 通常のリソースリクエストはエントリーファイルを経由せず、書き換え不要。http://localhost/assets/css/a.css RewriteCond %{REQUEST_URI} !^/v[1-3]/.* # APIリクエストはBasic認証が必要で、デフォルトでindex.phpエントリーファイルで処理。書き換え不要。http://localhost/v1/search/search(http://localhost/index.php/v1/search/searchと同等)

RewriteCond %{REQUEST_URI} !^/index.php/.* # エントリーファイルが指定されている場合は書き換え不要 http://localhost/index.php/test1 RewriteCond %{REQUEST_URI} !^/index2.php/.* # エントリーファイルが指定されている場合は書き換え不要 http://localhost/index2.php/test2 RewriteRule (.*) %{REQUEST_SCHEME}://%{SERVER_NAME}/index2.php/$1 [P] # # その他、エントリーファイルが省略されたURLはすべてindex2.phpに書き換える # [P]はプロキシ方式でURLを書き換え、これにより書き換え後のURLがブラウザに反映されない

注意、プロキシ書き換えを使用するには、対応するモジュールをロードする必要があり、書き換え文字列はhttp://から始まる完全なURLである必要があります。上記の%{REQUEST_SCHEME}://%{SERVER_NAME}/index2.php/$1のように。

LoadModule proxy_module         modules/mod_proxy.so
LoadModule proxy_http_module  modules/mod_proxy_http.so

さらに、プロキシ書き換えを使用した後、FuelPHPフレームワークの関係で、CSSなどのリクエストのホスト名が書き換え後のホスト名に変わってしまいます。不要なトラブルを避けるため、サーバーの仮想ホスト設定(/etc/hosts)はローカルと一致させる必要があります。

タグ: Apache mod_rewrite URL書き換え .htaccess 正規表現

6月4日 20:42 投稿