1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

正規表現の /g フラグはサーバーで使うと怖いことがあるという話

Last updated at Posted at 2022-03-20

まず、次のコードを見てください。

const http = require('http');
const url = require('url');


const regex = /^hoge$/g;
const server = http.createServer((req, res) => {
    if (regex.test('hoge')) {
        res.end('hoge', 'utf8')
    } else {
        res.end('hogeじゃない', 'utf8');
    }
});
server.listen(8000);

この Node.js 向けのコードを実行すると HTTP サーバーが起動します。
どんなレスポンスが返ってくるでしょうか?

% curl localhost:8000
hoge
% curl localhost:8000
hogeじゃない
% curl localhost:8000
hoge
% curl localhost:8000
hogeじゃない
% curl localhost:8000
hoge
% curl localhost:8000
hogeじゃない

1回ごとにレスポンスが変わります。
なぜこのようなレスポンスになるかというと、 regex 変数に保持している正規表現のオブジェクトが状態を保持しているためです
以下、MDN からの引用です。

正規表現にグローバルフラグが設定されている場合、 test() は正規表現が所有する lastIndex の値を加算します。 (exec() も同様に lastIndex プロパティの値を加算します。)
その後にさらに test(str) を呼び出すと、 str を lastIndex から検索します。 lastIndex プロパティは test() が true を返すたびに増え続けます。

test() が false を返した場合、正規表現の lastIndex プロパティを呼び出すと 0 にリセットされます。

はじめのコードでは、regex 変数の正規表現のオブジェクトに /g とグローバルフラグが指定されているため、リクエストを受けるたびに regex.test() が実行され lastIndex が更新されます。
初回のリクエストでは regex.lastIndex = 0 なので想定通りに hoge という文字列に正規表現がマッチします。
しかし 2 回目のリクエストでは regex.lastIndex = 4 になっているため hoge の 4 文字目から hoge を探し始め正規表現がマッチしません。そして、マッチしないために regex.lastIndex が 0 に戻ります。

少しコードを書き足して確認してみると、以下のように lastIndex が 0 / 4 と切り替わっていることがわかります。

const http = require('http');
const url = require('url');

const regex = /^hoge$/g;
const server = http.createServer((req, res) => {
    if (regex.test('hoge')) {
        res.end(`hoge lastIndex: ${regex.lastIndex}`, 'utf8')
    } else {
        res.end(`hogeじゃない lastIndex: ${regex.lastIndex}`, 'utf8');
    }
});
server.listen(8000);
% curl localhost:8000
hogeじゃない lastIndex: 0
% curl localhost:8000
hoge lastIndex: 4
% curl localhost:8000
hogeじゃない lastIndex: 0
% curl localhost:8000
hoge lastIndex: 4

常に同じ判定をさせるためには、正規表現の後ろの /g で指定している global フラグを使用しない、lastIndex を実行のたびに 0 に戻す、正規表現オブジェクトのスコープを絞るなどの対策が必要です。

以下のように global フラグをはずすだけで常に同じレスポンスを返すようになります。

const http = require('http');
const url = require('url');


const regex = /^hoge$/;
const server = http.createServer((req, res) => {
    if (regex.test('hoge')) {
        res.end(`hoge lastIndex: ${regex.lastIndex}`, 'utf8')
    } else {
        res.end(`hogeじゃない lastIndex: ${regex.lastIndex}`, 'utf8');
    }
});
server.listen(8000);
% curl localhost:8000
hoge lastIndex: 0
% curl localhost:8000
hoge lastIndex: 0
% curl localhost:8000
hoge lastIndex: 0
% curl localhost:8000
hoge lastIndex: 0
% curl localhost:8000
hoge lastIndex: 0

というわけで、正規表現オブジェクトは状態を持つので気をつけないといけないよ、ということを最近改めて思い出したのでメモを残しておきます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?