一覧SD-10-3 · 解釈系への入力

デシリアライズとXML外部実体参照

受信したバイト列を復元する際、許される型・呼出し・参照先を具体的に確認します。

説明

データを読むAPIでも、型や設定によっては関数の呼出しや外部参照が発生します。復元結果だけを見ると、読み込み中に起きた副作用を見落とすことがあります。

pickleの公開試料でメモリー上の呼出し履歴を取り、JSONとの違いを比較します。続いてXMLの外部実体を固定resolverで展開し、展開を止めたときの参照履歴を調べます。

受信したバイト列を復元する際、許される型・呼出し・参照先を具体的に確認します。

1直列化データに含まれる復元命令を読む

Sampleは復元時にrecord関数へ公開文字列を渡す固定試料です。

from sd10 import * EVENTS.clear() data = pickle.dumps復元手順を含む試料を作る(Sample(),protocol=4) print([op.name for op,arg,pos in pickletools.genopsバイト列の命令を逆アセンブルする(data)]) print(EVENTS)

命令列にはSTACK_GLOBALやREDUCEが含まれます。この時点は直列化しただけで、呼出し履歴は空です。

2復元時の関数呼出しを確認する

同じバイト列をpickle.loadsへ渡します。

print(pickle.loads固定試料の復元処理を実行する(data)) print(EVENTS復元中に生じた副作用を調べる)

辞書が返るだけでなく、public markerが履歴へ追加されました。復元中に関数が呼ばれる形式なので、未信頼データに汎用のpickle復元を使いません。

3グローバル参照を拒否する設定を比較する

この制限Unpicklerはfind_classの呼出しをすべて拒否します。

EVENTS.clear() try:Restrictedグローバル参照を拒否する復元器を使う(io.BytesIO(data)).load() except pickle.UnpicklingError as e:print(str(e)) print(EVENTS)

global lookup rejected、履歴は空です。特定の型を許可する方式では、許可した型の復元動作まで調べる必要があります。

4必要な情報をJSONの値として読む

同じ表示用の情報をJSONで保持します。

parsed = json.loads復元関数を指定しないデータ形式を読む('{"restored":"public marker"}') print(parsed) print(EVENTS)

辞書の値を得て、呼出し履歴は空です。型、キー、文字数などの業務検査はJSONの解析後に追加します。

5XML実体の展開時に参照が起きることを確認する

XMLのSYSTEM識別子は固定resolverで公開文字列へ解決します。

print(xml(True)外部実体を展開し参照履歴を取る)

reportの内容がpublic external valueへ変わり、参照URLも記録されます。no_network=Trueでも、この明示resolverの参照処理は実行されています。

6実体を展開しない設定へ変える

同じXMLをresolve_entities=Falseで読みます。

print(xml(False)実体の展開を無効にする)

&value;がそのまま残り、resolver履歴は空です。外部参照の禁止と、後続で未展開実体をどう扱うかを併せて決めます。

7通常のXML値を取得する

DTDや実体が不要なデータ契約では、通常の要素から値を取得します。

parser = etree.XMLParserDTD読込みと実体展開を抑える(load_dtd=False,resolve_entities=False,no_network=True) root = etree.fromstring(b"<report><count>3</count></report>",parser) print(root.findtext必要な要素から値を取り出す("count"),int(root.findtext("count")))

文字列3を取り出して整数化しました。パーサー機能を絞ったうえで、要求する要素と業務上の範囲を検証します。

まとめ

復元APIの副作用は返り値だけで判断しません。

汎用オブジェクト復元より必要なデータ型を選びます。

XMLの参照・展開設定と後続処理を一体で確認します。

操作履歴と解説を振り返れます。

Python 3.15 / pickle / lxml 6.1.3

1
from sd10 import * EVENTS.clear() data = pickle.dumps(Sample(),protocol=4) print([op.name for op,arg,pos in pickletools.genops(data)]) print(EVENTS)
['PROTO', 'FRAME', 'SHORT_BINUNICODE', 'MEMOIZE', 'SHORT_BINUNICODE', 'MEMOIZE', 'STACK_GLOBAL', 'MEMOIZE', 'SHORT_BINUNICODE', 'MEMOIZE', 'TUPLE1', 'MEMOIZE', 'REDUCE', 'MEMOIZE', 'STOP']
[]
2
print(pickle.loads(data)) print(EVENTS)
{'restored': 'public marker'}
['public marker']
3
EVENTS.clear() try:Restricted(io.BytesIO(data)).load() except pickle.UnpicklingError as e:print(str(e)) print(EVENTS)
global lookup rejected
[]
4
parsed = json.loads('{"restored":"public marker"}') print(parsed) print(EVENTS)
{'restored': 'public marker'}
[]
5
print(xml(True))
('<report>public external value</report>', ['https://data.example.test/value'])
6
print(xml(False))
('<report>&value;</report>', [])
7
parser = etree.XMLParser(load_dtd=False,resolve_entities=False,no_network=True) root = etree.fromstring(b"<report><count>3</count></report>",parser) print(root.findtext("count"),int(root.findtext("count")))
3 3

演習完了