Delphi の URLEncode は罠が多い


URLEncode の方法

URLEncode は2種類ある

ちょっと古い資料を見ると

System.Net.URLClient.TURI.URLEncode

を使うと出てたりします。
例えば、↓こんな感じです。

TURI.URLEncode
program Project1;

uses
  System.Net.URLClient;

begin
  Writeln(TURI.URLEncode('Hello, Delphi!'));
  // 出力:Hello%2C%20Delphi%21
  Readln;
end.

で、それに従って TURI.URLEncode を使うと

[dcc32 警告] Project1.dpr(7): W1000 シンボル 'URLEncode' を使用することは推奨されていません : 'Use TNetEncoding.URL.Encode'

と、警告が出ます。
警告は消した方がいいよなーということで、警告に従って TNetEncoding.URL.Encode を使うようにしてみます。

TNetEncoding.URL.Encode
program Project1;

uses
  System.NetEncoding;

begin
  Writeln(TNetEncoding.URL.Encode('Hello, Delphi!'));
  // 出力:Hello%2C+Delphi!
  Readln;
end.

警告も出ませんし、上手くいったように見えますが…
出力結果が変わっています!

TURI.URLEncode
Hello%2C%20Delphi%21
TNetEncoding.URL.Encode
Hello%2C+Delphi!

空白が「%20」ではなく「+」になっています。

ちなみに、Docwiki の System.NetEncoding.TURLEncoding の文章は完全に日本語がおかしく

TURLEncoding は、スペース(プラス記号 + として)と、次の予約された URL エンコード文字のみをサポートします: ;:&=+,/?%#[]。 TURLEncoding は、プラス記号(スペースとして)と、どんなパーセント エンコード文字(%2A や %41 など)のデコードもサポートします。

意味が通じません。
日本語に直すと「空白は + 記号になる。また、;:&=+,/?%#[] 文字もエンコードされる」と書いてあります。

しかし、空白を「+」とすると正しく動かない処理もあります。

できれば、空白を「%20」にするか「+」にするか選べるといいですよね。

本当は2種類どころか、もっとある

先ほどの、TNetEncoding.URL.Encode は TURLEncoding.DoEncode を呼び出しますが、そこでは決め打ちで空白は「+」に変換しています。
↓こうなっています。

else if Sp^ = ' ' then
begin
  Rp^ := '+';
  Inc(Rp)
end

では、どうすればいいかというと TURLEncoding.Encode の別バージョンを呼び出せばOKです。

program Project1;

uses
  System.NetEncoding;

begin
  Writeln(TNetEncoding.URL.Encode('Hello, Delphi!', [], []));
  Readln;
end.

TNetEncoding.URL.Encode は overload されたメソッドがあり、上記のように引数を指定すると別バージョンの Encode メソッドが呼ばれます。
別バージョンの TURLEncoding.Encode は第2引数に追加で %xx に変更する文字、第3引数で TEncodeOption を指定できます。
なお、TEncodeOption は下記の様に定義されています。

TEncodeOption = (SpacesAsPlus, EncodePercent);
  • SpacesAsPlus は、空白を「+」に変換します。
  • EncodeParcent は、「%」自身も変換するかどうかを示します。

今回は、空白を %20 にするだけだったので、第2, 第3引数は何も無しです。

また、先ほど「正しく動かない処理もある」と書きました。
なので、処理ごとにメソッドが用意されてればいいなあと思いますよね。
TURLEncoding には、ちゃんと用途ごとのメソッドも用意されています。
それがこちら

function EncodePath(const APath: string; const ExtraUnsafeChars: TUnsafeChars = []): string;
function EncodeAuth(const Auth: string; const ExtraUnsafeChars: TUnsafeChars = []): string; inline;
function EncodeQuery(const AQuery: string; const ExtraUnsafeChars: TUnsafeChars = []): string; inline;
function EncodeForm(const Input: string; const ExtraUnsafeChars: TUnsafeChars = []): string; inline;

です。
名前から何使うか解ると思います(DocWiki にはドキュメントが存在しません…)
コレを使って、先ほどのコードは、こうも書けます。

program Project1;

uses
  System.NetEncoding;

begin
  Writeln(TNetEncoding.URL.EncodeQuery('Hello, Delphi!'));
  Readln;
end.

まとめ

いついかなる時も TNetEncoding.URL.Encode は引数を指定するか、用途ごとの Encode メソッドを使うようにしよう!