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; }
int CHTTPImageTransformationHandler::HandleRequest() { if (m_response.type == HTTPError) return MHD_YES; // nothing else to do if this is a HEAD request if (m_request.method == HEAD) { m_response.status = MHD_HTTP_OK; m_response.type = HTTPMemoryDownloadNoFreeNoCopy; return MHD_YES; } // get the transformation options std::map<std::string, std::string> options; HTTPRequestHandlerUtils::GetRequestHeaderValues(m_request.connection, MHD_GET_ARGUMENT_KIND, options); std::vector<std::string> urlOptions; std::map<std::string, std::string>::const_iterator option = options.find(TRANSFORMATION_OPTION_WIDTH); if (option != options.end()) urlOptions.push_back(TRANSFORMATION_OPTION_WIDTH "=" + option->second); option = options.find(TRANSFORMATION_OPTION_HEIGHT); if (option != options.end()) urlOptions.push_back(TRANSFORMATION_OPTION_HEIGHT "=" + option->second); option = options.find(TRANSFORMATION_OPTION_SCALING_ALGORITHM); if (option != options.end()) urlOptions.push_back(TRANSFORMATION_OPTION_SCALING_ALGORITHM "=" + option->second); std::string imagePath = m_url; if (!urlOptions.empty()) { imagePath += "?"; imagePath += StringUtils::Join(urlOptions, "&"); } // resize the image into the local buffer size_t bufferSize; if (!CTextureCacheJob::ResizeTexture(imagePath, m_buffer, bufferSize)) { m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR; m_response.type = HTTPError; return MHD_YES; } // store the size of the image m_response.totalLength = bufferSize; // nothing else to do if the request is not ranged if (!GetRequestedRanges(m_response.totalLength)) { m_responseData.push_back(CHttpResponseRange(m_buffer, 0, m_response.totalLength - 1)); return MHD_YES; } for (HttpRanges::const_iterator range = m_request.ranges.Begin(); range != m_request.ranges.End(); ++range) m_responseData.push_back(CHttpResponseRange(m_buffer + range->GetFirstPosition(), range->GetFirstPosition(), range->GetLastPosition())); return MHD_YES; }