int CWebServer::CreateRangedMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const { if (handler == nullptr) return MHD_NO; const HTTPRequest &request = handler->GetRequest(); const HTTPResponseDetails &responseDetails = handler->GetResponseDetails(); HttpResponseRanges responseRanges = handler->GetResponseData(); // if there's no or only one range this is not the right place if (responseRanges.size() <= 1) return CreateMemoryDownloadResponse(handler, response); // extract all the valid ranges and calculate their total length uint64_t firstRangePosition = 0; HttpResponseRanges ranges; for (HttpResponseRanges::const_iterator range = responseRanges.begin(); range != responseRanges.end(); ++range) { // ignore invalid ranges if (!range->IsValid()) continue; // determine the first range position if (ranges.empty()) firstRangePosition = range->GetFirstPosition(); ranges.push_back(*range); } if (ranges.empty()) return CreateMemoryDownloadResponse(request.connection, nullptr, 0, false, false, response); // determine the last range position uint64_t lastRangePosition = ranges.back().GetLastPosition(); // adjust the HTTP status of the response handler->SetResponseStatus(MHD_HTTP_PARTIAL_CONTENT); // add Content-Range header handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_RANGE, HttpRangeUtils::GenerateContentRangeHeaderValue(firstRangePosition, lastRangePosition, responseDetails.totalLength)); // generate a multipart boundary std::string multipartBoundary = HttpRangeUtils::GenerateMultipartBoundary(); // and the content-type std::string contentType = HttpRangeUtils::GenerateMultipartBoundaryContentType(multipartBoundary); // add Content-Type header handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_TYPE, contentType); // generate the multipart boundary with the Content-Type header field std::string multipartBoundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(multipartBoundary, contentType); std::string result; // add all the ranges to the result for (HttpResponseRanges::const_iterator range = ranges.begin(); range != ranges.end(); ++range) { // add a newline before any new multipart boundary if (range != ranges.begin()) result += HEADER_NEWLINE; // generate and append the multipart boundary with the full header (Content-Type and Content-Length) result += HttpRangeUtils::GenerateMultipartBoundaryWithHeader(multipartBoundaryWithHeader, &*range); // and append the data of the range result.append(static_cast<const char*>(range->GetData()), static_cast<size_t>(range->GetLength())); // check if we need to free the range data if (responseDetails.type == HTTPMemoryDownloadFreeNoCopy || responseDetails.type == HTTPMemoryDownloadFreeCopy) free(const_cast<void*>(range->GetData())); } result += HttpRangeUtils::GenerateMultipartBoundaryEnd(multipartBoundary); // add Content-Length header handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_LENGTH, StringUtils::Format("%" PRIu64, static_cast<uint64_t>(result.size()))); // finally create the response return CreateMemoryDownloadResponse(request.connection, result.c_str(), result.size(), false, true, response); }
int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const { if (handler == nullptr) 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, XFILE::READ_NO_CACHE)) { CLog::Log(LOGERROR, "CWebServer[%hu]: Failed to open %s", m_port, filePath.c_str()); return SendErrorResponse(request, 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 HTTPRequestHandlerUtils::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 == nullptr) { CLog::Log(LOGERROR, "CWebServer[%hu]: failed to create a HTTP response for %s to be filled from %s", m_port, 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 = create_response(0, nullptr, MHD_NO, MHD_NO); if (response == nullptr) { CLog::Log(LOGERROR, "CWebServer[%hu]: failed to create a HTTP HEAD response for %s", m_port, 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; }
int CWebServer::FinalizeRequest(const std::shared_ptr<IHTTPRequestHandler>& handler, int responseStatus, struct MHD_Response *response) { if (handler == nullptr || response == nullptr) 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); return SendResponse(request, responseStatus, response); }