Secure StringのC++版を作ってみた。Java版ともっとも大きく違うのはテンプレートを使用していること。あとコンストラクタがexplicitじゃないから初期化のときに = が使えるのも大きい。
#pragmaonce#include<string>/* * C++版セキュアな文字列クラス。 * std::basic_stringとは違いイミュータブルなクラスとして設計。 */template<template<typename,class,class>classWasher,// 文字列洗浄用オブジェクトtypenamecharT,// 文字列の文字classtraits = std::char_traits<charT>,// 文字の特性classallocator = std::allocator<charT>// アロケータ>classbasic_secure_string :publicWasher<charT, traits, allocator> {private:typedefstd::basic_string<charT, traits, allocator> b_string;typedefWasher<charT, traits, allocator> washer;constb_string str;mutableboolisTaint;mutableb_string secStr;// 代入演算子と+=演算子は使用禁止にしておく。template<template<typename,class,class>classW,typenameT,classCT,classA> basic_secure_string<W, T, CT, A>&operator= (constbasic_secure_string<W, T, CT, A>&);template<template<typename,class,class>classW,typenameT,classCT,classA> basic_secure_string<W, T, CT, A>&operator+= (constbasic_secure_string<W, T, CT, A>&);public:// コンストラクタはCの文字列を受け取るものと、basic_secure_string(constchar* str) : str(str), isTaint(true) {}// C++の文字列を受け取るものと、template<typenameT,classCT,classA> basic_secure_string(conststd::basic_string<T, CT, A>& str) : str(str.c_str()), isTaint(true) {}// 自分自身を受け取るもので、explicitではない。template<typenameT,classCT,classA> basic_secure_string(constbasic_secure_string<T, CT, A>& str) : str(str.unsecureStr()), isTaint(true) {}// メンバ関数はオリジナルの文字列を取得するものと、constb_string& unsecureStr()const{returnthis->str; }// セキュアな文字列を取得するものだけ。constb_string& secureStr()const{if(isTaint) { secStr = washer::wash(unsecureStr()); isTaint =false; }returnsecStr; } };/* * basic_secure_stringに渡すテンプレートパラメータのWasher用ユーティリティ。 * と言っても、今のところ長ったらしいbasic_stringのtypedefと * C++では割と面倒な全置換しか実装していない。 */template<typenamecharT,classtraits,classallocator >classwasher_util {protected:// いちいちstd::basic_string<charT, traits, allocator>って書くのは・・・typedefstd::basic_string<charT, traits, allocator> b_string;// outputのoldStrを全てnewStrに置換する関数。staticvoidreplaceAll(b_string& output,constb_string& oldStr,constb_string& newStr) {for(inti = output.find(oldStr,0); i != output.npos; i = output.find(oldStr, i+1)) output.replace(i, oldStr.length(), newStr); } };/* * Washer作るのにテンプレートを書くのが面倒だからマクロ化。 */#defineMAKE_WASHER(name) \template\ < \typenamecharT, \classtraits, \classallocator \ > \structname \ :publicwasher_util<charT, traits, allocator>/* * HTML用のWasher。 * MAKE_WASHERマクロを使用して作られており、他のWasher作成の参考に。 */MAKE_WASHER(html_washer) {staticconstb_string wash(constb_string& str) { b_string secStr = str; replaceAll(secStr,"&","&"); replaceAll(secStr,"<","<"); replaceAll(secStr,">",">");returnsecStr; } };/* * これまたいちいちbasic_secure_string<html_washer, char> * って書くのが面倒なのでtypedefしておく。 */typedefbasic_secure_string<html_washer,char> html_secure_string;
Javaに比べて劣る点はもちろんコードが長いことと、複雑に見えることw その分クライアント側はすっきりしてるけど。
で、クライアントコードは下の通り。
#include<iostream>#include"secure_string.h"MAKE_WASHER(test) {staticconstb_string wash(constb_string& str) { b_string secStr = str; replaceAll(secStr,"test","foo");returnsecStr; } };intmain() {// 文字列リテラル(Cの文字列)からセキュアな文字列を構築。html_secure_string str ="<s>test</s>"; std::cout << str.unsecureStr() << std::endl;// セキュアじゃない(オリジナルの)文字列を表示std::cout << str.secureStr() << std::endl;// セキュアな(サニタイズされた)文字列を表示std::cout << std::endl;// str(セキュアな文字列)からstr2を構築。basic_secure_string<test,char> str2 = str; std::cout << str2.unsecureStr() << std::endl;// コピーしてもオリジナルの文字列は残るstd::cout << str2.secureStr() << std::endl; std::cout << std::endl;// str.unsecureStr(basic_string:C++の文字列)からstr3を構築。basic_secure_string<test,char> str3 = str.unsecureStr(); std::cout << str3.unsecureStr() << std::endl; std::cout << str3.secureStr() << std::endl; std::cout << std::endl; }
やっぱり問題はoperator<<演算子をどうするかだけど、自分の中ではsecureStrを呼ぶようにするのが(C++では)自然かなぁ、と思ったり。Javaは組み合わせて出力するのが自然かも。
ちなみにVisual Studio 2005でしか試してないからほかの環境では動かない可能性が・・・ そもそも#pragma once使ってる時点で・・・ねぇ?