Пример #1
0
  void CheckRangesTestFileResponse(const CCurlFile& curl, int httpStatus = MHD_HTTP_OK, bool empty = false)
  {
    // 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 ", httpStatus);
    std::string protocolLine = httpHeader.GetProtoLine();
    ASSERT_TRUE(protocolLine.find(httpStatusString) != std::string::npos);

    // Content-Type must be "text/html"
    EXPECT_STREQ("text/plain", httpHeader.GetMimeType().c_str());
    // check Content-Length
    if (empty)
      EXPECT_STREQ("0", httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str());
    else
      EXPECT_STREQ("20", httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str());
    // 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);
  }
Пример #2
0
TEST_F(TestWebServer, CanGetCachedFileWithOlderIfUnmodifiedSince)
{
  // get the last modified date of the file
  CDateTime lastModified;
  ASSERT_TRUE(GetLastModifiedOfTestFile(TEST_FILES_RANGES, lastModified));
  CDateTime lastModifiedOlder = lastModified - CDateTimeSpan(1, 0, 0, 0);

  // get the file with an older If-Unmodified-Since value
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, "");
  curl.SetRequestHeader(MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE, lastModifiedOlder.GetAsRFC1123DateTime());
  ASSERT_FALSE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
}
Пример #3
0
TEST_F(TestWebServer, CanGetCachedFileWithExactIfUnmodifiedSince)
{
  // get the last modified date of the file
  CDateTime lastModified;
  ASSERT_TRUE(GetLastModifiedOfTestFile(TEST_FILES_RANGES, lastModified));

  // get the file with an older If-Unmodified-Since value
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, "");
  curl.SetRequestHeader(MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE, lastModified.GetAsRFC1123DateTime());
  ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
  EXPECT_STREQ(TEST_FILES_DATA_RANGES, result.c_str());
  CheckRangesTestFileResponse(curl);
}
Пример #4
0
TEST_F(TestWebServer, CanGetCachedFileWithExactIfModifiedSince)
{
  // get the last modified date of the file
  CDateTime lastModified;
  ASSERT_TRUE(GetLastModifiedOfTestFile(TEST_FILES_RANGES, lastModified));

  // get the file with the exact If-Modified-Since value
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, "");
  curl.SetRequestHeader(MHD_HTTP_HEADER_IF_MODIFIED_SINCE, lastModified.GetAsRFC1123DateTime());
  ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
  ASSERT_TRUE(result.empty());
  CheckRangesTestFileResponse(curl, MHD_HTTP_NOT_MODIFIED, true);
}
Пример #5
0
TEST_F(TestWebServer, CanGetCachedFileWithNewerIfModifiedSinceForcingNoCache)
{
  // get the last modified date of the file
  CDateTime lastModified;
  ASSERT_TRUE(GetLastModifiedOfTestFile(TEST_FILES_RANGES, lastModified));
  CDateTime lastModifiedNewer = lastModified + CDateTimeSpan(1, 0, 0, 0);

  // get the file with a newer If-Modified-Since value but forcing no caching
  std::string result;
  CCurlFile curl;
  curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, "");
  curl.SetRequestHeader(MHD_HTTP_HEADER_IF_MODIFIED_SINCE, lastModifiedNewer.GetAsRFC1123DateTime());
  curl.SetRequestHeader(MHD_HTTP_HEADER_CACHE_CONTROL, "no-cache");
  ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result));
  EXPECT_STREQ(TEST_FILES_DATA_RANGES, result.c_str());
  CheckRangesTestFileResponse(curl);
}
Пример #6
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);
}
Пример #7
0
  void CheckHtmlTestFileResponse(const CCurlFile& curl)
  {
    // get the HTTP header details
    const CHttpHeader& httpHeader = curl.GetHttpHeader();

    // Content-Type must be "text/html"
    EXPECT_STREQ("text/html", httpHeader.GetMimeType().c_str());
    // Content-Length must be "4"
    EXPECT_STREQ("4", httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str());
    // 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_HTML, 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=0") != std::string::npos);
    EXPECT_TRUE(cacheControl.find("no-cache") != std::string::npos);
  }
