Secure String(C++版)
Secure StringのC++版を作ってみた。Java版ともっとも大きく違うのはテンプレートを使用していること。あとコンストラクタがexplicitじゃないから初期化のときに = が使えるのも大きい。
#pragma
once#include
<string>/* * C++版セキュアな文字列クラス。 * std::basic_stringとは違いイミュータブルなクラスとして設計。 */
template
<template
<typename
,class
,class
>class
Washer,// 文字列洗浄用オブジェクト
typename
charT,// 文字列の文字
class
traits = std::char_traits<charT>,// 文字の特性
class
allocator = std::allocator<charT>// アロケータ
>class
basic_secure_string :public
Washer<charT, traits, allocator> {private
:typedef
std::basic_string<charT, traits, allocator> b_string;typedef
Washer<charT, traits, allocator> washer;const
b_string str;mutable
bool
isTaint;mutable
b_string secStr;// 代入演算子と+=演算子は使用禁止にしておく。
template
<template
<typename
,class
,class
>class
W,typename
T,class
CT,class
A> basic_secure_string<W, T, CT, A>&operator
= (const
basic_secure_string<W, T, CT, A>&);template
<template
<typename
,class
,class
>class
W,typename
T,class
CT,class
A> basic_secure_string<W, T, CT, A>&operator
+= (const
basic_secure_string<W, T, CT, A>&);public
:// コンストラクタはCの文字列を受け取るものと、
basic_secure_string(const
char
* str) : str(str), isTaint(true
) {}// C++の文字列を受け取るものと、
template
<typename
T,class
CT,class
A> basic_secure_string(const
std::basic_string<T, CT, A>& str) : str(str.c_str()), isTaint(true
) {}// 自分自身を受け取るもので、explicitではない。
template
<typename
T,class
CT,class
A> basic_secure_string(const
basic_secure_string<T, CT, A>& str) : str(str.unsecureStr()), isTaint(true
) {}// メンバ関数はオリジナルの文字列を取得するものと、
const
b_string& unsecureStr()const
{return
this
->str; }// セキュアな文字列を取得するものだけ。
const
b_string& secureStr()const
{if
(isTaint) { secStr = washer::wash(unsecureStr()); isTaint =false
; }return
secStr; } };/* * basic_secure_stringに渡すテンプレートパラメータのWasher用ユーティリティ。 * と言っても、今のところ長ったらしいbasic_stringのtypedefと * C++では割と面倒な全置換しか実装していない。 */
template
<typename
charT,class
traits,class
allocator >class
washer_util {protected
:// いちいちstd::basic_string<charT, traits, allocator>って書くのは・・・
typedef
std::basic_string<charT, traits, allocator> b_string;// outputのoldStrを全てnewStrに置換する関数。
static
void
replaceAll(b_string& output,const
b_string& oldStr,const
b_string& newStr) {for
(int
i = output.find(oldStr,0
); i != output.npos; i = output.find(oldStr, i+1
)) output.replace(i, oldStr.length(), newStr); } };/* * Washer作るのにテンプレートを書くのが面倒だからマクロ化。 */
#define
MAKE_WASHER(name) \template
\ < \typename
charT, \class
traits, \class
allocator \ > \struct
name \ :public
washer_util<charT, traits, allocator>/* * HTML用のWasher。 * MAKE_WASHERマクロを使用して作られており、他のWasher作成の参考に。 */
MAKE_WASHER(html_washer) {static
const
b_string wash(const
b_string& str) { b_string secStr = str; replaceAll(secStr,"&"
,"&"
); replaceAll(secStr,"<"
,"<"
); replaceAll(secStr,">"
,">"
);return
secStr; } };/* * これまたいちいちbasic_secure_string<html_washer, char> * って書くのが面倒なのでtypedefしておく。 */
typedef
basic_secure_string<html_washer,char
> html_secure_string;
Javaに比べて劣る点はもちろんコードが長いことと、複雑に見えることw その分クライアント側はすっきりしてるけど。
で、クライアントコードは下の通り。
#include
<iostream>#include
"secure_string.h"
MAKE_WASHER(test) {static
const
b_string wash(const
b_string& str) { b_string secStr = str; replaceAll(secStr,"test"
,"foo"
);return
secStr; } };int
main() {// 文字列リテラル(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使ってる時点で・・・ねぇ?