Browse Source

- Add jsescape

- Improved filters performance and memory use
master
Artyom Beilis 8 years ago
parent
commit
d4923f5021
6 changed files with 380 additions and 13 deletions
  1. +26
    -0
      cppcms/filters.h
  2. +122
    -0
      cppcms/steal_buf.h
  3. +20
    -0
      cppcms/util.h
  4. +104
    -4
      src/filters.cpp
  5. +30
    -7
      src/util.cpp
  6. +78
    -2
      tests/tc_test.cpp

+ 26
- 0
cppcms/filters.h View File

@@ -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;
}


///


+ 122
- 0
cppcms/steal_buf.h View File

@@ -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


+ 20
- 0
cppcms/util.h View File

@@ -25,6 +25,7 @@ namespace cppcms {
/// - > - \&gt;
/// - \& - \&amp;
/// - &quot; - \&quot;
/// - &#39; - \&#39;
///
/// 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 {
/// - > - \&gt;
/// - \& - \&amp;
/// - &quot; - \&quot;
/// - &#39; - \&#39;
///
/// 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.
///
/// - < - \&lt;
/// - > - \&gt;
/// - \& - \&amp;
/// - &quot; - \&quot;
/// - &#39; - \&#39;
///
/// 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);


+ 104
- 4
src/filters.cpp View File

@@ -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 {};


+ 30
- 7
src/util.cpp View File

@@ -31,25 +31,39 @@ std::string escape(std::string const &s)
case '>': content+="&gt;"; break;
case '&': content+="&amp;"; break;
case '\"': content+="&quot;"; break;
case '\'': content+="&#39;"; 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 << "&lt;"; break;
case '>': output << "&gt;"; break;
case '&': output << "&amp;"; break;
case '\"': output<<"&quot;"; break;
default: output << c;
case '<': ok = output.sputn("&lt;",4)==4; break;
case '>': ok = output.sputn("&gt;",4)==4; break;
case '&': ok = output.sputn("&amp;",5)==5; break;
case '\"': ok = output.sputn("&quot;",6)==6; break;
case '\'': ok = output.sputn("&#39;",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;


+ 78
- 2
tests/tc_test.cpp View File

@@ -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(),"&lt;th attr=&quot;a&quot; attr=&#39;b&#39; &amp; &gt;");
}
{
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();


Loading…
Cancel
Save