Пример #8
0
int CWebServer::FinalizeRequest(IHTTPRequestHandler *handler, int responseStatus, struct MHD_Response *response)
{
  if (handler == NULL || response == NULL)
    return MHD_NO;

  const HTTPRequest &request = handler->GetRequest();
  const HTTPResponseDetails &responseDetails = handler->GetResponseDetails();

  // if the request handler has set a content type and it hasn't been set as a header, add it 
  if (!responseDetails.contentType.empty())
    handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_TYPE, responseDetails.contentType);

  // if the request handler has set a last modified date and it hasn't been set as a header, add it
  CDateTime lastModified;
  if (handler->GetLastModifiedDate(lastModified) && lastModified.IsValid())
    handler->AddResponseHeader(MHD_HTTP_HEADER_LAST_MODIFIED, lastModified.GetAsRFC1123DateTime());

  // check if the request handler has set Cache-Control and add it if not
  if (!handler->HasResponseHeader(MHD_HTTP_HEADER_CACHE_CONTROL))
  {
    int maxAge = handler->GetMaximumAgeForCaching();
    if (handler->CanBeCached() && maxAge == 0 && !responseDetails.contentType.empty())
    {
      // don't cache HTML, CSS and JavaScript files
      if (!StringUtils::EqualsNoCase(responseDetails.contentType, "text/html") &&
          !StringUtils::EqualsNoCase(responseDetails.contentType, "text/css") &&
          !StringUtils::EqualsNoCase(responseDetails.contentType, "application/javascript"))
        maxAge = CDateTimeSpan(365, 0, 0, 0).GetSecondsTotal();
    }

    // if the response can't be cached or the maximum age is 0 force the client not to cache
    if (!handler->CanBeCached() || maxAge == 0)
      handler->AddResponseHeader(MHD_HTTP_HEADER_CACHE_CONTROL, "private, max-age=0, " HEADER_VALUE_NO_CACHE);
    else
    {
      // create the value of the Cache-Control header
      std::string cacheControl = StringUtils::Format("public, max-age=%d", maxAge);

      // check if the response contains a Set-Cookie header because they must not be cached
      if (handler->HasResponseHeader(MHD_HTTP_HEADER_SET_COOKIE))
        cacheControl += ", no-cache=\"set-cookie\"";

      // set the Cache-Control header
      handler->AddResponseHeader(MHD_HTTP_HEADER_CACHE_CONTROL, cacheControl);

      // set the Expires header
      CDateTime expiryTime = CDateTime::GetCurrentDateTime() + CDateTimeSpan(0, 0, 0, maxAge);
      handler->AddResponseHeader(MHD_HTTP_HEADER_EXPIRES, expiryTime.GetAsRFC1123DateTime());
    }
  }

  // if the request handler can handle ranges and it hasn't been set as a header, add it
  if (handler->CanHandleRanges())
    handler->AddResponseHeader(MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes");
  else
    handler->AddResponseHeader(MHD_HTTP_HEADER_ACCEPT_RANGES, "none");

  // add MHD_HTTP_HEADER_CONTENT_LENGTH
  if (responseDetails.totalLength > 0)
    handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_LENGTH, StringUtils::Format("%" PRIu64, responseDetails.totalLength));

  // add all headers set by the request handler
  for (std::multimap<std::string, std::string>::const_iterator it = responseDetails.headers.begin(); it != responseDetails.headers.end(); ++it)
    AddHeader(response, it->first, it->second);

#ifdef WEBSERVER_DEBUG
  std::multimap<std::string, std::string> headerValues;
  GetRequestHeaderValues(request.connection, MHD_RESPONSE_HEADER_KIND, headerValues);

  CLog::Log(LOGDEBUG, "webserver [OUT] %s %d %s", request.version.c_str(), responseStatus, request.pathUrlFull.c_str());

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

  int ret = MHD_queue_response(request.connection, responseStatus, response);
  MHD_destroy_response(response);
  delete handler;

  return ret;
}
Пример #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());
    }
  }
