LoginSignup
1
1

More than 5 years have passed since last update.

不透明URIのためのSystem.UriParserの派生クラスを実装する場合の注意点

Last updated at Posted at 2015-11-03

本記事の内容は,.Net Framework 4.6で動作確認をしています.

結論

不透明URIに対しては,System.GenericUriParserを基底クラスにするのではなく,System.UriParserを基底クラスにするのがよい.

new Uri("data:,%30")の結果がおかしい

まず最初に書いておかないといけないが,System.Uriはdata URIスキームをサポートしていない.

ただ,サポートしていないURIスキームであっても,何となく処理してくれる.

var uri = new Uri("data:,0");
Console.WriteLine(uri);
// stdout> data:,0

ところが,パーセントエンコードするとおかしくなる.

// 以下のコードには,パーセントエンコードを実行したときの文字エンコーディングが何だったのかという問題があるが,
// ひとまず無視しておく
// (ちなみに,C#はUTF-8だと思ってパーセントエンコードをデコードするようだ)
var uri = new Uri("data:,%30");
Console.WriteLine(uri);
// stdout> data:%30,0

単なるバグだと思うが,どういう理屈でこのような結果になるのか理解に苦しむ..

やはり非サポートURIスキームなのに,無理やりSystem.Uriに食わせたのがよくない.仕方ないのでちゃんと実装しよう..

どうやって実装するんだ?

System.UriParser.Register()を使う.
https://msdn.microsoft.com/ja-jp/library/system.uriparser.register%28v=vs.110%29.aspx

これらをキーワードにGoogle検索すると,少ないが情報が見つかる.

  1. http://www.ageektrapped.com/blog/writing-your-own-uriparser
  2. http://www.ageektrapped.com/blog/writing-your-own-uriparser-part-2-implementing-getcomponents/
  3. http://www.codeproject.com/Articles/13773/Writing-a-custom-UriParser-for-NET

1と2は続き物で,3がその集大成と言ったところか.どちらの場合もSystem.GenericUriParserを基底クラスとするサンプルが書かれている.

しかし,data URIのような不透明URIを追加する場合,System.GenericUriParserを基底クラスにするのは適切ではない.なぜならば,本家のAPIドキュメントにこう書かれているからだ.
https://msdn.microsoft.com/ja-jp/library/system.genericuriparser%28v=vs.110%29.aspx

A customizable parser for a hierarchical URI

System.GenericUriParserは階層URIに対するものだと明確に書かれている.実際,以下のようなコードを書いて

class DataUriParser : GenericUriParser
{
    public DataUriParser() : base(..省略..)
    {
        System.Diagnostics.Debugger.Break();
    }
}

thisの非公開メンバー変数m_Flagsを確認すれば,APIドキュメントに書かれてあることが正しいとわかる.

MustHaveAuthority | ..

ちなみに,m_Flagsの値はGenericUriParserOptions.AllowEmptyAuthorityなどを渡しても変化しない.

このような設定になっている結果,不透明URIに対してSystem.GenericUriParserを基底クラスにするといろいろと面倒なことになる.

Stack OverflowにSystem.Reflectionを使ってm_Flagsを強引に書き換えるという荒業も紹介されているが,正直どうかと思う.

素直にSystem.UriParserを基底クラスにしよう

実際にやってみたところ,m_FlagsMayHavePathとなっており,不透明URIにはちょうどよいことが分かった.

System.UriParserは抽象クラスなので,必ず発生クラスを作る必要があり,実装するURIスキームの仕様に合わせてSystem.UriParser.InitializeAndValidate()をオーバーライドするのがよいだろう.

class DataUriParser : UriParser
{
    protected override void InitializeAndValidate(Uri uri, out UriFormatException parsingError)
    {
        // uri.OriginalStringなどを再度解析して,不正な形式ならparsingErrorを設定
    }
}

余談

System.UriParserの設計は,かなり難解だ.正直,ちゃんと理解できたという自信はない.

最初,System.GenericUriParserを基底クラスにしていろいろ試していたが,

  • System.GenericUriParser.InitializeAndValidate()を必ずオーバーライドしないと例外発生
  • System.GenericUriParser.InitializeAndValidate()をオーバーライドした場合は,System.GenericUriParser.GetComponents()も必ずオーバーライドが必要っぽい

どうやらnew Uri("...")しただけでは文字列解析は十分に行われることはなく,System.Uri.QueryなどURIの各パーツへのアクセスが発生しないと派生クラスは機能しないようだ.アクセスが発生すると上記のメソッドが呼び出される.

System.GenericUriParser.GetComponents()の呼び出しのたびに文字列を解析するのは非効率なので,そう考えるとクラスのメンバー変数に解析結果を保持するのが妥当だが,そうするならSystem.GenericUriParser.OnNewUri()もオーバーライドが必要だろう.

あと,System.GenericUriParserのコンストラクターにはSystem.GenericUriParserOptionsを渡すのだが,System.GenericUriParser.GetComponents()をオーバーライドしている場合,コンストラクターに指定したフラグ値を考慮した実装を行う必要があるようだ.

例えば,コンストラクターにSystem.GenericUriParser.NoQueryを含めた場合,System.GenericUriParser.GetComponents()は"scheme://authority/path?query=value"を"scheme://authority/path%3Fquery=value"と変換する.しかし,System.GenericUriParser.GetComponents()をオーバーライドすることで,このフラグ値やメソッド引数の指示を無視して任意の文字列を返すことが可能だ.このとき,例外などは一切発生しない.

1
1
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
1