MXS-1220: Add request body parsing

The HTTP request body is expected to be a valid JSON object. All other
requests are considered malformed requests and result in a HTTP 400 error.

Added the Jansson license to the LICENSE-THIRDPARTY.TXT file. Imported
some of the tests from the Jansson test suite to the HttpParser test.
This commit is contained in:
Markus Mäkelä
2017-04-16 21:31:52 +03:00
committed by Markus Mäkelä
parent 4eb121ce35
commit c937457738
7 changed files with 244 additions and 24 deletions

View File

@ -56,7 +56,23 @@ elseif(WITH_TCMALLOC)
target_link_libraries(maxscale-common ${TCMALLOC_LIBRARIES})
endif()
target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl pthread crypt dl crypto inih z rt m stdc++)
target_link_libraries(maxscale-common
${MARIADB_CONNECTOR_LIBRARIES}
${LZMA_LINK_FLAGS}
${PCRE2_LIBRARIES}
${CURL_LIBRARIES}
${JANSSON_LIBRARIES}
ssl
pthread
crypt
dl
crypto
inih
z
rt
m
stdc++
)
add_dependencies(maxscale-common pcre2 connector-c)
set_target_properties(maxscale-common PROPERTIES VERSION "1.0.0")

View File

@ -99,7 +99,9 @@ void AdminClient::process()
enum http_code status = parser.get() ? HTTP_200_OK : HTTP_400_BAD_REQUEST;
atomic_write_int64(&m_last_activity, hkheartbeat);
write_response(m_fd, HTTP_200_OK, parser->get_body());
/** Echo the request body back */
write_response(m_fd, HTTP_200_OK, parser->get_json_str());
}
else
{

View File

@ -20,14 +20,29 @@ namespace maxscale
{
static inline string& trim(string& str)
{
while (isspace(*str.begin()))
if (str.length())
{
str.erase(str.begin());
}
if (isspace(*str.begin()))
{
string::iterator it = str.begin();
while (isspace(*str.rbegin()))
{
str.erase(str.rbegin().base());
while (it != str.end() && isspace(*it))
{
it++;
}
str.erase(str.begin(), it);
}
if (isspace(*str.rbegin()))
{
string::reverse_iterator it = str.rbegin();
while (it != str.rend() && isspace(*it))
{
it++;
}
str.erase(it.base(), str.end());
}
}
return str;
@ -76,22 +91,48 @@ HttpRequest* HttpRequest::parse(string data)
/** The headers are now processed and consumed. The message body is
* the only thing left in the request string. */
bool ok = false;
HttpRequest* request = NULL;
enum http_verb verb_value = string_to_http_verb(verb);
json_error_t json_error = {};
json_t* body = NULL;
/** Remove leading and trailing whitespace */
if (data.length())
{
mxs::trim(data);
}
if (http_version == "HTTP/1.1" && verb_value != HTTP_UNKNOWN)
{
if (data.length() && (body = json_loads(data.c_str(), 0, &json_error)) == NULL)
{
MXS_DEBUG("JSON error in input on line %d column %d: %s (%s)",
json_error.line, json_error.column, json_error.text,
data.c_str());
}
else
{
ok = true;
}
}
if (ok)
{
request = new HttpRequest();
request->m_verb = verb_value;
request->m_resource = uri;
request->m_headers = headers;
request->m_body = data;
request->m_json.reset(body);
request->m_json_string = data;
}
return request;
}
HttpRequest::HttpRequest()
HttpRequest::HttpRequest():
m_json(NULL),
m_verb(HTTP_UNKNOWN)
{
}

View File

@ -18,11 +18,15 @@
#include <map>
#include <tr1/memory>
#include <maxscale/jansson.hh>
#include <maxscale/utils.hh>
#include "http.hh"
using std::shared_ptr;
using std::string;
using std::map;
using mxs::Closer;
class HttpRequest;
@ -85,24 +89,24 @@ public:
return rval;
}
/**
* @brief Check if body is defined
*
* @return True if body is defined
*/
bool have_body() const
{
return m_body.length() != 0;
}
/**
* @brief Return request body
*
* @return Request body or empty string if no body is defined
*/
const string& get_body() const
const string& get_json_str() const
{
return m_body;
return m_json_string;
}
/**
* @brief Return raw JSON body
*
* @return Raw JSON body or NULL if no body is defined
*/
const json_t* get_json() const
{
return m_json.get();
}
/**
@ -121,7 +125,8 @@ private:
HttpRequest& operator = (const HttpRequest&);
map<string, string> m_headers;
string m_body;
Closer<json_t*> m_json;
string m_json_string;
string m_resource;
enum http_verb m_verb;
};

View File

@ -159,12 +159,144 @@ int test_headers()
return 0;
}
/**
The following JSON tests are imported from the Jansson test suite
*/
const char* body_pass[] =
{
"{\"i\": [1]}",
"{\"i\": [1.8011670033376514e-308]}",
"{\"i\": [123.456e78]}",
"{\"i\": [-1]}",
"{\"i\": [-123]}",
"{\"i\": [\"\u0821 three-byte UTF-8\"]}",
"{\"i\": [123]}",
"{\"i\": [1E+2]}",
"{\"i\": [123e45]}",
"{\"i\": [false]}",
"{\"i\": [\"\u002c one-byte UTF-8\"]}",
"{\"i\": {\"a\":[]}}",
"{\"i\": [\"abcdefghijklmnopqrstuvwxyz1234567890 \"]}",
"{\"i\": [-0]}",
"{\"i\": [\"\"]}",
"{\"i\": [1,2,3,4]}",
"{\"i\": [\"a\", \"b\", \"c\"]}",
"{\"foo\": \"bar\", \"core\": \"dump\"}",
"{\"i\": [true, false, true, true, null, false]}",
"{\"b\": [\"a\"]}",
"{\"i\": [true]}",
"{\"i\": {}}",
"{\"i\": [{}]}",
"{\"i\": [0]}",
"{\"i\": [123.456789]}",
"{\"i\": [1e+2]}",
"{\"i\": [\"\u0123 two-byte UTF-8\"]}",
"{\"i\": [123e-10000000]}",
"{\"i\": [null]}",
"{\"i\": [\"€þıœəßð some utf-8 ĸʒ×ŋµåäö𝄞\"]}",
"{\"i\": [1e-2]}",
"{\"i\": [1E22]}",
"{\"i\": [1E-2]}",
"{\"i\": []}",
/** Additional tests */
"{\"this is\": \"a JSON value\"}",
NULL
};
const char* body_fail[] =
{
"{{}",
"{[-123foo]}",
"{[1,}",
"{[troo}",
"{{\"a\"}",
"{[-123123123123123123123123123123]}",
"{{[}",
"{[1.]}",
"{[1ea]}",
"{['}",
"{[-012]}",
"{[012]}",
"{{\"a}",
"{[{}",
"{[123123123123123123123123123123]}",
"{[1,2,3]}",
"{foo}",
"{[\"\a <-- invalid escape\"]}",
"{[{}}",
"{[\" <-- tab character\"]}",
"{[\"a\"}",
"{{'a'}",
"{[,}",
"{{\"a\":}",
"{{\"a\":\"a}",
"{[-123123e100000]}",
"{[\"null escape \u0000 not allowed\"]}",
"{[1,}",
"{2,}",
"{3,}",
"{4,}",
"{5,}",
"{]}",
"{null}",
"{[-123.123foo]}",
"{[}",
"{aå}",
"{{\"foo\u0000bar\": 42}{\"a\":\"a\" 123}}",
"{[\"a}",
"{[123123e100000]}",
"{[1e]}",
"{[1,]}",
"{{,}",
"{[-foo]}",
"{å}",
"{{\"}",
"{[\"null byte not allowed\"]}",
"{[}",
"{[1,2,3]foo}",
/** Additional tests */
"Hello World!",
"<p>I am a paragraph</p>",
"",
NULL
};
int test_message_body()
{
for (int i = 0; body_pass[i]; i++)
{
stringstream ss;
ss << "GET / HTTP/1.1\r\n\r\n" << body_pass[i];
SHttpRequest parser(HttpRequest::parse(ss.str()));
TEST(parser.get() != NULL, "Valid request body should be parsed: %s",
ss.str().c_str());
TEST(parser->get_json(), "Body should be found");
TEST(parser->get_json_str() == body_pass[i], "Body value should be correct: %s",
parser->get_json_str().c_str());
}
for (int i = 0; body_pass[i]; i++)
{
stringstream ss;
ss << "GET / HTTP/1.1\r\n\r\n" << body_fail[i];
SHttpRequest parser(HttpRequest::parse(ss.str()));
TEST(parser.get() == NULL, "Invalid request body should not be parsed: %s",
ss.str().c_str());
}
return 0;
}
int main(int argc, char** argv)
{
int rc = 0;
rc += test_basic();
rc += test_headers();
rc += test_message_body();
return rc;
}