Пример #10
0
int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode)
{
  CFile *file = new CFile();

#ifdef WEBSERVER_DEBUG
  CLog::Log(LOGDEBUG, "webserver  [IN] %s", strURL.c_str());
  multimap<string, string> headers;
  if (GetRequestHeaderValues(connection, MHD_HEADER_KIND, headers) > 0)
  {
    for (multimap<string, string>::const_iterator header = headers.begin(); header != headers.end(); header++)
      CLog::Log(LOGDEBUG, "webserver  [IN] %s: %s", header->first.c_str(), header->second.c_str());
  }
#endif

  if (file->Open(strURL, READ_NO_CACHE))
  {
    bool getData = true;
    bool ranged = false;
    int64_t fileLength = file->GetLength();

    // try to get the file's last modified date
    CDateTime lastModified;
    if (!GetLastModifiedDateTime(file, lastModified))
      lastModified.Reset();

    // get the MIME type for the Content-Type header
    CStdString ext = URIUtils::GetExtension(strURL);
    ext = ext.ToLower();
    string mimeType = CreateMimeTypeFromExtension(ext.c_str());

    if (methodType != HEAD)
    {
      int64_t firstPosition = 0;
      int64_t lastPosition = fileLength - 1;
      uint64_t totalLength = 0;
      HttpFileDownloadContext *context = new HttpFileDownloadContext();
      context->file = file;
      context->rangesLength = fileLength;
      context->contentType = mimeType;
      context->boundaryWritten = false;
      context->writePosition = 0;

      if (methodType == GET)
      {
        // handle If-Modified-Since
        string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Modified-Since");
        if (!ifModifiedSince.empty() && lastModified.IsValid())
        {
          CDateTime ifModifiedSinceDate;
          ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince);

          if (lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
          {
            getData = false;
            response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
            responseCode = MHD_HTTP_NOT_MODIFIED;
          }
        }

        if (getData)
        {
          // handle Range header
          context->rangesLength = ParseRangeHeader(GetRequestHeaderValue(connection, MHD_HEADER_KIND, "Range"), fileLength, context->ranges, firstPosition, lastPosition);

          // handle If-Range header but only if the Range header is present
          if (!context->ranges.empty())
          {
            string ifRange = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "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)
                context->ranges.clear();
            }
          }
        }
      }

      if (getData)
      {
        // if there are no ranges, add the whole range
        if (context->ranges.empty() || context->rangesLength == fileLength)
        {
          if (context->rangesLength == fileLength)
            context->ranges.clear();

          context->ranges.push_back(HttpRange(0, fileLength - 1));
          context->rangesLength = fileLength;
          firstPosition = 0;
          lastPosition = fileLength - 1;
        }
        else
          responseCode = MHD_HTTP_PARTIAL_CONTENT;

        // remember the total number of ranges
        context->rangeCount = context->ranges.size();
        // remember the total length
        totalLength = context->rangesLength;

        // we need to remember whether we are ranged because the range length
        // might change and won't be reliable anymore for length comparisons
        ranged = context->rangeCount > 1 || context->rangesLength < fileLength;

        // adjust the MIME type and range length in case of multiple ranges
        // which requires multipart boundaries
        if (context->rangeCount > 1)
        {
          context->boundary = GenerateMultipartBoundary();
          mimeType = "multipart/byteranges; boundary=" + context->boundary;

          // build part of the boundary with the optional Content-Type header
          // "--<boundary>\r\nContent-Type: <content-type>\r\n
          context->boundaryWithHeader = "\r\n--" + context->boundary + "\r\n";
          if (!context->contentType.empty())
            context->boundaryWithHeader += "Content-Type: " + context->contentType + "\r\n";

          // for every range, we need to add a boundary with header
          for (HttpRanges::const_iterator range = context->ranges.begin(); range != context->ranges.end(); range++)
          {
            // we need to temporarily add the Content-Range header to the
            // boundary to be able to determine the length
            string completeBoundaryWithHeader = context->boundaryWithHeader;
            completeBoundaryWithHeader += StringUtils::Format("Content-Range: " CONTENT_RANGE_FORMAT,
                                                              range->first, range->second, range->second - range->first + 1);
            completeBoundaryWithHeader += "\r\n\r\n";

            totalLength += completeBoundaryWithHeader.size();
          }
          // and at the very end a special end-boundary "\r\n--<boundary>--"
          totalLength += 4 + context->boundary.size() + 2;
        }

        // set the initial write position
        context->writePosition = context->ranges.begin()->first;

        // create the response object
        response = MHD_create_response_from_callback(totalLength,
                                                     2048,
                                                     &CWebServer::ContentReaderCallback, context,
                                                     &CWebServer::ContentReaderFreeCallback);
      }

      if (response == NULL)
      {
        file->Close();
        delete file;
        delete context;
        return MHD_NO;
      }

      // add Content-Range header
      if (ranged)
        AddHeader(response, "Content-Range", StringUtils::Format(CONTENT_RANGE_FORMAT, firstPosition, lastPosition, fileLength).c_str());
    }
    else
    {
      getData = false;

      CStdString contentLength;
      contentLength.Format("%" PRId64, fileLength);

      response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
      if (response == NULL)
      {
        file->Close();
        delete file;
        return MHD_NO;
      }
      AddHeader(response, "Content-Length", contentLength);
    }

    // add "Accept-Ranges: bytes" header
    AddHeader(response, "Accept-Ranges", "bytes");

    // set the Content-Type header
    if (!mimeType.empty())
      AddHeader(response, "Content-Type", mimeType.c_str());

    // set the Last-Modified header
    if (lastModified.IsValid())
      AddHeader(response, "Last-Modified", lastModified.GetAsRFC1123DateTime());

    // set the Expires header
    CDateTime expiryTime = CDateTime::GetCurrentDateTime();
    if (StringUtils::EqualsNoCase(mimeType, "text/html") ||
        StringUtils::EqualsNoCase(mimeType, "text/css") ||
        StringUtils::EqualsNoCase(mimeType, "application/javascript"))
      expiryTime += CDateTimeSpan(1, 0, 0, 0);
    else
      expiryTime += CDateTimeSpan(365, 0, 0, 0);
    AddHeader(response, "Expires", expiryTime.GetAsRFC1123DateTime());

    // only close the CFile instance if libmicrohttpd doesn't have to grab the data of the file
    if (!getData)
    {
      file->Close();
      delete file;
    }
  }
  else
  {
    delete file;
    CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str());
    return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
  }

  return MHD_YES;
}
Пример #11
0
int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode)
{
  CFile *file = new CFile();

  if (file->Open(strURL, READ_NO_CACHE))
  {
    bool getData = true;
    if (methodType != HEAD)
    {
      if (methodType == GET)
      {
        string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Modified-Since");
        if (!ifModifiedSince.empty())
        {
          CDateTime ifModifiedSinceDate;
          ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince);

          struct __stat64 statBuffer;
          if (file->Stat(&statBuffer) == 0)
          {
            struct tm *time = localtime((time_t *)&statBuffer.st_mtime);
            if (time != NULL)
            {
              CDateTime lastModified = *time;
              if (lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
              {
                getData = false;
                response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
                responseCode = MHD_HTTP_NOT_MODIFIED;
              }
            }
          }
        }
      }

      if (getData)
        response = MHD_create_response_from_callback(file->GetLength(),
                                                     2048,
                                                     &CWebServer::ContentReaderCallback, file,
                                                     &CWebServer::ContentReaderFreeCallback);
      if (response == NULL)
      {
        file->Close();
        delete file;
        return MHD_NO;
      }
    }
    else
    {
      getData = false;

      CStdString contentLength;
      contentLength.Format("%I64d", file->GetLength());

      response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
      if (response == NULL)
      {
        file->Close();
        delete file;
        return MHD_NO;
      }
      MHD_add_response_header(response, "Content-Length", contentLength);
    }

    // set the Content-Type header
    CStdString ext = URIUtils::GetExtension(strURL);
    ext = ext.ToLower();
    const char *mime = CreateMimeTypeFromExtension(ext.c_str());
    if (mime)
      MHD_add_response_header(response, "Content-Type", mime);

    // set the Last-Modified header
    struct __stat64 statBuffer;
    if (file->Stat(&statBuffer) == 0)
    {
      struct tm *time = localtime((time_t *)&statBuffer.st_mtime);
      if (time != NULL)
      {
        CDateTime lastModified = *time;
        MHD_add_response_header(response, "Last-Modified", lastModified.GetAsRFC1123DateTime());
      }
    }

    // set the Expires header
    CDateTime expiryTime = CDateTime::GetCurrentDateTime();
    if (mime && strncmp(mime, "text/html", 9) == 0)
      expiryTime += CDateTimeSpan(1, 0, 0, 0);
    else
      expiryTime += CDateTimeSpan(365, 0, 0, 0);
    MHD_add_response_header(response, "Expires", expiryTime.GetAsRFC1123DateTime());

    // only close the CFile instance if libmicrohttpd doesn't have to grab the data of the file
    if (!getData)
    {
      file->Close();
      delete file;
    }
  }
  else
  {
    delete file;
    CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str());
    return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, GET); /* GET Assumed Temporarily */
  }
  return MHD_YES;
}
Пример #12
0
int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode)
{
  boost::shared_ptr<CFile> file = boost::make_shared<CFile>();

#ifdef WEBSERVER_DEBUG
  CLog::Log(LOGDEBUG, "webserver  [IN] %s", strURL.c_str());
  multimap<string, string> headers;
  if (GetRequestHeaderValues(connection, MHD_HEADER_KIND, headers) > 0)
  {
    for (multimap<string, string>::const_iterator header = headers.begin(); header != headers.end(); header++)
      CLog::Log(LOGDEBUG, "webserver  [IN] %s: %s", header->first.c_str(), header->second.c_str());
  }
#endif

  if (!file->Open(strURL, READ_NO_CACHE))
  {
    CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str());
    return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType);
  }

  bool getData = true;
  bool ranged = false;
  int64_t fileLength = file->GetLength();

  // try to get the file's last modified date
  CDateTime lastModified;
  if (!GetLastModifiedDateTime(file.get(), lastModified))
    lastModified.Reset();

  // get the MIME type for the Content-Type header
  std::string ext = URIUtils::GetExtension(strURL);
  StringUtils::ToLower(ext);
  string mimeType = CreateMimeTypeFromExtension(ext.c_str());

  if (methodType != HEAD)
  {
    int64_t firstPosition = 0;
    int64_t lastPosition = fileLength - 1;
    uint64_t totalLength = 0;
    std::auto_ptr<HttpFileDownloadContext> context(new HttpFileDownloadContext());
    context->file = file;
    context->rangesLength = fileLength;
    context->contentType = mimeType;
    context->boundaryWritten = false;
    context->writePosition = 0;

    if (methodType == GET)
    {
      bool cacheable = true;

      // handle Cache-Control
      string cacheControl = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CACHE_CONTROL);
      if (!cacheControl.empty())
      {
        vector<string> cacheControls = StringUtils::Split(cacheControl, ",");
        for (vector<string>::const_iterator it = cacheControls.begin(); it != cacheControls.end(); ++it)
        {
          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)
        string pragma = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_PRAGMA);
        if (pragma.compare(HEADER_VALUE_NO_CACHE) == 0)
          cacheable = false;
      }

      if (lastModified.IsValid())
      {
        // handle If-Modified-Since or If-Unmodified-Since
        string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_MODIFIED_SINCE);
        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)
        {
          getData = false;
          response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
          if (response == NULL)
            return MHD_NO;

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

      if (getData)
      {
        // handle Range header
        context->rangesLength = ParseRangeHeader(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE), fileLength, context->ranges, firstPosition, lastPosition);

        // handle If-Range header but only if the Range header is present
        if (!context->ranges.empty())
        {
          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)
              context->ranges.clear();
          }
        }
      }
    }

    if (getData)
    {
      // if there are no ranges, add the whole range
      if (context->ranges.empty() || context->rangesLength == fileLength)
      {
        if (context->rangesLength == fileLength)
          context->ranges.clear();

        context->ranges.push_back(HttpRange(0, fileLength - 1));
        context->rangesLength = fileLength;
        firstPosition = 0;
        lastPosition = fileLength - 1;
      }
      else
        responseCode = MHD_HTTP_PARTIAL_CONTENT;

      // remember the total number of ranges
      context->rangeCount = context->ranges.size();
      // remember the total length
      totalLength = context->rangesLength;

      // we need to remember whether we are ranged because the range length
      // might change and won't be reliable anymore for length comparisons
      ranged = context->rangeCount > 1 || context->rangesLength < fileLength;

      // adjust the MIME type and range length in case of multiple ranges
      // which requires multipart boundaries
      if (context->rangeCount > 1)
      {
        context->boundary = GenerateMultipartBoundary();
        mimeType = "multipart/byteranges; boundary=" + context->boundary;

        // build part of the boundary with the optional Content-Type header
        // "--<boundary>\r\nContent-Type: <content-type>\r\n
        context->boundaryWithHeader = HEADER_NEWLINE HEADER_BOUNDARY + context->boundary + HEADER_NEWLINE;
        if (!context->contentType.empty())
          context->boundaryWithHeader += MHD_HTTP_HEADER_CONTENT_TYPE ": " + context->contentType + HEADER_NEWLINE;

        // for every range, we need to add a boundary with header
        for (HttpRanges::const_iterator range = context->ranges.begin(); range != context->ranges.end(); range++)
        {
          // we need to temporarily add the Content-Range header to the
          // boundary to be able to determine the length
          string completeBoundaryWithHeader = context->boundaryWithHeader;
          completeBoundaryWithHeader += StringUtils::Format(MHD_HTTP_HEADER_CONTENT_RANGE ": " CONTENT_RANGE_FORMAT,
                                                            range->first, range->second, range->second - range->first + 1);
          completeBoundaryWithHeader += HEADER_SEPARATOR;

          totalLength += completeBoundaryWithHeader.size();
        }
        // and at the very end a special end-boundary "\r\n--<boundary>--"
        totalLength += strlen(HEADER_SEPARATOR) + strlen(HEADER_BOUNDARY) + context->boundary.size() + strlen(HEADER_BOUNDARY);
      }

      // set the initial write position
      context->writePosition = context->ranges.begin()->first;

      // create the response object
      response = MHD_create_response_from_callback(totalLength, 2048,
                                                   &CWebServer::ContentReaderCallback,
                                                   context.get(),
                                                   &CWebServer::ContentReaderFreeCallback);
      if (response == NULL)
        return MHD_NO;
        
      context.release(); // ownership was passed to mhd
    }

    // add Content-Range header
    if (ranged)
      AddHeader(response, MHD_HTTP_HEADER_CONTENT_RANGE, StringUtils::Format(CONTENT_RANGE_FORMAT, firstPosition, lastPosition, fileLength));
  }
  else
  {
    getData = false;

    std::string contentLength = StringUtils::Format("%" PRId64, fileLength);

    response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
    if (response == NULL)
      return MHD_NO;

    AddHeader(response, MHD_HTTP_HEADER_CONTENT_LENGTH, contentLength);
  }

  // add "Accept-Ranges: bytes" header
  AddHeader(response, MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes");

  // set the Content-Type header
  if (!mimeType.empty())
    AddHeader(response, MHD_HTTP_HEADER_CONTENT_TYPE, mimeType);

  // set the Last-Modified header
  if (lastModified.IsValid())
    AddHeader(response, MHD_HTTP_HEADER_LAST_MODIFIED, lastModified.GetAsRFC1123DateTime());

  // set the Expires header
  CDateTime now = CDateTime::GetCurrentDateTime();
  CDateTime expiryTime = now;
  if (StringUtils::EqualsNoCase(mimeType, "text/html") ||
      StringUtils::EqualsNoCase(mimeType, "text/css") ||
      StringUtils::EqualsNoCase(mimeType, "application/javascript"))
    expiryTime += CDateTimeSpan(1, 0, 0, 0);
  else
    expiryTime += CDateTimeSpan(365, 0, 0, 0);
  AddHeader(response, MHD_HTTP_HEADER_EXPIRES, expiryTime.GetAsRFC1123DateTime());

  // set the Cache-Control header
  int maxAge = (expiryTime - now).GetSecondsTotal();
  AddHeader(response, MHD_HTTP_HEADER_CACHE_CONTROL, StringUtils::Format("max-age=%d, public", maxAge));

  return MHD_YES;
}