デシリアライズと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
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'] []
print(pickle.loads(data))
print(EVENTS){'restored': 'public marker'}
['public marker']EVENTS.clear()
try:Restricted(io.BytesIO(data)).load()
except pickle.UnpicklingError as e:print(str(e))
print(EVENTS)global lookup rejected []
parsed = json.loads('{"restored":"public marker"}')
print(parsed)
print(EVENTS){'restored': 'public marker'}
[]print(xml(True))('<report>public external value</report>', ['https://data.example.test/value'])print(xml(False))('<report>&value;</report>', [])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
演習完了