
The resource handler system is now usable but it doesn't perform anything useful. Although, this will allows it to be tested for correctness. Minor fixes to HttpResponse output and renaming of functions.
467 lines
12 KiB
C++
467 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
|
*
|
|
* Change Date: 2019-07-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
|
|
#include "../maxscale/httprequest.hh"
|
|
#include "../maxscale/httpresponse.hh"
|
|
|
|
#include <sstream>
|
|
|
|
using std::stringstream;
|
|
using std::string;
|
|
|
|
#define TEST(a, b, ...) do{if (!(a)){printf(b "\n", ##__VA_ARGS__);return 1;}}while(false)
|
|
|
|
const char* verbs_pass[] =
|
|
{
|
|
"GET",
|
|
"PUT",
|
|
"POST",
|
|
"OPTIONS",
|
|
"PATCH",
|
|
"HEAD",
|
|
NULL
|
|
};
|
|
|
|
const char* verbs_fail[] =
|
|
{
|
|
"LOAD",
|
|
"STORE",
|
|
"PUBLISH",
|
|
"Something that's not a verb",
|
|
"⬠",
|
|
NULL
|
|
};
|
|
|
|
static struct
|
|
{
|
|
const char* input;
|
|
const char* output;
|
|
} paths_pass[] =
|
|
{
|
|
{ "/", "/" },
|
|
{ "*", "*" },
|
|
{ "/test/", "/test" },
|
|
{ "/test", "/test" },
|
|
{ "/servers/list", "/servers/list" },
|
|
{ "/servers/list/", "/servers/list" },
|
|
{ "/?test=true", "/" },
|
|
{ "/test/?test=y", "/test" },
|
|
{ "/?", "/" },
|
|
{}
|
|
};
|
|
|
|
const char* paths_fail[] =
|
|
{
|
|
"-strikethrough-",
|
|
"_underline_",
|
|
"*bold*",
|
|
"?",
|
|
NULL
|
|
};
|
|
|
|
const char* proto_pass[] =
|
|
{
|
|
"HTTP/1.1",
|
|
NULL
|
|
};
|
|
|
|
const char* proto_fail[] =
|
|
{
|
|
"HTTP/2.0",
|
|
"SMTP/0.0",
|
|
"CDC/1.0",
|
|
NULL
|
|
};
|
|
|
|
int test_basic()
|
|
{
|
|
/** Test parts that should pass */
|
|
for (int i = 0; verbs_pass[i]; i++)
|
|
{
|
|
for (int j = 0; paths_pass[j].input; j++)
|
|
{
|
|
for (int k = 0; proto_pass[k]; k++)
|
|
{
|
|
stringstream ss;
|
|
ss << verbs_pass[i] << " " << paths_pass[j].input << " " << proto_pass[k] << "\r\n\r\n";
|
|
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
|
TEST(parser.get() != NULL, "Valid HTTP request should be parsed: %s", ss.str().c_str());
|
|
TEST(parser->get_uri() == string(paths_pass[j].output),
|
|
"The request path '%s' should be correct: %s",
|
|
paths_pass[j].output, parser->get_uri().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Test parts that should fail */
|
|
for (int i = 0; verbs_fail[i]; i++)
|
|
{
|
|
for (int j = 0; paths_fail[j]; j++)
|
|
{
|
|
for (int k = 0; proto_fail[k]; k++)
|
|
{
|
|
stringstream ss;
|
|
ss << verbs_fail[i] << " " << paths_fail[j] << " " << proto_fail[k] << "\r\n\r\n";
|
|
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
|
TEST(parser.get() == NULL, "Invalid HTTP request should not be parsed: %s", ss.str().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct
|
|
{
|
|
const char* key;
|
|
const char* value;
|
|
} headers_pass[] =
|
|
{
|
|
{"Accept", "*/*"},
|
|
{"User-Agent", "curl/7.51.0"},
|
|
{"Authorization", "bWF4dXNlcjptYXhwd2QK"},
|
|
{"Content-Type", "application/json"},
|
|
{"Date", "1.1.2017 10:10:10"},
|
|
{"Host", "127.0.0.1:8080"},
|
|
{"If-Match", "bWF4dXNlcjptYXhwd2QK"},
|
|
{"If-Modified-Since", "Mon, 18 Nov 2013 08:14:29 -0600"},
|
|
{"If-None-Match", "bWF4dXNlcjptYXhwd2QK"},
|
|
{"If-Unmodified-Since", "Mon, 18 Nov 2013 08:14:29 -0600"},
|
|
{"X-HTTP-Method-Override", "PATCH"},
|
|
{"Allow", "GET, PATCH, PUT"},
|
|
{"Accept-Patch", "application/json-patch"},
|
|
{"Date", "Mon, 18 Nov 2013 08:14:29 -0600"},
|
|
{"ETag", "bWF4dXNlcjptYXhwd2QK"},
|
|
{"Last-Modified", "Mon, 18 Nov 2013 08:14:29 -0600"},
|
|
{"Location", "/servers/server1"},
|
|
{"WWW-Authenticate", "Basic"},
|
|
{0}
|
|
};
|
|
|
|
int test_headers()
|
|
{
|
|
for (int i = 0; headers_pass[i].key; i++)
|
|
{
|
|
stringstream ss;
|
|
ss << "GET / HTTP/1.1\r\n" << headers_pass[i].key << ": "
|
|
<< headers_pass[i].value << "\r\n\r\n";
|
|
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
|
TEST(parser.get() != NULL, "Valid HTTP request should be parsed: %s", ss.str().c_str());
|
|
TEST(parser->get_header(headers_pass[i].key).length() > 0, "Header should be found");
|
|
TEST(parser->get_header(headers_pass[i].key) == string(headers_pass[i].value),
|
|
"Header value should be correct: %s", parser->get_header(headers_pass[i].key).c_str());
|
|
}
|
|
|
|
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
|
|
};
|
|
|
|
const char* body_verbs_pass[] =
|
|
{
|
|
"PUT",
|
|
"POST",
|
|
"PATCH",
|
|
NULL
|
|
};
|
|
|
|
int test_message_body()
|
|
{
|
|
for (int i = 0; body_pass[i]; i++)
|
|
{
|
|
for (int j = 0; body_verbs_pass[j]; j++)
|
|
{
|
|
/** Only PUT/POST/PATCH methods should have request bodies */
|
|
stringstream ss;
|
|
ss << body_verbs_pass[j] << " / 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++)
|
|
{
|
|
for (int j = 0; verbs_pass[j]; j++)
|
|
{
|
|
stringstream ss;
|
|
ss << verbs_pass[j] << " / 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;
|
|
}
|
|
|
|
static struct
|
|
{
|
|
const char* input;
|
|
const char* key;
|
|
const char* value;
|
|
} options_pass[] =
|
|
{
|
|
{ "/", "", "" },
|
|
{ "*", "", "" },
|
|
{ "/?a=b", "a", "b" },
|
|
{ "/?a=b,c=d", "a", "b" },
|
|
{ "/?a=b,c=d", "c", "d" },
|
|
{ "/test?q=w", "q", "w" },
|
|
{ "/servers/list?all=false", "all", "false" },
|
|
{ "/servers/list/?pretty=true", "pretty", "true"},
|
|
{ "/?test=true", "test", "true" },
|
|
{ "/test/?test=y", "test", "y" },
|
|
{ "/?", "", "" },
|
|
{}
|
|
};
|
|
|
|
const char* options_fail[] =
|
|
{
|
|
"/?,",
|
|
"/??",
|
|
"/test?/",
|
|
"/test/?a,b",
|
|
"/test?a,",
|
|
NULL
|
|
};
|
|
|
|
int test_options()
|
|
{
|
|
for (int i = 0; options_pass[i].input; i++)
|
|
{
|
|
stringstream ss;
|
|
ss << "GET " << options_pass[i].input << " HTTP/1.1\r\n\r\n";
|
|
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
|
|
|
TEST(parser.get() != NULL, "Valid option should be parsed: %s", ss.str().c_str());
|
|
TEST(parser->get_option(options_pass[i].key) == options_pass[i].value,
|
|
"The option value for '%s' should be '%s': %s",
|
|
options_pass[i].key, options_pass[i].value,
|
|
parser->get_option(options_pass[i].key).c_str());
|
|
}
|
|
|
|
for (int i = 0; options_fail[i]; i++)
|
|
{
|
|
stringstream ss;
|
|
ss << "GET " << options_fail[i] << " HTTP/1.1\r\n\r\n";
|
|
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
|
TEST(parser.get() == NULL, "Invalid option should not be parsed: %s", ss.str().c_str());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int test_response()
|
|
{
|
|
TEST(HttpResponse().get_response().find("200 OK") != string::npos,
|
|
"Default constructor should return a 200 OK with no body");
|
|
TEST(HttpResponse(HTTP_200_OK, "Test").get_response().find("\r\n\r\nTest") != string::npos,
|
|
"Custom payload should be found in the response");
|
|
TEST(HttpResponse(HTTP_204_NO_CONTENT).get_response().find("204 No Content") != string::npos,
|
|
"Using custom header should generate correct response");
|
|
|
|
HttpResponse response(HTTP_502_BAD_GATEWAY, "A Bad gateway");
|
|
TEST(response.get_response().find("\r\n\r\nA Bad gateway") != string::npos &&
|
|
response.get_response().find("502 Bad Gateway") != string::npos,
|
|
"Both custom response body and return code should be found");
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct
|
|
{
|
|
const char* input;
|
|
const char* value;
|
|
int offset;
|
|
size_t size;
|
|
} resource_parts[] =
|
|
{
|
|
{"/", "", 0, 1},
|
|
{"/?a=b", "", 0, 1},
|
|
|
|
{"/servers/", "servers", 0, 1},
|
|
{"/servers", "servers", 0, 1},
|
|
{"servers", "servers", 0, 1},
|
|
|
|
{"/servers/my-server", "servers", 0, 2},
|
|
{"/servers/my-server/", "servers", 0, 2},
|
|
{"servers/my-server", "servers", 0, 2},
|
|
{"/servers/my-server", "my-server", 1, 2},
|
|
{"/servers/my-server/", "my-server", 1, 2},
|
|
{"servers/my-server", "my-server", 1, 2},
|
|
|
|
{"/servers/my-server/user", "servers", 0, 3},
|
|
{"/servers/my-server/user", "servers", 0, 3},
|
|
{"servers/my-server/user", "servers", 0, 3},
|
|
{"/servers/my-server/user", "my-server", 1, 3},
|
|
{"/servers/my-server/user/", "my-server", 1, 3},
|
|
{"servers/my-server/user", "my-server", 1, 3},
|
|
{"/servers/my-server/user", "user", 2, 3},
|
|
{"/servers/my-server/user/", "user", 2, 3},
|
|
{"servers/my-server/user", "user", 2, 3},
|
|
|
|
{"/servers?a=b", "servers", 0, 1},
|
|
{"/servers/?a=b", "servers", 0, 1},
|
|
{"servers/?a=b", "servers", 0, 1},
|
|
{"/servers/my-server?a=b", "my-server", 1, 2},
|
|
{"/servers/my-server/?a=b", "my-server", 1, 2},
|
|
{"servers/my-server/?a=b", "my-server", 1, 2},
|
|
{"/servers/my-server/user?a=b", "user", 2, 3},
|
|
{"/servers/my-server/user/?a=b", "user", 2, 3},
|
|
{"servers/my-server/user?a=b", "user", 2, 3},
|
|
|
|
{}
|
|
};
|
|
|
|
int test_resource_parts()
|
|
{
|
|
for (int i = 0; resource_parts[i].input; i++)
|
|
{
|
|
stringstream ss;
|
|
ss << "GET " << resource_parts[i].input << " HTTP/1.1\r\n\r\n";
|
|
SHttpRequest request(HttpRequest::parse(ss.str()));
|
|
|
|
TEST(request.get(), "Request should be OK: %s", ss.str().c_str());
|
|
|
|
TEST(request->uri_part_count() == resource_parts[i].size,
|
|
"Request should have %lu parts: %lu", resource_parts[i].size,
|
|
request->uri_part_count());
|
|
|
|
string value = request->uri_part(resource_parts[i].offset);
|
|
TEST(value == resource_parts[i].value,
|
|
"Request part at %d should be '%s': %s", resource_parts[i].offset,
|
|
resource_parts[i].value, value.c_str());
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc += test_basic();
|
|
rc += test_headers();
|
|
rc += test_message_body();
|
|
rc += test_response();
|
|
rc += test_resource_parts();
|
|
|
|
return rc;
|
|
}
|