@@ -229,6 +229,32 @@ namespace cppcms { | |||
obj(out); | |||
return out; | |||
} | |||
/// | |||
/// \brief Output filter escape | |||
/// | |||
/// Escape text for JavaScript string -- make text safe to include between quotes in the JavaScript or JSON code | |||
/// | |||
/// \ver{v1_2} | |||
class CPPCMS_API jsescape { | |||
public: | |||
jsescape(); | |||
~jsescape(); | |||
jsescape(jsescape const &); | |||
jsescape const &operator=(jsescape const &other); | |||
void operator()(std::ostream &out) const; | |||
jsescape(streamable const &obj); | |||
private: | |||
streamable obj_; | |||
struct _data; | |||
booster::copy_ptr<_data> d; | |||
}; | |||
inline std::ostream &operator<<(std::ostream &out,jsescape const &obj) | |||
{ | |||
obj(out); | |||
return out; | |||
} | |||
/// | |||
@@ -313,6 +313,128 @@ namespace util { | |||
private: | |||
stackbuf<Size> buf_; | |||
}; | |||
template<typename Filter,int BufferSize=128> | |||
class filterbuf : public std::streambuf { | |||
public: | |||
filterbuf() : output_(0), output_stream_(0) | |||
{ | |||
setp(buffer_,buffer_+BufferSize); | |||
} | |||
filterbuf(std::ostream &out) : output_(0), output_stream_(0) | |||
{ | |||
setp(buffer_,buffer_+BufferSize); | |||
steal(out); | |||
} | |||
~filterbuf() | |||
{ | |||
try { | |||
release(); | |||
} | |||
catch(...) {} | |||
} | |||
void steal(std::ostream &out) | |||
{ | |||
release(); | |||
output_stream_ = &out; | |||
output_ = out.rdbuf(this); | |||
} | |||
int release() | |||
{ | |||
int r=0; | |||
if(output_stream_) { | |||
if(write()!=0) | |||
r=-1; | |||
output_stream_->rdbuf(output_); | |||
output_=0; | |||
output_stream_=0; | |||
} | |||
return r; | |||
} | |||
protected: | |||
int overflow(int c) | |||
{ | |||
if(write()!=0) | |||
return -1; | |||
if(c!=EOF) { | |||
*pptr()=c; | |||
pbump(1); | |||
} | |||
return 0; | |||
} | |||
private: | |||
int write() | |||
{ | |||
if(static_cast<Filter*>(this)->convert(pbase(),pptr(),output_)!=0) { | |||
output_stream_->setstate(std::ios_base::failbit); | |||
return -1; | |||
} | |||
setp(buffer_,buffer_ + BufferSize); | |||
return 0; | |||
} | |||
char buffer_[BufferSize]; | |||
std::streambuf *output_; | |||
std::ostream *output_stream_; | |||
}; | |||
template<typename Filter> | |||
class filterbuf<Filter,0> : public std::streambuf { | |||
public: | |||
filterbuf() : output_(0), output_stream_(0) | |||
{ | |||
} | |||
filterbuf(std::ostream &out) : output_(0), output_stream_(0) | |||
{ | |||
steal(out); | |||
} | |||
~filterbuf() | |||
{ | |||
release(); | |||
} | |||
void steal(std::ostream &out) | |||
{ | |||
release(); | |||
output_stream_ = &out; | |||
output_ = out.rdbuf(this); | |||
} | |||
int release() | |||
{ | |||
int r=0; | |||
if(output_stream_) { | |||
output_stream_->rdbuf(output_); | |||
output_=0; | |||
output_stream_=0; | |||
} | |||
return r; | |||
} | |||
protected: | |||
int overflow(int c) | |||
{ | |||
if(c!=EOF) { | |||
char tmp=c; | |||
if(write(&tmp,&tmp+1)!=0) | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
std::streamsize xsputn(char const *s,std::streamsize size) | |||
{ | |||
if(write(s,s+size)!=0) | |||
return -1; | |||
return size; | |||
} | |||
private: | |||
int write(char const *begin,char const *end) | |||
{ | |||
if(static_cast<Filter*>(this)->convert(begin,end,output_)!=0) { | |||
output_stream_->setstate(std::ios_base::failbit); | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
std::streambuf *output_; | |||
std::ostream *output_stream_; | |||
}; | |||
} // util | |||
} // cppcms | |||
@@ -25,6 +25,7 @@ namespace cppcms { | |||
/// - > - \> | |||
/// - \& - \& | |||
/// - " - \" | |||
/// - ' - \' | |||
/// | |||
/// Note, this function does not deal with encodings, so it's up to you to | |||
/// provide valid text encoding | |||
@@ -37,12 +38,27 @@ namespace cppcms { | |||
/// - > - \> | |||
/// - \& - \& | |||
/// - " - \" | |||
/// - ' - \' | |||
/// | |||
/// Note, this function does not deal with encodings, so it's up to you to | |||
/// provide valid text encoding | |||
/// | |||
void CPPCMS_API escape(char const *begin,char const *end,std::ostream &output); | |||
/// | |||
/// Escape string for inclusion in HTML page, i.e. | |||
/// | |||
/// - < - \< | |||
/// - > - \> | |||
/// - \& - \& | |||
/// - " - \" | |||
/// - ' - \' | |||
/// | |||
/// Note, this function does not deal with encodings, so it's up to you to | |||
/// provide valid text encoding | |||
/// | |||
/// if I/O operation on output fails returns -1, otherwise returns 0 | |||
int CPPCMS_API escape(char const *begin,char const *end,std::streambuf &output); | |||
/// | |||
/// Encode string for URL (percent encoding) | |||
/// | |||
std::string CPPCMS_API urlencode(std::string const &s); | |||
@@ -51,6 +67,10 @@ namespace cppcms { | |||
/// | |||
void CPPCMS_API urlencode(char const *begin,char const *end,std::ostream &output); | |||
/// | |||
/// Encode string for URL (percent encoding), returns -1 in case of IO failure, and 0 on success | |||
/// | |||
int CPPCMS_API urlencode(char const *begin,char const *end,std::streambuf &output); | |||
/// | |||
/// Decode string from URL-encoding (percent-encoding) | |||
/// | |||
std::string CPPCMS_API urldecode(std::string const &s); | |||
@@ -120,6 +120,18 @@ namespace cppcms { namespace filters { | |||
out << ::cppcms::locale::to_title( sb.begin(),sb.end(),out.getloc()); | |||
} | |||
namespace { | |||
struct escape_buf : public util::filterbuf<escape_buf,128> { | |||
public: | |||
int convert(char const *begin,char const *end,std::streambuf *out) | |||
{ | |||
if(!out) | |||
return -1; | |||
return util::escape(begin,end,*out); | |||
} | |||
}; | |||
} | |||
struct escape::_data {}; | |||
escape::escape() {} | |||
escape::~escape() {} | |||
@@ -128,12 +140,100 @@ namespace cppcms { namespace filters { | |||
escape const &escape::operator=(escape const &other){ obj_ = other.obj_; return *this; } | |||
void escape::operator()(std::ostream &out) const | |||
{ | |||
util::steal_buffer<> sb(out); | |||
escape_buf eb; | |||
eb.steal(out); | |||
obj_(out); | |||
eb.release(); | |||
} | |||
namespace { | |||
struct jsescape_buf : public util::filterbuf<jsescape_buf,128> { | |||
public: | |||
jsescape_buf() | |||
{ | |||
buf_[0]='\\'; | |||
buf_[1]= 'u'; | |||
buf_[2]= '0'; | |||
buf_[3]= '0'; | |||
// 4 first digit | |||
// 5 2nd digit | |||
buf_[6]='\0'; | |||
} | |||
char const *encode(unsigned char c) | |||
{ | |||
static char const tohex[]="0123456789abcdef"; | |||
buf_[4]=tohex[c >> 4]; | |||
buf_[5]=tohex[c & 0xF]; | |||
return buf_; | |||
} | |||
int convert(char const *begin,char const *end,std::streambuf *out) | |||
{ | |||
if(!out) | |||
return -1; | |||
while(begin!=end) { | |||
char const *addon = 0; | |||
unsigned char c=*begin++; | |||
switch(c) { | |||
case 0x22: addon = "\\\""; break; | |||
case 0x5C: addon = "\\\\"; break; | |||
case '\b': addon = "\\b"; break; | |||
case '\f': addon = "\\f"; break; | |||
case '\n': addon = "\\n"; break; | |||
case '\r': addon = "\\r"; break; | |||
case '\t': addon = "\\t"; break; | |||
case '\'': addon = encode(c); break; | |||
default: | |||
if(c<=0x1F) | |||
addon=encode(c); | |||
else { | |||
if(out->sputc(c)==EOF) | |||
return -1; | |||
continue; | |||
} | |||
} | |||
while(*addon) | |||
if(out->sputc(*addon++)==EOF) | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
private: | |||
char buf_[8]; | |||
}; | |||
} | |||
struct jsescape::_data {}; | |||
jsescape::jsescape() {} | |||
jsescape::~jsescape() {} | |||
jsescape::jsescape(jsescape const &other) : obj_(other.obj_) {} | |||
jsescape::jsescape(streamable const &obj) : obj_(obj) {} | |||
jsescape const &jsescape::operator=(jsescape const &other){ obj_ = other.obj_; return *this; } | |||
void jsescape::operator()(std::ostream &out) const | |||
{ | |||
jsescape_buf sb; | |||
sb.steal(out); | |||
obj_(out); | |||
sb.release(); | |||
util::escape(sb.begin(),sb.end(),out); | |||
} | |||
namespace { | |||
struct urlencode_buf : public util::filterbuf<urlencode_buf,128> { | |||
public: | |||
int convert(char const *begin,char const *end,std::streambuf *out) | |||
{ | |||
if(!out) | |||
return -1; | |||
return util::urlencode(begin,end,*out); | |||
} | |||
}; | |||
} | |||
struct urlencode::_data {}; | |||
urlencode::urlencode() {} | |||
urlencode::~urlencode() {} | |||
@@ -142,10 +242,10 @@ namespace cppcms { namespace filters { | |||
urlencode const &urlencode::operator=(urlencode const &other){ obj_ = other.obj_; return *this; } | |||
void urlencode::operator()(std::ostream &out) const | |||
{ | |||
util::steal_buffer<> sb(out); | |||
urlencode_buf sb; | |||
sb.steal(out); | |||
obj_(out); | |||
sb.release(); | |||
util::urlencode(sb.begin(),sb.end(),out); | |||
} | |||
struct raw::_data {}; | |||
@@ -31,25 +31,39 @@ std::string escape(std::string const &s) | |||
case '>': content+=">"; break; | |||
case '&': content+="&"; break; | |||
case '\"': content+="""; break; | |||
case '\'': content+="'"; break; | |||
default: content+=c; | |||
} | |||
} | |||
return content; | |||
} | |||
void escape(char const *begin,char const *end,std::ostream &output) | |||
int escape(char const *begin,char const *end,std::streambuf &output) | |||
{ | |||
while(begin!=end) { | |||
char c=*begin++; | |||
bool ok; | |||
switch(c){ | |||
case '<': output << "<"; break; | |||
case '>': output << ">"; break; | |||
case '&': output << "&"; break; | |||
case '\"': output<<"""; break; | |||
default: output << c; | |||
case '<': ok = output.sputn("<",4)==4; break; | |||
case '>': ok = output.sputn(">",4)==4; break; | |||
case '&': ok = output.sputn("&",5)==5; break; | |||
case '\"': ok = output.sputn(""",6)==6; break; | |||
case '\'': ok = output.sputn("'",5)==5; break; | |||
default: ok = output.sputc(c)!=EOF; | |||
} | |||
if(!ok) | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
void escape(char const *begin,char const *end,std::ostream &output) | |||
{ | |||
std::streambuf *buf = output.rdbuf(); | |||
if(!output || !buf) | |||
return; | |||
if(escape(begin,end,*buf)!=0) | |||
output.setstate(std::ios_base::failbit); | |||
} | |||
template<typename Iterator> | |||
@@ -90,6 +104,15 @@ void urlencode(char const *b,char const *e,std::ostream &out) | |||
std::ostream_iterator<char> it(out); | |||
urlencode_impl(b,e,it); | |||
} | |||
int urlencode(char const *b,char const *e,std::streambuf &out) | |||
{ | |||
std::ostreambuf_iterator<char> it(&out); | |||
urlencode_impl(b,e,it); | |||
if(it.failed()) | |||
return -1; | |||
return 0; | |||
} | |||
std::string urlencode(std::string const &s) | |||
{ | |||
std::string content; | |||
@@ -11,6 +11,8 @@ | |||
#include <cppcms/json.h> | |||
#include <cppcms/cache_interface.h> | |||
#include <cppcms/url_mapper.h> | |||
#include <cppcms/steal_buf.h> | |||
#include <sstream> | |||
#include "dummy_api.h" | |||
#include "test.h" | |||
@@ -52,8 +54,9 @@ std::string conv(std::string const &l,size_t i) | |||
void compare_strings(std::string const &l,std::string const &r) | |||
{ | |||
if(l==r) | |||
if(l==r) { | |||
return; | |||
} | |||
size_t m = l.size(); | |||
if(r.size() > m) m = r.size(); | |||
int line = 1; | |||
@@ -103,6 +106,77 @@ public: | |||
output_.clear(); | |||
} | |||
struct upperA : public cppcms::util::filterbuf<upperA,16> { | |||
public: | |||
int convert(char const *begin,char const *end,std::streambuf *out) | |||
{ | |||
while(begin!=end) { | |||
char c=*begin++; | |||
if('a'<=c && c<='z') | |||
c = 'A' + (c-'a'); | |||
if(out->sputc(c)==EOF) | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
}; | |||
struct upperB : public cppcms::util::filterbuf<upperB,0> { | |||
public: | |||
int convert(char const *begin,char const *end,std::streambuf *out) | |||
{ | |||
while(begin!=end) { | |||
char c=*begin++; | |||
if('a'<=c && c<='z') | |||
c = 'A' + (c-'a'); | |||
if(out->sputc(c)==EOF) | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
}; | |||
void test_buffer() | |||
{ | |||
std::cout << "- Testing filter API" << std::endl; | |||
{ | |||
std::ostringstream ss; | |||
upperA a; | |||
a.steal(ss); | |||
ss<<"test 123456 Hello World"; | |||
a.release(); | |||
compare_strings(ss.str(),"TEST 123456 HELLO WORLD"); | |||
} | |||
{ | |||
std::ostringstream ss; | |||
upperB b; | |||
b.steal(ss); | |||
ss<<"test 123456 Hello World"; | |||
b.release(); | |||
compare_strings(ss.str(),"TEST 123456 HELLO WORLD"); | |||
} | |||
{ | |||
std::ostringstream ss; | |||
ss<<cppcms::filters::escape(std::string("<th attr=\"a\" attr='b' & >")); | |||
compare_strings(ss.str(),"<th attr="a" attr='b' & >"); | |||
} | |||
{ | |||
std::ostringstream ss; | |||
ss<<cppcms::filters::urlencode(std::string("Test-Test /xx")); | |||
compare_strings(ss.str(),"Test-Test%20%2fxx"); | |||
} | |||
{ | |||
std::ostringstream ss; | |||
ss<<cppcms::filters::urlencode(cppcms::filters::escape(std::string("<test>"))); | |||
compare_strings(ss.str(),"%26lt%3btest%26gt%3b"); | |||
} | |||
{ | |||
std::ostringstream ss; | |||
ss<<cppcms::filters::jsescape(std::string("Hello\ntest\t msg=\"a\" or msg='a' \1 ")); | |||
compare_strings(ss.str(),"Hello\\ntest\\t msg=\\\"a\\\" or msg=\\u0027a\\u0027 \\u0001 "); | |||
} | |||
} | |||
std::string str() | |||
{ | |||
response().out() << std::flush; | |||
@@ -229,7 +303,7 @@ public: | |||
m.integer = 1; | |||
m.text = "/"; | |||
render("master_url",m); | |||
TEST(str()=="\n" | |||
compare_strings(str(),"\n" | |||
"/\n" | |||
"/1\n" | |||
"/1/%2f\n" | |||
@@ -407,6 +481,8 @@ int main(int argc,char **argv) | |||
cppcms::service srv(cfg); | |||
test_app app(srv); | |||
app.test_buffer(); | |||
app.set_context(); | |||
app.test_skins(); | |||