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:
parent
4eb121ce35
commit
c937457738
@ -84,3 +84,25 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The Jansson JSON parsing library is distributed under the MIT license.
|
||||
|
||||
Copyright (c) 2009-2016 Petri Lehtinen <petri@digip.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
@ -6,7 +6,9 @@
|
||||
# JANSSON_INCLUDE_DIR - Path to Jansson headers
|
||||
|
||||
find_path(JANSSON_INCLUDE_DIR jansson.h)
|
||||
find_library(JANSSON_LIBRARIES NAMES libjansson.so libjansson.a)
|
||||
|
||||
# Use the static library
|
||||
find_library(JANSSON_LIBRARIES NAMES libjansson.a)
|
||||
|
||||
if (JANSSON_INCLUDE_DIR AND JANSSON_LIBRARIES)
|
||||
message(STATUS "Found Jansson: ${JANSSON_LIBRARIES}")
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user