/*
* RequestParserTests.cpp
*
* Copyright (C) 2019 by RStudio, PBC
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
#include
#include
#include
#include
#include
#include
namespace rstudio {
namespace core {
namespace http {
namespace tests {
std::string simpleRequest(std::string* pBodyStr)
{
std::string bodyStr =
"--boundary\r\n"
"Content-Disposition: form-data; name=\"field1\"\r\n\r\n"
"value1\r\n"
"--boundary\r\n"
"Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r\n"
"Content-Type: text/plain\r\n\r\n"
"This is a simple text file\r\n"
"--boundary--\r\n";
*pBodyStr = bodyStr;
std::string bodySizeStr = safe_convert::numberToString(bodyStr.size());
std::string requestStr =
"POST /test HTTP/1.1\r\n"
"Host: foo.example\r\n"
"Content-Type: multipart/form-data; boundary=boundary\r\n"
"Content-Length: " + bodySizeStr + "\r\n\r\n" + bodyStr;
return requestStr;
}
std::string generateRandomBytes()
{
// generate a large random payload
uint32_t payloadSize = 1024*1024*2; // 2 MB
std::vector fileVector;
REQUIRE_FALSE(core::system::crypto::random(payloadSize, &fileVector));
std::string fileBytes;
std::copy(fileVector.begin(), fileVector.end(), std::back_inserter(fileBytes));
return fileBytes;
}
std::string complexRequest(const std::string& fileBytes,
std::string* pBodyStr)
{
std::string bodyStr =
"--boundary\r\n"
"Content-Disposition: form-data; name=\"field1\"\r\n\r\n"
"value1\r\n"
"--boundary\r\n"
"Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r\n"
"Content-Type: application/octet-stream\r\n\r\n" +
fileBytes + "\r\n"
"--boundary--\r\n";
*pBodyStr = bodyStr;
std::string bodySizeStr = safe_convert::numberToString(bodyStr.size());
std::string requestStr =
"POST /test HTTP/1.1\r\n"
"Host: foo.example\r\n"
"Content-Type: multipart/form-data; boundary=boundary\r\n"
"Content-Length: " + bodySizeStr + "\r\n\r\n" + bodyStr;
return requestStr;
}
FormHandler formHandler(const std::string& expectedData)
{
boost::shared_ptr data = boost::make_shared();
auto formHandler = [=](const std::string& formData, bool complete) -> bool
{
(*data) += formData;
if (complete)
{
REQUIRE(*data == expectedData);
}
return true;
};
return formHandler;
}
test_context("RequestParserTests")
{
test_that("Simple form parsing works")
{
std::string bodyStr;
std::string requestStr = simpleRequest(&bodyStr);
Request request;
FormHandler handler = formHandler(bodyStr);
RequestParser parser;
parser.setFormHandler(handler);
RequestParser::status status = parser.parse(request, requestStr.c_str(), requestStr.c_str() + requestStr.size());
REQUIRE(status == RequestParser::headers_parsed);
status = parser.parse(request, requestStr.c_str(), requestStr.c_str() + requestStr.size());
REQUIRE(status == RequestParser::form_complete);
}
test_that("Simple form parsing works, one byte at a time")
{
std::string bodyStr;
std::string requestStr = simpleRequest(&bodyStr);
Request request;
FormHandler handler = formHandler(bodyStr);
RequestParser parser;
parser.setFormHandler(handler);
RequestParser::status status;
for (size_t i = 0; i < requestStr.size() - 1; ++i)
{
status = parser.parse(request, requestStr.c_str() + i, requestStr.c_str() + i + 1);
REQUIRE((status == RequestParser::headers_parsed || status == RequestParser::incomplete));
if (status == RequestParser::headers_parsed)
{
// need to pass the same buffer to resume
i--;
}
}
status = parser.parse(request, requestStr.c_str() + requestStr.size() - 1, requestStr.c_str() + requestStr.size());
REQUIRE(status == RequestParser::form_complete);
}
test_that("Long, complicated form parsing works")
{
std::string fileBytes = generateRandomBytes();
std::string bodyStr;
std::string requestStr = complexRequest(fileBytes, &bodyStr);
Request request;
FormHandler handler = formHandler(bodyStr);
RequestParser parser;
parser.setFormHandler(handler);
RequestParser::status status = parser.parse(request, requestStr.c_str(), requestStr.c_str() + requestStr.size());
REQUIRE(status == RequestParser::headers_parsed);
status = parser.parse(request, requestStr.c_str(), requestStr.c_str() + requestStr.size());
REQUIRE(status == RequestParser::form_complete);
}
test_that("Long, complicated form parsing works, one byte at a time")
{
std::string fileBytes = generateRandomBytes();
std::string bodyStr;
std::string requestStr = complexRequest(fileBytes, &bodyStr);
Request request;
FormHandler handler = formHandler(bodyStr);
RequestParser parser;
parser.setFormHandler(handler);
RequestParser::status status;
for (size_t i = 0; i < requestStr.size() - 1; ++i)
{
status = parser.parse(request, requestStr.c_str() + i, requestStr.c_str() + i + 1);
REQUIRE((status == RequestParser::headers_parsed || status == RequestParser::incomplete));
if (status == RequestParser::headers_parsed)
{
// need to pass the same buffer to resume
i--;
}
}
status = parser.parse(request, requestStr.c_str() + requestStr.size() - 1, requestStr.c_str() + requestStr.size());
REQUIRE(status == RequestParser::form_complete);
}
test_that("Long, complicated form parsing works, random byte boundaries")
{
std::string fileBytes = generateRandomBytes();
std::string bodyStr;
std::string requestStr = complexRequest(fileBytes, &bodyStr);
Request request;
FormHandler handler = formHandler(bodyStr);
RequestParser parser;
parser.setFormHandler(handler);
RequestParser::status status;
for (size_t i = 0; i < requestStr.size();)
{
size_t byteAmount = rand() % 8192 + 1;
if (byteAmount > requestStr.size() - i)
byteAmount = requestStr.size() - i;
status = parser.parse(request, requestStr.c_str() + i, requestStr.c_str() + i + byteAmount);
REQUIRE((status == RequestParser::headers_parsed || status == RequestParser::incomplete || status == RequestParser::form_complete));
if (status == RequestParser::headers_parsed)
{
// need to pass the same buffer to resume
status = parser.parse(request, requestStr.c_str() + i, requestStr.c_str() + i + byteAmount);
REQUIRE(status == RequestParser::incomplete);
}
else if (status == RequestParser::form_complete)
{
break;
}
i += byteAmount;
}
}
}
} // end namespace tests
} // end namespace http
} // end namespace core
} // end namespace rstudio