Example #1
0
bool CWebServer::GetRequestedRanges(struct MHD_Connection *connection, uint64_t totalLength, CHttpRanges &ranges)
{
  ranges.Clear();

  if (connection == NULL)
    return false;

  return ranges.Parse(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE), totalLength);
}
Example #2
0
bool HTTPRequestHandlerUtils::GetRequestedRanges(struct MHD_Connection *connection, uint64_t totalLength, CHttpRanges &ranges)
{
  ranges.Clear();

  if (connection == nullptr)
    return false;

  return ranges.Parse(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE), totalLength);
}
Example #3
0
TEST_F(TestWebServer, CanGetRangedFileRange0_2xEnd)
{
  const std::string rangedFileContent = TEST_FILES_DATA_RANGES;
  const std::string range = GenerateRangeHeaderValue(0, rangedFileContent.size() * 2);

  CHttpRanges ranges;
  ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size()));

  // get the whole file but specify a larger range
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range);
  ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
  CheckRangesTestFileResponse(curl, result, ranges);
}
Example #4
0
TEST_F(TestWebServer, CanGetRangedFileRange0_)
{
  const std::string rangedFileContent = TEST_FILES_DATA_RANGES;
  const std::string range = "bytes=0-";

  CHttpRanges ranges;
  ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size()));

  // get the whole file but specify the beginning of the range
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range);
  ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
  CheckRangesTestFileResponse(curl, result, ranges);
}
Example #5
0
TEST_F(TestWebServer, CanGetRangedFileRange_Last)
{
  const std::string rangedFileContent = TEST_FILES_DATA_RANGES;
  std::vector<std::string> rangedContent = StringUtils::Split(TEST_FILES_DATA_RANGES, ";");
  const std::string range = StringUtils::Format("bytes=-%u", static_cast<unsigned int>(rangedContent.back().size()));

  CHttpRanges ranges;
  ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size()));

  // get the whole file but specify a larger range
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range);
  ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
  CheckRangesTestFileResponse(curl, result, ranges);
}
Example #6
0
TEST_F(TestWebServer, CanGetRangedFileRangeFirst_Second)
{
  const std::string rangedFileContent = TEST_FILES_DATA_RANGES;
  std::vector<std::string> rangedContent = StringUtils::Split(TEST_FILES_DATA_RANGES, ";");
  const std::string range = GenerateRangeHeaderValue(rangedContent.front().size() + 1, rangedContent.front().size() + 1 + rangedContent.at(2).size() - 1);

  CHttpRanges ranges;
  ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size()));

  // get the whole file but specify a larger range
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range);
  ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
  CheckRangesTestFileResponse(curl, result, ranges);
}
Example #7
0
TEST_F(TestWebServer, CanGetCachedRangedFileWithExactIfRange)
{
  const std::string rangedFileContent = TEST_FILES_DATA_RANGES;
  const std::string range = "bytes=0-";

  CHttpRanges ranges;
  ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size()));

  // get the last modified date of the file
  CDateTime lastModified;
  ASSERT_TRUE(GetLastModifiedOfTestFile(TEST_FILES_RANGES, lastModified));

  // get the whole file (but ranged) with an older If-Range value
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range);
  curl.SetRequestHeader(MHD_HTTP_HEADER_IF_RANGE, lastModified.GetAsRFC1123DateTime());
  ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
  CheckRangesTestFileResponse(curl, result, ranges);
}
Example #8
0
bool CWebServer::IsRequestRanged(const HTTPRequest& request, const CDateTime &lastModified) const
{
  // parse the Range header and store it in the request object
  CHttpRanges ranges;
  bool ranged = ranges.Parse(HTTPRequestHandlerUtils::GetRequestHeaderValue(request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE));

  // handle If-Range header but only if the Range header is present
  if (ranged && lastModified.IsValid())
  {
    std::string ifRange = HTTPRequestHandlerUtils::GetRequestHeaderValue(request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_RANGE);
    if (!ifRange.empty() && lastModified.IsValid())
    {
      CDateTime ifRangeDate;
      ifRangeDate.SetFromRFC1123DateTime(ifRange);

      // check if the last modification is newer than the If-Range date
      // if so we have to server the whole file instead
      if (lastModified.GetAsUTCDateTime() > ifRangeDate)
        ranges.Clear();
    }
  }

  return !ranges.IsEmpty();
}
Example #9
0
  void CheckRangesTestFileResponse(const CCurlFile& curl, const std::string& result, const CHttpRanges& ranges)
  {
    // get the HTTP header details
    const CHttpHeader& httpHeader = curl.GetHttpHeader();

    // check the protocol line for the expected HTTP status
    std::string httpStatusString = StringUtils::Format(" %d ", MHD_HTTP_PARTIAL_CONTENT);
    std::string protocolLine = httpHeader.GetProtoLine();
    ASSERT_TRUE(protocolLine.find(httpStatusString) != std::string::npos);

    // Accept-Ranges must be "bytes"
    EXPECT_STREQ("bytes", httpHeader.GetValue(MHD_HTTP_HEADER_ACCEPT_RANGES).c_str());

    // check Last-Modified
    CDateTime lastModified;
    ASSERT_TRUE(GetLastModifiedOfTestFile(TEST_FILES_RANGES, lastModified));
    ASSERT_STREQ(lastModified.GetAsRFC1123DateTime().c_str(), httpHeader.GetValue(MHD_HTTP_HEADER_LAST_MODIFIED).c_str());

    // Cache-Control must contain "mag-age=0" and "no-cache"
    std::string cacheControl = httpHeader.GetValue(MHD_HTTP_HEADER_CACHE_CONTROL);
    EXPECT_TRUE(cacheControl.find("max-age=31536000") != std::string::npos);
    EXPECT_TRUE(cacheControl.find("public") != std::string::npos);

    // If there's no range Content-Length must be "20"
    if (ranges.IsEmpty())
    {
      EXPECT_STREQ("20", httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str());
      EXPECT_STREQ(TEST_FILES_DATA_RANGES, result.c_str());
      return;
    }

    // check Content-Range
    uint64_t firstPosition, lastPosition;
    ASSERT_TRUE(ranges.GetFirstPosition(firstPosition));
    ASSERT_TRUE(ranges.GetLastPosition(lastPosition));
    EXPECT_STREQ(HttpRangeUtils::GenerateContentRangeHeaderValue(firstPosition, lastPosition, 20).c_str(), httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_RANGE).c_str());

    std::string expectedContent = TEST_FILES_DATA_RANGES;
    const std::string expectedContentType = "text/plain";
    if (ranges.Size() == 1)
    {
      // Content-Type must be "text/html"
      EXPECT_STREQ(expectedContentType.c_str(), httpHeader.GetMimeType().c_str());

      // check the content
      CHttpRange firstRange;
      ASSERT_TRUE(ranges.GetFirst(firstRange));
      expectedContent = expectedContent.substr(firstRange.GetFirstPosition(), firstRange.GetLength());
      EXPECT_STREQ(expectedContent.c_str(), result.c_str());

      // and Content-Length
      EXPECT_STREQ(StringUtils::Format("%u", static_cast<unsigned int>(expectedContent.size())).c_str(), httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str());

      return;
    }

    // Content-Type contains the multipart boundary
    const std::string expectedMimeType = "multipart/byteranges";
    std::string mimeType = httpHeader.GetMimeType();
    ASSERT_STREQ(expectedMimeType.c_str(), mimeType.c_str());

    std::string contentType = httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_TYPE);
    std::string contentTypeStart = expectedMimeType + "; boundary=";
    // it must start with "multipart/byteranges; boundary=" followed by the boundary
    ASSERT_EQ(0, contentType.find(contentTypeStart));
    ASSERT_GT(contentType.size(), contentTypeStart.size());
    // extract the boundary
    std::string multipartBoundary = contentType.substr(contentTypeStart.size());
    ASSERT_FALSE(multipartBoundary.empty());
    multipartBoundary = "--" + multipartBoundary;

    ASSERT_EQ(0, result.find(multipartBoundary));
    std::vector<std::string> rangeParts = StringUtils::Split(result, multipartBoundary);
    // the first part is not really a part and is therefore empty (the place before the first boundary)
    ASSERT_TRUE(rangeParts.front().empty());
    rangeParts.erase(rangeParts.begin());
    // the last part is the end of the end multipart boundary
    ASSERT_STREQ("--", rangeParts.back().c_str());
    rangeParts.erase(rangeParts.begin() + rangeParts.size() - 1);
    ASSERT_EQ(ranges.Size(), rangeParts.size());

    for (size_t i = 0; i < rangeParts.size(); ++i)
    {
      std::string data = rangeParts.at(i);
      StringUtils::Trim(data, " \r\n");

      // find the separator between header and data
      size_t pos = data.find("\r\n\r\n");
      ASSERT_NE(std::string::npos, pos);

      std::string header = data.substr(0, pos + 4);
      data = data.substr(pos + 4);

      // get the expected range
      CHttpRange range;
      ASSERT_TRUE(ranges.Get(i, range));

      // parse the header of the range part
      CHttpHeader rangeHeader;
      rangeHeader.Parse(header);

      // check Content-Type
      EXPECT_STREQ(expectedContentType.c_str(), rangeHeader.GetMimeType().c_str());

      // parse and check Content-Range
      std::string contentRangeHeader = rangeHeader.GetValue(MHD_HTTP_HEADER_CONTENT_RANGE);
      std::vector<std::string> contentRangeHeaderParts = StringUtils::Split(contentRangeHeader, "/");
      ASSERT_EQ(2, contentRangeHeaderParts.size());

      // check the length of the range
      EXPECT_TRUE(StringUtils::IsNaturalNumber(contentRangeHeaderParts.back()));
      uint64_t contentRangeLength = str2uint64(contentRangeHeaderParts.back());
      EXPECT_EQ(range.GetLength(), contentRangeLength);

      // remove the leading "bytes " string from the range definition
      std::string contentRangeDefinition = contentRangeHeaderParts.front();
      ASSERT_EQ(0, contentRangeDefinition.find("bytes "));
      contentRangeDefinition = contentRangeDefinition.substr(6);

      // check the start and end positions of the range
      std::vector<std::string> contentRangeParts = StringUtils::Split(contentRangeDefinition, "-");
      ASSERT_EQ(2, contentRangeParts.size());
      EXPECT_TRUE(StringUtils::IsNaturalNumber(contentRangeParts.front()));
      uint64_t contentRangeStart = str2uint64(contentRangeParts.front());
      EXPECT_EQ(range.GetFirstPosition(), contentRangeStart);
      EXPECT_TRUE(StringUtils::IsNaturalNumber(contentRangeParts.back()));
      uint64_t contentRangeEnd = str2uint64(contentRangeParts.back());
      EXPECT_EQ(range.GetLastPosition(), contentRangeEnd);

      // make sure the length of the content matches the one of the expected range
      EXPECT_EQ(range.GetLength(), data.size());
      EXPECT_STREQ(expectedContent.substr(range.GetFirstPosition(), range.GetLength()).c_str(), data.c_str());
    }
  }
