Skip to content

Commit cae6ac6

Browse files
committed
Added testing of raw filter
1 parent 72a96d5 commit cae6ac6

8 files changed

Lines changed: 268 additions & 36 deletions

File tree

cppcms/http_content_filter.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,35 +115,98 @@ namespace http {
115115
booster::hold_ptr<_data> d;
116116
};
117117

118+
///
119+
/// Basic content filter that can be installed to request, all filters should be derived from this base class
120+
///
121+
/// Note that when `on_*` member functions of the basic_content_filter are called the original application that runs the filtering
122+
/// has temporary installed context that can be accessed from it.
123+
///
118124
class CPPCMS_API basic_content_filter {
119125
basic_content_filter(basic_content_filter const &);
120126
void operator=(basic_content_filter const &);
121127
public:
122128
basic_content_filter();
123129
virtual ~basic_content_filter();
124130

131+
///
132+
/// Member function that is called when entire content is read. By default does nothing.
133+
///
134+
/// The request can be aborted by throwing abort_upload
135+
///
125136
virtual void on_end_of_content();
137+
///
138+
/// Member function that is called in case of a error occuring during upload progress, user should not throw exception from this function but rather
139+
/// perform cleanup procedures if needed
140+
///
126141
virtual void on_error();
127142
private:
128143
struct _data;
129144
booster::hold_ptr<_data> d;
130145
};
131146

147+
///
148+
/// Process of any kind of generic content data.
149+
///
150+
/// Note: when raw_content_filter is used no content data is actually saved to request, for example request().raw_post_data() would return
151+
/// an empty content, so it is your responsibility to store/parse whatever content you use
152+
///
132153
class CPPCMS_API raw_content_filter : public basic_content_filter {
133154
public:
155+
///
156+
/// You must implement this member function to handle the data
157+
///
158+
/// A chunk of incoming data is avalible refered by data of size data_size
159+
///
160+
/// The request can be aborted by throwing abort_upload
161+
///
134162
virtual void on_data_chunk(void const *data,size_t data_size) = 0;
163+
164+
raw_content_filter();
135165
virtual ~raw_content_filter();
136166
private:
137167
struct _raw_data;
138168
booster::hold_ptr<_raw_data> d;
139169
};
140170

171+
///
172+
/// Filter for multipart/form-data - file upload
173+
///
174+
/// It allows to process/validate incomping data on the fly and make sure that for example the user is actually authorized to upload
175+
/// such a files
176+
///
141177
class CPPCMS_API multipart_filter : public basic_content_filter {
142178
public:
143179
multipart_filter();
144180
virtual ~multipart_filter();
181+
///
182+
/// New file meta-data of a form field or file is ready: the mime-type, form name and file name if provided are known, the content wasn't processed yet
183+
///
184+
/// Notes:
185+
///
186+
/// - This is the point when you can change various file properties, like location of the temporary file or specifiy output file name and more
187+
/// - The request can be aborted by throwing abort_upload
188+
/// - By default does nothing
189+
///
145190
virtual void on_new_file(http::file &input_file);
191+
///
192+
/// Some of the file data is available, you can access it and run some validation during upload progress.
193+
///
194+
/// Notes:
195+
///
196+
/// - This is the point when you can perform some file content validation
197+
/// - The request can be aborted by throwing abort_upload
198+
/// - By default does nothing
199+
///
146200
virtual void on_upload_progress(http::file &input_file);
201+
///
202+
/// The entire file data was transfered, its size wouldn't change
203+
///
204+
/// Notes:
205+
///
206+
/// - This is the point when you can save file if needed or perform final validation
207+
/// - The request can be aborted by throwing abort_upload
208+
/// - By default does nothing
209+
///
147210
virtual void on_data_ready(http::file &input_file);
148211

149212
private:

cppcms/http_request.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,10 @@ namespace http {
283283
files_type files();
284284

285285
///
286-
/// Access to raw bits of POST data. If the POST request is empty or the request_type in not POST
287-
/// {NULL,0} will be returned.
286+
/// Access to raw bits of POST (content) data. If the content is empty if raw_content_filter is installed
287+
/// or multipart/form-data is handled the read_post_data().second will be 0;
288288
///
289-
/// Note: when processing multipart/form-data POST request this function will always return {NULL,0} as
289+
/// Note: when processing multipart/form-data returns chunk of zero size as
290290
/// such requests maybe huge (file uploads of multiple hundreds of MB or even GB) that are would be stored in
291291
/// temporary files instead of memory. In order to get access to POST data you'll have to use post(), get(), or files()
292292
/// member functions.

src/http_content_filter.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ void multipart_filter::on_data_ready(http::file &) {}
7979

8080
struct raw_content_filter::_raw_data {};
8181
void raw_content_filter::on_data_chunk(void const *,size_t) {}
82+
raw_content_filter::raw_content_filter() {}
8283
raw_content_filter::~raw_content_filter() {}
8384

8485
} // http

src/http_context.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <booster/backtrace.h>
2323
#include <booster/aio/io_service.h>
2424
#include <cppcms/http_content_filter.h>
25+
#include <stdio.h>
2526

2627
#include "cached_settings.h"
2728

@@ -251,13 +252,11 @@ int context::on_headers_ready()
251252
}
252253
}
253254

