Ejemplo n.º 1
0
bool CHttpRanges::Parse(const std::string& header, uint64_t totalLength)
{
  m_ranges.clear();

  if (header.empty() || totalLength == 0 || !StringUtils::StartsWithNoCase(header, "bytes="))
    return false;

  uint64_t lastPossiblePosition = totalLength - 1;

  // remove "bytes=" from the beginning
  std::string rangesValue = header.substr(6);

  // split the value of the "Range" header by ","
  std::vector<std::string> rangeValues = StringUtils::Split(rangesValue, ",");

  for (std::vector<std::string>::const_iterator range = rangeValues.begin(); range != rangeValues.end(); range++)
  {
    // there must be a "-" in the range definition
    if (range->find("-") == std::string::npos)
      return false;

    std::vector<std::string> positions = StringUtils::Split(*range, "-");
    if (positions.size() != 2)
      return false;

    bool hasStart = false;
    uint64_t start = 0;
    bool hasEnd = false;
    uint64_t end = 0;

    // parse the start and end positions
    if (!positions.front().empty())
    {
      if (!StringUtils::IsNaturalNumber(positions.front()))
        return false;

      start = str2uint64(positions.front(), 0);
      hasStart = true;
    }
    if (!positions.back().empty())
    {
      if (!StringUtils::IsNaturalNumber(positions.back()))
        return false;

      end = str2uint64(positions.back(), 0);
      hasEnd = true;
    }

    // nothing defined at all
    if (!hasStart && !hasEnd)
      return false;

    // make sure that the end position makes sense
    if (hasEnd)
      end = std::min(end, lastPossiblePosition);

    if (!hasStart && hasEnd)
    {
      // the range is defined as the number of bytes from the end
      start = totalLength - end;
      end = lastPossiblePosition;
    }
    else if (hasStart && !hasEnd)
      end = lastPossiblePosition;

    // make sure the start position makes sense
    if (start > lastPossiblePosition)
      return false;

    // make sure that the start position is smaller or equal to the end position
    if (end < start)
      return false;

    m_ranges.push_back(CHttpRange(start, end));
  }

  if (m_ranges.empty())
    return false;

  SortAndCleanup();
  return !m_ranges.empty();
}
Ejemplo n.º 2
0
int CWebServer::CreateFileDownloadResponse(IHTTPRequestHandler *handler, struct MHD_Response *&response)
{
  if (handler == NULL)
    return MHD_NO;

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

  std::shared_ptr<XFILE::CFile> file = std::make_shared<XFILE::CFile>();
  std::string filePath = handler->GetResponseFile();

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

  bool ranged = false;
  uint64_t fileLength = static_cast<uint64_t>(file->GetLength());

  // get the MIME type for the Content-Type header
  std::string mimeType = responseDetails.contentType;
  if (mimeType.empty())
  {
    std::string ext = URIUtils::GetExtension(filePath);
    StringUtils::ToLower(ext);
    mimeType = CreateMimeTypeFromExtension(ext.c_str());
  }

  if (request.method != HEAD)
  {
    uint64_t totalLength = 0;
    std::unique_ptr<HttpFileDownloadContext> context(new HttpFileDownloadContext());
    context->file = file;
    context->contentType = mimeType;
    context->boundaryWritten = false;
    context->writePosition = 0;

    if (handler->IsRequestRanged())
    {
      if (!request.ranges.IsEmpty())
        context->ranges = request.ranges;
      else
        GetRequestedRanges(request.connection, fileLength, context->ranges);
    }

    uint64_t firstPosition = 0;
    uint64_t lastPosition = 0;
    // if there are no ranges, add the whole range
    if (context->ranges.IsEmpty())
      context->ranges.Add(CHttpRange(0, fileLength - 1));
    else
    {
      handler->SetResponseStatus(MHD_HTTP_PARTIAL_CONTENT);

      // we need to remember that we are ranged because the range length might change and won't be reliable anymore for length comparisons
      ranged = true;

      context->ranges.GetFirstPosition(firstPosition);
      context->ranges.GetLastPosition(lastPosition);
    }

    // remember the total number of ranges
    context->rangeCountTotal = context->ranges.Size();
    // remember the total length
    totalLength = context->ranges.GetLength();

    // adjust the MIME type and range length in case of multiple ranges which requires multipart boundaries
    if (context->rangeCountTotal > 1)
    {
      context->boundary = HttpRangeUtils::GenerateMultipartBoundary();
      mimeType = HttpRangeUtils::GenerateMultipartBoundaryContentType(context->boundary);

      // build part of the boundary with the optional Content-Type header
      // "--<boundary>\r\nContent-Type: <content-type>\r\n
      context->boundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundary, context->contentType);
      context->boundaryEnd = HttpRangeUtils::GenerateMultipartBoundaryEnd(context->boundary);

      // 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
        std::string completeBoundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundaryWithHeader, &*range);
        totalLength += completeBoundaryWithHeader.size();

        // add a newline before any new multipart boundary
        if (range != context->ranges.Begin())
          totalLength += strlen(HEADER_NEWLINE);
      }
      // and at the very end a special end-boundary "\r\n--<boundary>--"
      totalLength += context->boundaryEnd.size();
    }

    // set the initial write position
    context->ranges.GetFirstPosition(context->writePosition);

    // create the response object
    response = MHD_create_response_from_callback(totalLength, 2048,
                                                  &CWebServer::ContentReaderCallback,
                                                  context.get(),
                                                  &CWebServer::ContentReaderFreeCallback);
    if (response == NULL)
    {
      CLog::Log(LOGERROR, "CWebServer: failed to create a HTTP response for %s to be filled from %s", request.pathUrl.c_str(), filePath.c_str());
      return MHD_NO;
    }

    context.release(); // ownership was passed to mhd

    // add Content-Range header
    if (ranged)
      handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_RANGE, HttpRangeUtils::GenerateContentRangeHeaderValue(firstPosition, lastPosition, fileLength));
  }
  else
  {
    response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO);
    if (response == NULL)
    {
      CLog::Log(LOGERROR, "CWebServer: failed to create a HTTP HEAD response for %s", request.pathUrl.c_str());
      return MHD_NO;
    }

    handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_LENGTH, StringUtils::Format("%" PRId64, fileLength));
  }

  // set the Content-Type header
  if (!mimeType.empty())
    handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_TYPE, mimeType);

  return MHD_YES;
}