[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pearsonca/rstudio/master/src/cpp/core/http/RequestParserTests.cpp [Back]  [Original]

/*
 * 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


Web Proxy Viewer  |  New URL  |  Original Page