254-
int status = request().on_content_start();
255-
if(status!=0)
256-
return status;
257255
d->pool.swap(pool);
258256
d->matched.swap(matched);
259257
d->app.swap(app);
260-
return 0;
258+
259+
return request().on_content_start();
261260
}
262261

263262
int context::on_content_progress(size_t n)

tests/filter_test.cpp

Lines changed: 131 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,9 @@ int total_on_error;
3030
#define TESTNT(x) do { if(x) break; std::cerr << "FAIL: " #x " in line: " << __LINE__ << std::endl; g_fail = 1; return; } while(0)
3131

3232

33-
class file_test : public cppcms::application, public cppcms::http::multipart_filter {
33+
class basic_test : public cppcms::application {
3434
public:
35-
file_test(cppcms::service &s) : cppcms::application(s)
36-
{
37-
}
38-
35+
basic_test(cppcms::service &srv) : cppcms::application(srv) {}
3936
std::string get_ref(std::string const &name)
4037
{
4138
int len = atoi(request().get("l_" + name).c_str());
@@ -50,6 +47,31 @@ class file_test : public cppcms::application, public cppcms::http::multipart_fil
5047
}
5148
return r;
5249
}
50+
51+
void do_abort(int code)
52+
{
53+
int how=atoi(request().get("how").c_str());
54+
switch(how){
55+
case 3:
56+
response().setbuf(0);
57+
case 2:
58+
response().full_asynchronous_buffering(false);
59+
case 1:
60+
response().status(code);
61+
response().set_plain_text_header();
62+
response().out() << "at="<<request().get("abort");
63+
case 0:
64+
throw cppcms::http::abort_upload(code);
65+
}
66+
}
67+
68+
};
69+
70+
class file_test : public basic_test, public cppcms::http::multipart_filter {
71+
public:
72+
file_test(cppcms::service &s) : basic_test(s)
73+
{
74+
}
5375

5476
struct test_data {
5577
int on_new_file;
@@ -128,22 +150,6 @@ class file_test : public cppcms::application, public cppcms::http::multipart_fil
128150
total_on_error++;
129151
}
130152

131-
void do_abort(int code)
132-
{
133-
int how=atoi(request().get("how").c_str());
134-
switch(how){
135-
case 3:
136-
response().setbuf(0);
137-
case 2:
138-
response().full_asynchronous_buffering(false);
139-
case 1:
140-
response().status(code);
141-
response().set_plain_text_header();
142-
response().out() << "at="<<request().get("abort");
143-
case 0:
144-
throw cppcms::http::abort_upload(code);
145-
}
146-
}
147153
void main(std::string path)
148154
{
149155
if(path=="/total_on_error") {
@@ -199,6 +205,106 @@ class file_test : public cppcms::application, public cppcms::http::multipart_fil
199205
};
200206

201207

208+
class raw_test : public basic_test, public cppcms::http::raw_content_filter {
209+
public:
210+
raw_test(cppcms::service &s) : basic_test(s)
211+
{
212+
}
213+
214+
struct test_data {
215+
int on_data_chunk;
216+
int on_end_of_content;
217+
int on_error;
218+
std::string content;
219+
test_data() : on_data_chunk(0), on_end_of_content(0), on_error(0) {}
220+
void write(std::ostream &out)
221+
{
222+
out <<
223+
"on_data_chunk="<<on_data_chunk<<"\n"
224+
"on_end_of_content="<<on_end_of_content<<"\n"
225+
;
226+
}
227+
};
228+
229+
test_data *data()
230+
{
231+
return context().get_specific<test_data>();
232+
}
233+
234+
void on_data_chunk(void const *ptr,size_t data_size)
235+
{
236+
if(request().get("abort")=="on_data_chunk" && atoi(request().get("at").c_str()) >= int(data()->content.size()))
237+
do_abort(502);
238+
data()->on_data_chunk++;
239+
data()->content.append(static_cast<char const *>(ptr),data_size);
240+
}
241+
242+
void on_end_of_content(){
243+
data()->on_end_of_content++;
244+
TESTNT(request().get("fail")=="");
245+
if(request().get("abort")=="on_end_of_content")
246+
do_abort(503);
247+
248+
}
249+
void on_error() {
250+
data()->on_error++;
251+
TESTNT(request().get("fail")=="1");
252+
total_on_error++;
253+
}
254+
255+
void main(std::string path)
256+
{
257+
if(path=="/total_on_error") {
258+
response().out() << "total_on_error=" << total_on_error;
259+
total_on_error = 0;
260+
return;
261+
}
262+
if(path=="/no_content") {
263+
TESTNT(request().is_ready());
264+
response().out() << "no_content=1";
265+
return;
266+
}
267+
if(request().get("setbuf")!="") {
268+
request().setbuf(atoi(request().get("setbuf").c_str()));
269+
}
270+
271+
TESTNT(request().is_ready() || context().get_specific<test_data>()==0);
272+
273+
if(!request().is_ready()) {
274+
test_data *td = new test_data();
275+
context().reset_specific<test_data>(td);
276+
if(request().get("abort")=="on_headers_ready")
277+
do_abort(501);
278+
request().set_content_filter(*this);
279+
std::string cl_limit,mp_limit;
280+
if((cl_limit=request().get("cl_limit"))!="")
281+
request().limits().content_length_limit(atoi(cl_limit.c_str()));
282+
if((mp_limit=request().get("mp_limit"))!="")
283+
request().limits().multipart_form_data_limit(atoi(mp_limit.c_str()));
284+
}
285+
else {
286+
test_data *td = context().get_specific<test_data>();
287+
TESTNT(td);
288+
if(request().get("abort")=="") {
289+
TESTNT(td->on_error == 0);
290+
TESTNT(td->on_data_chunk >= 1);
291+
TESTNT(td->on_end_of_content == 1);
292+
if(request().get("chunks")!="")
293+
TESTNT(td->on_data_chunk > 1);
294+
if(request().get("l_1")!="")
295+
TESTNT(td->content == get_ref("1"));
296+
}
297+
TESTNT(request().content_length() > 0);
298+
TESTNT(request().raw_post_data().second == 0);
299+
td->write(response().out());
300+
301+
}
302+
303+
}
304+
};
305+
306+
307+
202308
int main(int argc,char **argv)
203309
{
204310
try {
@@ -208,6 +314,10 @@ int main(int argc,char **argv)
208314
srv.applications_pool().mount( cppcms::create_pool<file_test>(),
209315
mount_point("/upload"),
210316
cppcms::app::asynchronous | cppcms::app::content_filter);
317+
318+
srv.applications_pool().mount( cppcms::create_pool<raw_test>(),
319+
mount_point("/raw"),
320+
cppcms::app::asynchronous | cppcms::app::content_filter);
211321

212322

213323
srv.after_fork(submitter(srv));

tests/filter_test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
},
1010
"http" : {
1111
"timeout" : 3,
12-
"script_names" : [ "/upload" ]
12+
"script_names" : [ "/upload", "/raw" ]
1313
}
1414
}

0 commit comments

Comments
 (0)