/*---------------------------------------------------------------------- | CMediaCrawler::ProcessFileRequest +---------------------------------------------------------------------*/ NPT_Result CMediaCrawler::ProcessFileRequest(NPT_HttpRequest& request, NPT_HttpResponse& response, NPT_SocketInfo& info) { NPT_COMPILER_UNUSED(info); NPT_LOG_FINE("CMediaCrawler::ProcessFileRequest Received Request:"); PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request); if (request.GetMethod().Compare("GET") && request.GetMethod().Compare("HEAD")) { response.SetStatus(500, "Internal Server Error"); return NPT_SUCCESS; } // add the user agent header, some stupid media servers like YME needs it if (!request.GetHeaders().GetHeader(NPT_HTTP_HEADER_USER_AGENT)) { request.GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, "Platinum/" PLT_PLATINUM_VERSION_STRING); } // File requested NPT_HttpResponse* out_response = NULL; NPT_HttpUrlQuery query(request.GetUrl().GetQuery()); const char* url = query.GetField("url"); if (url) { // look for handler CStreamHandler* handler = NULL; NPT_ContainerFind(m_StreamHandlers, CStreamHandlerFinder(NULL, url), handler); if (handler && NPT_SUCCEEDED(handler->ProcessFileRequest(request, out_response)) && out_response) { // copy response code and reason response.SetStatus(out_response->GetStatusCode(), out_response->GetReasonPhrase()); // copy headers NPT_List<NPT_HttpHeader*>::Iterator headers = out_response->GetHeaders().GetHeaders().GetFirstItem(); while (headers) { response.GetHeaders().SetHeader((*headers)->GetName(), (*headers)->GetValue()); ++headers; } response.SetEntity(new NPT_HttpEntity(response.GetHeaders())); // update inputstream NPT_HttpEntity* out_entity; if ((out_entity = out_response->GetEntity()) != NULL) { NPT_InputStreamReference inputstream; out_entity->GetInputStream(inputstream); if (!inputstream.IsNull()) { // set the stream but do not update the content length response.GetEntity()->SetInputStream(inputstream, false); } } delete out_response; return NPT_SUCCESS; } } response.SetStatus(404, "File Not Found"); return NPT_SUCCESS; }
/*---------------------------------------------------------------------- | PLT_HttpServer::ServeFile +---------------------------------------------------------------------*/ NPT_Result PLT_HttpServer::ServeFile(const NPT_HttpRequest& request, const NPT_HttpRequestContext& context, NPT_HttpResponse& response, NPT_String file_path) { NPT_InputStreamReference stream; NPT_File file(file_path); NPT_FileInfo file_info; // prevent hackers from accessing files outside of our root if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0) || NPT_FAILED(NPT_File::GetInfo(file_path, &file_info))) { return NPT_ERROR_NO_SUCH_ITEM; } // check for range requests const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE); // handle potential 304 only if range header not set NPT_DateTime date; NPT_TimeStamp timestamp; if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIfModifiedSince((NPT_HttpMessage&)request, date)) && !range_spec) { date.ToTimeStamp(timestamp); NPT_LOG_INFO_5("File %s timestamps: request=%d (%s) vs file=%d (%s)", (const char*)request.GetUrl().GetPath(), (NPT_UInt32)timestamp.ToSeconds(), (const char*)date.ToString(), (NPT_UInt32)file_info.m_ModificationTime, (const char*)NPT_DateTime(file_info.m_ModificationTime).ToString()); if (timestamp >= file_info.m_ModificationTime) { // it's a match NPT_LOG_FINE_1("Returning 304 for %s", request.GetUrl().GetPath().GetChars()); response.SetStatus(304, "Not Modified", NPT_HTTP_PROTOCOL_1_1); return NPT_SUCCESS; } } // open file if (NPT_FAILED(file.Open(NPT_FILE_OPEN_MODE_READ)) || NPT_FAILED(file.GetInputStream(stream)) || stream.IsNull()) { return NPT_ERROR_NO_SUCH_ITEM; } // set Last-Modified and Cache-Control headers if (file_info.m_ModificationTime) { NPT_DateTime last_modified = NPT_DateTime(file_info.m_ModificationTime); response.GetHeaders().SetHeader("Last-Modified", last_modified.ToString(NPT_DateTime::FORMAT_RFC_1123), true); response.GetHeaders().SetHeader("Cache-Control", "max-age=0,must-revalidate", true); //response.GetHeaders().SetHeader("Cache-Control", "max-age=1800", true); } PLT_HttpRequestContext tmp_context(request, context); return ServeStream(request, context, response, stream, PLT_MimeType::GetMimeType(file_path, &tmp_context)); }
/*---------------------------------------------------------------------- | PLT_Downloader::ProcessResponse +---------------------------------------------------------------------*/ NPT_Result PLT_Downloader::ProcessResponse(NPT_Result res, const NPT_HttpRequest& request, const NPT_HttpRequestContext& context, NPT_HttpResponse* response) { NPT_COMPILER_UNUSED(request); NPT_COMPILER_UNUSED(context); if (NPT_FAILED(res)) { NPT_LOG_WARNING_2("Downloader error %d for %s", res, m_URL.ToString().GetChars()); m_State = PLT_DOWNLOADER_ERROR; return res; } m_State = PLT_DOWNLOADER_DOWNLOADING; NPT_HttpEntity* entity; NPT_InputStreamReference body; if (!response || !(entity = response->GetEntity()) || NPT_FAILED(entity->GetInputStream(body)) || body.IsNull()) { m_State = PLT_DOWNLOADER_ERROR; NPT_LOG_WARNING_2("No body %d for %s", res, m_URL.ToString().GetChars()); return NPT_FAILURE; } // Read body (no content length means until socket is closed) res = NPT_StreamToStreamCopy(*body.AsPointer(), *m_Output.AsPointer(), 0, entity->GetContentLength()); if (NPT_FAILED(res)) { NPT_LOG_WARNING_2("Downloader error %d for %s", res, m_URL.ToString().GetChars()); m_State = PLT_DOWNLOADER_ERROR; return res; } NPT_LOG_INFO_1("Finished downloading %s", m_URL.ToString().GetChars()); m_State = PLT_DOWNLOADER_SUCCESS; return NPT_SUCCESS; }
/*---------------------------------------------------------------------- | PLT_HttpHelper::IsBodyStreamSeekable +---------------------------------------------------------------------*/ bool PLT_HttpHelper::IsBodyStreamSeekable(NPT_HttpMessage& message) { NPT_HttpEntity* entity = message.GetEntity(); NPT_InputStreamReference stream; if (!entity || NPT_FAILED(entity->GetInputStream(stream)) || stream.IsNull()) { return true; } // try to get current position and seek there NPT_Position position; if (NPT_FAILED(stream->Tell(position)) || NPT_FAILED(stream->Seek(position))) { return false; } return true; }
/*---------------------------------------------------------------------- | PLT_Downloader::ProcessResponse +---------------------------------------------------------------------*/ NPT_Result PLT_Downloader::ProcessResponse(NPT_Result res, NPT_HttpRequest* request, const NPT_HttpRequestContext& context, NPT_HttpResponse* response) { NPT_COMPILER_UNUSED(request); NPT_COMPILER_UNUSED(context); if (NPT_FAILED(res)) { m_State = PLT_DOWNLOADER_ERROR; return res; } m_State = PLT_DOWNLOADER_DOWNLOADING; NPT_HttpEntity* entity; NPT_InputStreamReference body; if (!response || !(entity = response->GetEntity()) || NPT_FAILED(entity->GetInputStream(body)) || body.IsNull()) { m_State = PLT_DOWNLOADER_ERROR; return NPT_FAILURE; } // Read body (no content length means until socket is closed) res = NPT_StreamToStreamCopy(*body.AsPointer(), *m_Output.AsPointer(), 0, entity->GetContentLength()); if (NPT_FAILED(res)) { m_State = PLT_DOWNLOADER_ERROR; return res; } m_State = PLT_DOWNLOADER_SUCCESS; return NPT_SUCCESS; }
/*---------------------------------------------------------------------- | PLT_HttpHelper::GetBody +---------------------------------------------------------------------*/ NPT_Result PLT_HttpHelper::GetBody(const NPT_HttpMessage& message, NPT_String& body) { NPT_Result res; NPT_InputStreamReference stream; // get stream NPT_HttpEntity* entity = message.GetEntity(); if (!entity || NPT_FAILED(entity->GetInputStream(stream)) || stream.IsNull()) { return NPT_FAILURE; } // extract body NPT_StringOutputStream* output_stream = new NPT_StringOutputStream(&body); res = NPT_StreamToStreamCopy(*stream, *output_stream, 0, entity->GetContentLength()); delete output_stream; return res; }
/*---------------------------------------------------------------------- | PLT_HttpServer::ServeStream +---------------------------------------------------------------------*/ NPT_Result PLT_HttpServer::ServeStream(const NPT_HttpRequest& request, const NPT_HttpRequestContext& context, NPT_HttpResponse& response, NPT_InputStreamReference& body, const char* content_type) { if (body.IsNull()) return NPT_FAILURE; // set date NPT_TimeStamp now; NPT_System::GetCurrentTimeStamp(now); response.GetHeaders().SetHeader("Date", NPT_DateTime(now).ToString(NPT_DateTime::FORMAT_RFC_1123), true); // get entity NPT_HttpEntity* entity = response.GetEntity(); NPT_CHECK_POINTER_FATAL(entity); // set the content type entity->SetContentType(content_type); // check for range requests const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE); // setup entity body NPT_CHECK(NPT_HttpFileRequestHandler::SetupResponseBody(response, body, range_spec)); // set some default headers if (response.GetEntity()->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED) { // set but don't replace Accept-Range header only if body is seekable NPT_Position offset; if (NPT_SUCCEEDED(body->Tell(offset)) && NPT_SUCCEEDED(body->Seek(offset))) { response.GetHeaders().SetHeader(NPT_HTTP_HEADER_ACCEPT_RANGES, "bytes", false); } } // set getcontentFeatures.dlna.org const NPT_String* value = request.GetHeaders().GetHeaderValue("getcontentFeatures.dlna.org"); if (value) { PLT_HttpRequestContext tmp_context(request, context); const char* dlna = PLT_ProtocolInfo::GetDlnaExtension(entity->GetContentType(), &tmp_context); if (dlna) response.GetHeaders().SetHeader("ContentFeatures.DLNA.ORG", dlna, false); } // transferMode.dlna.org value = request.GetHeaders().GetHeaderValue("transferMode.dlna.org"); if (value) { // Interactive mode not supported? /*if (value->Compare("Interactive", true) == 0) { response.SetStatus(406, "Not Acceptable"); return NPT_SUCCESS; }*/ response.GetHeaders().SetHeader("TransferMode.DLNA.ORG", value->GetChars(), false); } else { response.GetHeaders().SetHeader("TransferMode.DLNA.ORG", "Streaming", false); } if (request.GetHeaders().GetHeaderValue("TimeSeekRange.dlna.org")) { response.SetStatus(406, "Not Acceptable"); return NPT_SUCCESS; } return NPT_SUCCESS; }