Example #10
0
TEST(TestHttpRanges, ParseUnorderedBackToBack)
{
  const uint64_t totalLength = 5;
  const CHttpRange range0_0(0, 0);
  const CHttpRange range1_1(1, 1);
  const CHttpRange range0_1(0, 1);
  const CHttpRange range2_2(2, 2);
  const CHttpRange range0_2(0, 2);
  const CHttpRange range1_2(1, 2);
  const CHttpRange range3_3(3, 3);
  const CHttpRange range0_3(0, 3);
  const CHttpRange range4_4(4, 4);
  const CHttpRange range0_4(0, 4);
  const CHttpRange range3_4(3, 4);

  CHttpRange range;

  CHttpRanges ranges;
  EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,0-0", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_1, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,0-0,2-2", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_2, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "2-2,1-1,3-3,0-0", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_3, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,1-1,0-0,2-2,3-3", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_4, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "3-3,0-0,4-4,1-1", totalLength));
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_1, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range3_4, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,1-1,2-2", totalLength));
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range1_2, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range4_4, range);
}
Example #11
0
TEST(TestHttpRanges, ParseUnorderedNotOverlapping)
{
  const uint64_t totalLength = 5;
  const CHttpRange range0_0(0, 0);
  const CHttpRange range0_1(0, 1);
  const CHttpRange range2_2(2, 2);
  const CHttpRange range2_(2, totalLength - 1);
  const CHttpRange range_1(totalLength - 1, totalLength - 1);

  CHttpRange range;

  CHttpRanges ranges;
  EXPECT_TRUE(ranges.Parse(RANGES_START "-1,0-0", totalLength));
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_0, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range_1, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "2-2,-1,0-0", totalLength));
  EXPECT_EQ(3U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_0, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range2_2, range);
  EXPECT_TRUE(ranges.Get(2, range));
  EXPECT_EQ(range_1, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "2-,0-0", totalLength));
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_0, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range2_, range);
}
Example #12
0
TEST(TestHttpRanges, ParseMulti)
{
  const uint64_t totalLength = 6;
  const CHttpRange range0_0(0, 0);
  const CHttpRange range0_1(0, 1);
  const CHttpRange range1_3(1, 3);
  const CHttpRange range2_2(2, 2);
  const CHttpRange range4_5(4, 5);
  const CHttpRange range5_5(5, 5);

  CHttpRange range;

  CHttpRanges ranges;
  EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2", totalLength));
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_0, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range2_2, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2,4-5", totalLength));
  EXPECT_EQ(3U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_0, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range2_2, range);
  EXPECT_TRUE(ranges.Get(2, range));
  EXPECT_EQ(range4_5, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,5-5", totalLength));
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_1, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range5_5, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "1-3,5-5", totalLength));
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range1_3, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range5_5, range);
}
Example #13
0
TEST(TestHttpRanges, ParseSingle)
{
  const uint64_t totalLength = 5;
  const CHttpRange range0_0(0, 0);
  const CHttpRange range0_1(0, 1);
  const CHttpRange range0_5(0, totalLength - 1);
  const CHttpRange range1_1(1, 1);
  const CHttpRange range1_3(1, 3);
  const CHttpRange range3_4(3, 4);
  const CHttpRange range4_4(4, 4);

  CHttpRange range;

  CHttpRanges ranges;
  EXPECT_TRUE(ranges.Parse(RANGES_START "0-0", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_0, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "0-1", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_1, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "0-5", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_5, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "1-1", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range1_1, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "1-3", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range1_3, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "3-4", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range3_4, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "4-4", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range4_4, range);
}
Example #14
0
TEST(TestHttpRanges, ParseInvalid)
{
  CHttpRanges ranges;

  // combinations of invalid string and invalid total length
  EXPECT_FALSE(ranges.Parse(""));
  EXPECT_FALSE(ranges.Parse("", 0));
  EXPECT_FALSE(ranges.Parse("", 1));
  EXPECT_FALSE(ranges.Parse("test", 0));
  EXPECT_FALSE(ranges.Parse(RANGES_START, 0));

  // empty range definition
  EXPECT_FALSE(ranges.Parse(RANGES_START));
  EXPECT_FALSE(ranges.Parse(RANGES_START "-"));

  // bad characters in range definition
  EXPECT_FALSE(ranges.Parse(RANGES_START "a"));
  EXPECT_FALSE(ranges.Parse(RANGES_START "1a"));
  EXPECT_FALSE(ranges.Parse(RANGES_START "1-a"));
  EXPECT_FALSE(ranges.Parse(RANGES_START "a-a"));
  EXPECT_FALSE(ranges.Parse(RANGES_START "a-1"));
  EXPECT_FALSE(ranges.Parse(RANGES_START "--"));
  EXPECT_FALSE(ranges.Parse(RANGES_START "1--"));
  EXPECT_FALSE(ranges.Parse(RANGES_START "1--2"));
  EXPECT_FALSE(ranges.Parse(RANGES_START "--2"));

  // combination of valid and empty range definitions
  EXPECT_FALSE(ranges.Parse(RANGES_START "0-1,"));
  EXPECT_FALSE(ranges.Parse(RANGES_START ",0-1"));

  // too big start position
  EXPECT_FALSE(ranges.Parse(RANGES_START "10-11", 5));

  // end position smaller than start position
  EXPECT_FALSE(ranges.Parse(RANGES_START "1-0"));
}
Example #15
0
TEST(TestHttpRanges, Add)
{
  CHttpRange range_0(0, 2);
  CHttpRange range_1(4, 6);
  CHttpRange range_2(8, 10);

  CHttpRanges ranges;
  CHttpRange range;

  ranges.Add(range_0);
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.GetFirst(range));
  EXPECT_EQ(range_0, range);
  EXPECT_TRUE(ranges.GetLast(range));
  EXPECT_EQ(range_0, range);

  ranges.Add(range_1);
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.GetFirst(range));
  EXPECT_EQ(range_0, range);
  EXPECT_TRUE(ranges.GetLast(range));
  EXPECT_EQ(range_1, range);

  ranges.Add(range_2);
  EXPECT_EQ(3U, ranges.Size());
  EXPECT_TRUE(ranges.GetFirst(range));
  EXPECT_EQ(range_0, range);
  EXPECT_TRUE(ranges.GetLast(range));
  EXPECT_EQ(range_2, range);
}
Example #16
0
int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
                      const char *url, const char *method,
                      const char *version, const char *upload_data,
                      unsigned int *upload_data_size, void **con_cls)
#endif
{
  if (cls == NULL || con_cls == NULL || *con_cls == NULL)
  {
    CLog::Log(LOGERROR, "CWebServer: invalid request received");
    return MHD_NO;
  }

  CWebServer *server = reinterpret_cast<CWebServer*>(cls);
  std::auto_ptr<ConnectionHandler> conHandler(reinterpret_cast<ConnectionHandler*>(*con_cls));
  HTTPMethod methodType = GetMethod(method);
  HTTPRequest request = { server, connection, conHandler->fullUri, url, methodType, version };

  // remember if the request was new
  bool isNewRequest = conHandler->isNew;
  // because now it isn't anymore
  conHandler->isNew = false;

  // reset con_cls and set it if still necessary
  *con_cls = NULL;

#ifdef WEBSERVER_DEBUG
  if (isNewRequest)
  {
    std::multimap<std::string, std::string> headerValues;
    GetRequestHeaderValues(connection, MHD_HEADER_KIND, headerValues);
    std::multimap<std::string, std::string> getValues;
    GetRequestHeaderValues(connection, MHD_GET_ARGUMENT_KIND, getValues);

    CLog::Log(LOGDEBUG, "webserver  [IN] %s %s %s", version, method, request.pathUrlFull.c_str());
    if (!getValues.empty())
    {
      std::string tmp;
      for (std::multimap<std::string, std::string>::const_iterator get = getValues.begin(); get != getValues.end(); ++get)
      {
        if (get != getValues.begin())
          tmp += "; ";
        tmp += get->first + " = " + get->second;
      }
      CLog::Log(LOGDEBUG, "webserver  [IN] Query arguments: %s", tmp.c_str());
    }

    for (std::multimap<std::string, std::string>::const_iterator header = headerValues.begin(); header != headerValues.end(); ++header)
      CLog::Log(LOGDEBUG, "webserver  [IN] %s: %s", header->first.c_str(), header->second.c_str());
  }
#endif

  if (!IsAuthenticated(server, connection)) 
    return AskForAuthentication(connection);

  // check if this is the first call to AnswerToConnection for this request
  if (isNewRequest)
  {
    // parse the Range header and store it in the request object
    CHttpRanges ranges;
    bool ranged = ranges.Parse(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE));

    // look for a IHTTPRequestHandler which can take care of the current request
    for (std::vector<IHTTPRequestHandler *>::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); ++it)
    {
      IHTTPRequestHandler *requestHandler = *it;
      if (requestHandler->CanHandleRequest(request))
      {
        // we found a matching IHTTPRequestHandler so let's get a new instance for this request
        IHTTPRequestHandler *handler = requestHandler->Create(request);

        // if we got a GET request we need to check if it should be cached
        if (methodType == GET)
        {
          if (handler->CanBeCached())
          {
            bool cacheable = true;

            // handle Cache-Control
            std::string cacheControl = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CACHE_CONTROL);
            if (!cacheControl.empty())
            {
              std::vector<std::string> cacheControls = StringUtils::Split(cacheControl, ",");
              for (std::vector<std::string>::const_iterator it = cacheControls.begin(); it != cacheControls.end(); ++it)
              {
                std::string control = *it;
                control = StringUtils::Trim(control);

                // handle no-cache
                if (control.compare(HEADER_VALUE_NO_CACHE) == 0)
                  cacheable = false;
              }
            }

            if (cacheable)
            {
              // handle Pragma (but only if "Cache-Control: no-cache" hasn't been set)
              std::string pragma = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_PRAGMA);
              if (pragma.compare(HEADER_VALUE_NO_CACHE) == 0)
                cacheable = false;
            }

            CDateTime lastModified;
            if (handler->GetLastModifiedDate(lastModified) && lastModified.IsValid())
            {
              // handle If-Modified-Since or If-Unmodified-Since
              std::string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_MODIFIED_SINCE);
              std::string ifUnmodifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE);

              CDateTime ifModifiedSinceDate;
              CDateTime ifUnmodifiedSinceDate;
              // handle If-Modified-Since (but only if the response is cacheable)
              if (cacheable &&
                ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince) &&
                lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
              {
                struct MHD_Response *response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
                if (response == NULL)
                {
                  CLog::Log(LOGERROR, "CWebServer: failed to create a HTTP 304 response");
                  return MHD_NO;
                }

                return FinalizeRequest(handler, MHD_HTTP_NOT_MODIFIED, response);
              }
              // handle If-Unmodified-Since
              else if (ifUnmodifiedSinceDate.SetFromRFC1123DateTime(ifUnmodifiedSince) &&
                lastModified.GetAsUTCDateTime() > ifUnmodifiedSinceDate)
                return SendErrorResponse(connection, MHD_HTTP_PRECONDITION_FAILED, methodType);
            }

            // handle If-Range header but only if the Range header is present
            if (ranged && lastModified.IsValid())
            {
              std::string ifRange = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_RANGE);
              if (!ifRange.empty() && lastModified.IsValid())
              {
                CDateTime ifRangeDate;
                ifRangeDate.SetFromRFC1123DateTime(ifRange);

                // check if the last modification is newer than the If-Range date
                // if so we have to server the whole file instead
                if (lastModified.GetAsUTCDateTime() > ifRangeDate)
                  ranges.Clear();
              }
            }

            // pass the requested ranges on to the request handler
            handler->SetRequestRanged(!ranges.IsEmpty());
          }
        }
        // if we got a POST request we need to take care of the POST data
        else if (methodType == POST)
        {
          conHandler->requestHandler = handler;

          // get the content-type of the POST data
          std::string contentType = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
          if (!contentType.empty())
          {
            // if the content-type is application/x-ww-form-urlencoded or multipart/form-data we can use MHD's POST processor
            if (StringUtils::EqualsNoCase(contentType, MHD_HTTP_POST_ENCODING_FORM_URLENCODED) ||
                StringUtils::EqualsNoCase(contentType, MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA))
            {
              // Get a new MHD_PostProcessor
              conHandler->postprocessor = MHD_create_post_processor(connection, MAX_POST_BUFFER_SIZE, &CWebServer::HandlePostField, (void*)conHandler.get());

              // MHD doesn't seem to be able to handle this post request
              if (conHandler->postprocessor == NULL)
              {
                CLog::Log(LOGERROR, "CWebServer: unable to create HTTP POST processor for %s", url);

                delete conHandler->requestHandler;

                return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType);
              }
            }
          }

          // otherwise we need to handle the POST data ourselves which is done in the next call to AnswerToConnection
          // as ownership of the connection handler is passed to libmicrohttpd we must not destroy it 
          *con_cls = conHandler.release();

          return MHD_YES;
        }

        return HandleRequest(handler);
      }
    }
  }
  // this is a subsequent call to AnswerToConnection for this request
  else
  {
    // again we need to take special care of the POST data
    if (methodType == POST)
    {
      if (conHandler->requestHandler == NULL)
      {
        CLog::Log(LOGERROR, "CWebServer: cannot handle partial HTTP POST for %s request because there is no valid request handler available", url);
        return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType);
      }

      // we only need to handle POST data if there actually is data left to handle
      if (*upload_data_size > 0)
      {
        // either use MHD's POST processor
        if (conHandler->postprocessor != NULL)
          MHD_post_process(conHandler->postprocessor, upload_data, *upload_data_size);
        // or simply copy the data to the handler
        else
          conHandler->requestHandler->AddPostData(upload_data, *upload_data_size);

        // signal that we have handled the data
        *upload_data_size = 0;

        // we may need to handle more POST data which is done in the next call to AnswerToConnection
        // as ownership of the connection handler is passed to libmicrohttpd we must not destroy it 
        *con_cls = conHandler.release();

        return MHD_YES;
      }
      // we have handled all POST data so it's time to invoke the IHTTPRequestHandler
      else
      {
        if (conHandler->postprocessor != NULL)
          MHD_destroy_post_processor(conHandler->postprocessor);

        return HandleRequest(conHandler->requestHandler);
      }
    }
    // it's unusual to get more than one call to AnswerToConnection for none-POST requests, but let's handle it anyway
    else
    {
      for (std::vector<IHTTPRequestHandler *>::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); ++it)
      {
        IHTTPRequestHandler *requestHandler = *it;
        if (requestHandler->CanHandleRequest(request))
          return HandleRequest(requestHandler->Create(request));
      }
    }
  }

  CLog::Log(LOGERROR, "CWebServer: couldn't find any request handler for %s", url);
  return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
}
Example #17
0
TEST(TestHttpRanges, ParseUnorderedOverlapping)
{
  const uint64_t totalLength = 5;
  const CHttpRange range0_0(0, 0);
  const CHttpRange range0_1(0, 1);
  const CHttpRange range0_2(0, 2);
  const CHttpRange range0_3(0, 3);
  const CHttpRange range0_4(0, 4);
  const CHttpRange range2_4(2, 4);

  CHttpRange range;

  CHttpRanges ranges;
  EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,0-0", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_1, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "0-2,0-0,0-1", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_2, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,1-2,0-0", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_2, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "0-2,0-0,1-3", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_3, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "2-3,1-2,0-1,3-4", totalLength));
  EXPECT_EQ(1U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_4, range);

  EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,0-0,2-4,2-3", totalLength));
  EXPECT_EQ(2U, ranges.Size());
  EXPECT_TRUE(ranges.Get(0, range));
  EXPECT_EQ(range0_0, range);
  EXPECT_TRUE(ranges.Get(1, range));
  EXPECT_EQ(range2_4, range);
}