diff --git a/xbmc/network/WebServer.cpp b/xbmc/network/WebServer.cpp index 66917dba705a7..28b5af1181d1d 100644 --- a/xbmc/network/WebServer.cpp +++ b/xbmc/network/WebServer.cpp @@ -52,12 +52,9 @@ #define PAGE_FILE_NOT_FOUND "File not foundFile not found" #define NOT_SUPPORTED "Not SupportedThe method you are trying to use is not supported by this server" -#define CONTENT_RANGE_FORMAT "bytes %" PRId64 "-%" PRId64 "/%" PRId64 +#define HEADER_VALUE_NO_CACHE "no-cache" #define HEADER_NEWLINE "\r\n" -#define HEADER_SEPARATOR HEADER_NEWLINE HEADER_NEWLINE -#define HEADER_BOUNDARY "--" -#define HEADER_VALUE_NO_CACHE "no-cache" using namespace XFILE; using namespace std; @@ -71,14 +68,14 @@ typedef struct ConnectionHandler typedef struct { boost::shared_ptr file; - HttpRanges ranges; - size_t rangeCount; - int64_t rangesLength; + CHttpRanges ranges; + size_t rangeCountTotal; string boundary; string boundaryWithHeader; + string boundaryEnd; bool boundaryWritten; string contentType; - int64_t writePosition; + uint64_t writePosition; } HttpFileDownloadContext; vector CWebServer::m_requestHandlers; @@ -92,6 +89,18 @@ CWebServer::CWebServer() { } +HTTPMethod CWebServer::GetMethod(const char *method) +{ + if (strcmp(method, "GET") == 0) + return GET; + if (strcmp(method, "POST") == 0) + return POST; + if (strcmp(method, "HEAD") == 0) + return HEAD; + + return UNKNOWN; +} + int CWebServer::FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { if (cls == NULL || key == NULL) @@ -116,16 +125,30 @@ int CWebServer::AskForAuthentication(struct MHD_Connection *connection) { struct MHD_Response *response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO); if (!response) + { + CLog::Log(LOGERROR, "CWebServer: unable to create HTTP Unauthorized response"); return MHD_NO; + } int ret = AddHeader(response, MHD_HTTP_HEADER_WWW_AUTHENTICATE, "Basic realm=XBMC"); ret |= AddHeader(response, MHD_HTTP_HEADER_CONNECTION, "close"); if (!ret) { + CLog::Log(LOGERROR, "CWebServer: unable to prepare HTTP Unauthorized response"); MHD_destroy_response(response); return MHD_NO; } +#ifdef WEBSERVER_DEBUG + std::multimap headerValues; + GetRequestHeaderValues(connection, MHD_RESPONSE_HEADER_KIND, headerValues); + + CLog::Log(LOGDEBUG, "webserver [OUT] HTTP %d", MHD_HTTP_UNAUTHORIZED); + + for (std::multimap::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 + ret = MHD_queue_response(connection, MHD_HTTP_UNAUTHORIZED, response); MHD_destroy_response(response); @@ -161,49 +184,155 @@ int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection, { CWebServer *server = (CWebServer *)cls; HTTPMethod methodType = GetMethod(method); - HTTPRequest request = { connection, url, methodType, version, server }; + HTTPRequest request = { server, connection, url, methodType, version }; + +#ifdef WEBSERVER_DEBUG + if (*con_cls == NULL) + { + std::multimap headerValues; + GetRequestHeaderValues(connection, MHD_HEADER_KIND, headerValues); + std::multimap getValues; + GetRequestHeaderValues(connection, MHD_GET_ARGUMENT_KIND, getValues); + + CLog::Log(LOGDEBUG, "webserver [IN] %s %s %s", version, method, url); + if (!getValues.empty()) + { + std::string tmp; + for (std::multimap::const_iterator get = getValues.begin(); get != getValues.end(); ++get) + { + if (get != getValues.begin()) + tmp += "; "; + tmp += get->first + " = " + get->second; + } + CLog::Log(LOGDEBUG, "webserver [IN] Query arguments: %s", tmp.c_str()); + } + + for (std::multimap::const_iterator header = headerValues.begin(); header != headerValues.end(); ++header) + CLog::Log(LOGDEBUG, "webserver [IN] %s: %s", header->first.c_str(), header->second.c_str()); + } +#endif if (!IsAuthenticated(server, connection)) return AskForAuthentication(connection); - // Check if this is the first call to - // AnswerToConnection for this request + // check if this is the first call to AnswerToConnection for this request if (*con_cls == NULL) { - // Look for a IHTTPRequestHandler which can - // take care of the current request + // parse the Range header and store it in the request object + CHttpRanges ranges; + bool ranged = ranges.Parse(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE)); + + // look for a IHTTPRequestHandler which can take care of the current request for (vector::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); ++it) { IHTTPRequestHandler *requestHandler = *it; if (requestHandler->CanHandleRequest(request)) { - // We found a matching IHTTPRequestHandler - // so let's get a new instance for this request + // we found a matching IHTTPRequestHandler so let's get a new instance for this request IHTTPRequestHandler *handler = requestHandler->Create(request); - // If we got a POST request we need to take - // care of the POST data - if (methodType == POST) + // if we got a GET request we need to check if it should be cached + if (methodType == GET) + { + if (handler->CanBeCached()) + { + bool cacheable = true; + + // handle Cache-Control + string cacheControl = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CACHE_CONTROL); + if (!cacheControl.empty()) + { + vector cacheControls = StringUtils::Split(cacheControl, ","); + for (vector::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; + } + + CDateTime lastModified; + if (handler->GetLastModifiedDate(lastModified) && 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) + { + struct MHD_Response *response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO); + if (response == NULL) + { + CLog::Log(LOGERROR, "CWebServer: failed to create a HTTP 304 response"); + return MHD_NO; + } + + return FinalizeRequest(handler, MHD_HTTP_NOT_MODIFIED, response); + } + // handle If-Unmodified-Since + else if (ifUnmodifiedSinceDate.SetFromRFC1123DateTime(ifUnmodifiedSince) && + lastModified.GetAsUTCDateTime() > ifUnmodifiedSinceDate) + return SendErrorResponse(connection, MHD_HTTP_PRECONDITION_FAILED, methodType); + } + + // handle If-Range header but only if the Range header is present + if (ranged && lastModified.IsValid()) + { + 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) + ranges.Clear(); + } + } + + // pass the requested ranges on to the request handler + handler->SetRequestRanged(!ranges.IsEmpty()); + } + } + // if we got a POST request we need to take care of the POST data + else if (methodType == POST) { ConnectionHandler *conHandler = new ConnectionHandler(); conHandler->requestHandler = handler; - // Get the content-type of the POST data + // get the content-type of the POST data string contentType = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE); if (!contentType.empty()) { - // If the content-type is application/x-ww-form-urlencoded or multipart/form-data - // we can use MHD's POST processor + // if the content-type is application/x-ww-form-urlencoded or multipart/form-data we can use MHD's POST processor if (StringUtils::EqualsNoCase(contentType, MHD_HTTP_POST_ENCODING_FORM_URLENCODED) || StringUtils::EqualsNoCase(contentType, MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)) { // Get a new MHD_PostProcessor conHandler->postprocessor = MHD_create_post_processor(connection, MAX_POST_BUFFER_SIZE, &CWebServer::HandlePostField, (void*)conHandler); - // MHD doesn't seem to be able to handle - // this post request + // MHD doesn't seem to be able to handle this post request if (conHandler->postprocessor == NULL) { + CLog::Log(LOGERROR, "CWebServer: unable to create HTTP POST processor for %s", url); + delete conHandler->requestHandler; delete conHandler; @@ -217,42 +346,40 @@ int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection, *con_cls = (void*)conHandler; return MHD_YES; } - // No POST request so nothing special to handle - else - return HandleRequest(handler); + + return HandleRequest(handler); } } } - // This is a subsequent call to - // AnswerToConnection for this request + // this is a subsequent call to AnswerToConnection for this request else { - // Again we need to take special care - // of the POST data + // again we need to take special care of the POST data if (methodType == POST) { ConnectionHandler *conHandler = (ConnectionHandler *)*con_cls; if (conHandler->requestHandler == NULL) + { + CLog::Log(LOGERROR, "CWebServer: cannot handle partial HTTP POST for %s request because there is no valid request handler available", url); return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType); + } - // We only need to handle POST data - // if there actually is data left to handle + // we only need to handle POST data if there actually is data left to handle if (*upload_data_size > 0) { - // Either use MHD's POST processor + // either use MHD's POST processor if (conHandler->postprocessor != NULL) MHD_post_process(conHandler->postprocessor, upload_data, *upload_data_size); // or simply copy the data to the handler else conHandler->requestHandler->AddPostData(upload_data, *upload_data_size); - // Signal that we have handled the data + // signal that we have handled the data *upload_data_size = 0; return MHD_YES; } - // We have handled all POST data - // so it's time to invoke the IHTTPRequestHandler + // we have handled all POST data so it's time to invoke the IHTTPRequestHandler else { if (conHandler->postprocessor != NULL) @@ -260,13 +387,10 @@ int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection, *con_cls = NULL; int ret = HandleRequest(conHandler->requestHandler); - delete conHandler; return ret; } } - // It's unusual to get more than one call - // to AnswerToConnection for none-POST - // requests, but let's handle it anyway + // it's unusual to get more than one call to AnswerToConnection for none-POST requests, but let's handle it anyway else { for (vector::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); ++it) @@ -278,6 +402,7 @@ int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection, } } + CLog::Log(LOGERROR, "CWebServer: couldn't find any request handler for %s", url); return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType); } @@ -295,8 +420,12 @@ int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char * { ConnectionHandler *conHandler = (ConnectionHandler *)cls; - if (conHandler == NULL || conHandler->requestHandler == NULL || size == 0) + if (conHandler == NULL || conHandler->requestHandler == NULL || + key == NULL || data == NULL || size == 0) + { + CLog::Log(LOGERROR, "CWebServer: unable to handle HTTP POST field"); return MHD_NO; + } conHandler->requestHandler->AddPostField(key, string(data, size)); return MHD_YES; @@ -307,19 +436,21 @@ int CWebServer::HandleRequest(IHTTPRequestHandler *handler) if (handler == NULL) return MHD_NO; - const HTTPRequest &request = handler->GetRequest(); + HTTPRequest request = handler->GetRequest(); int ret = handler->HandleRequest(); if (ret == MHD_NO) { + CLog::Log(LOGERROR, "CWebServer: failed to handle HTTP request for %s", request.url.c_str()); delete handler; return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); } + const HTTPResponseDetails &responseDetails = handler->GetResponseDetails(); struct MHD_Response *response = NULL; - int responseCode = handler->GetResponseCode(); - switch (handler->GetResponseType()) + switch (responseDetails.type) { case HTTPNone: + CLog::Log(LOGERROR, "CWebServer: HTTP request handler didn't process %s", request.url.c_str()); delete handler; return MHD_NO; @@ -328,306 +459,398 @@ int CWebServer::HandleRequest(IHTTPRequestHandler *handler) break; case HTTPFileDownload: - ret = CreateFileDownloadResponse(request.connection, handler->GetResponseFile(), request.method, response, responseCode); + ret = CreateFileDownloadResponse(handler, response); break; case HTTPMemoryDownloadNoFreeNoCopy: - ret = CreateMemoryDownloadResponse(request.connection, handler->GetResponseData(), handler->GetResponseDataLength(), false, false, response); - break; - case HTTPMemoryDownloadNoFreeCopy: - ret = CreateMemoryDownloadResponse(request.connection, handler->GetResponseData(), handler->GetResponseDataLength(), false, true, response); - break; - case HTTPMemoryDownloadFreeNoCopy: - ret = CreateMemoryDownloadResponse(request.connection, handler->GetResponseData(), handler->GetResponseDataLength(), true, false, response); - break; - case HTTPMemoryDownloadFreeCopy: - ret = CreateMemoryDownloadResponse(request.connection, handler->GetResponseData(), handler->GetResponseDataLength(), true, true, response); + ret = CreateMemoryDownloadResponse(handler, response); break; case HTTPError: - ret = CreateErrorResponse(request.connection, handler->GetResponseCode(), request.method, response); + ret = CreateErrorResponse(request.connection, responseDetails.status, request.method, response); break; default: + CLog::Log(LOGERROR, "CWebServer: internal error while HTTP request handler processed %s", request.url.c_str()); delete handler; return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); } if (ret == MHD_NO) { + CLog::Log(LOGERROR, "CWebServer: failed to create HTTP response for %s", request.url.c_str()); delete handler; return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); } - multimap header = handler->GetResponseHeaderFields(); - for (multimap::const_iterator it = header.begin(); it != header.end(); ++it) + return FinalizeRequest(handler, responseDetails.status, response); +} + +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 (multimap::const_iterator it = responseDetails.headers.begin(); it != responseDetails.headers.end(); ++it) AddHeader(response, it->first, it->second); - MHD_queue_response(request.connection, responseCode, response); +#ifdef WEBSERVER_DEBUG + std::multimap headerValues; + GetRequestHeaderValues(request.connection, MHD_RESPONSE_HEADER_KIND, headerValues); + + CLog::Log(LOGDEBUG, "webserver [OUT] %s %d %s", request.version.c_str(), responseStatus, request.url.c_str()); + + for (std::multimap::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 MHD_YES; + return ret; } -HTTPMethod CWebServer::GetMethod(const char *method) +int CWebServer::CreateMemoryDownloadResponse(IHTTPRequestHandler *handler, struct MHD_Response *&response) { - if (strcmp(method, "GET") == 0) - return GET; - if (strcmp(method, "POST") == 0) - return POST; - if (strcmp(method, "HEAD") == 0) - return HEAD; + if (handler == NULL) + return MHD_NO; - return UNKNOWN; + const HTTPRequest &request = handler->GetRequest(); + const HTTPResponseDetails &responseDetails = handler->GetResponseDetails(); + HttpResponseRanges responseRanges = handler->GetResponseData(); + + // check if the response is completely empty + if (responseRanges.empty()) + return CreateMemoryDownloadResponse(request.connection, NULL, 0, false, false, response); + + // check if the response contains more ranges than the request asked for + if ((request.ranges.IsEmpty() && responseRanges.size() > 1) || + (!request.ranges.IsEmpty() && responseRanges.size() > request.ranges.Size())) + { + CLog::Log(LOGWARNING, "CWebServer: response contains more ranges (%d) than the request asked for (%d)", (int)responseRanges.size(), (int)request.ranges.Size()); + return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); + } + + // if the request asked for no or only one range we can simply use MHDs memory download handler + // we MUST NOT send a multipart response + if (request.ranges.Size() <= 1) + { + CHttpResponseRange responseRange = responseRanges.front(); + // check if the range is valid + if (!responseRange.IsValid()) + { + CLog::Log(LOGWARNING, "CWebServer: invalid response data with range start at %lld and end at %lld", responseRange.GetFirstPosition(), responseRange.GetLastPosition()); + return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); + } + + const void* responseData = responseRange.GetData(); + size_t responseDataLength = static_cast(responseRange.GetLength()); + + switch (responseDetails.type) + { + case HTTPMemoryDownloadNoFreeNoCopy: + return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength, false, false, response); + + case HTTPMemoryDownloadNoFreeCopy: + return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength, false, true, response); + + case HTTPMemoryDownloadFreeNoCopy: + return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength, true, false, response); + + case HTTPMemoryDownloadFreeCopy: + return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength, true, true, response); + + default: + return SendErrorResponse(request.connection, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method); + } + } + + return CreateRangedMemoryDownloadResponse(handler, response); +} + +int CWebServer::CreateRangedMemoryDownloadResponse(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(); + + // 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, NULL, 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(range->GetData()), static_cast(range->GetLength())); + + // check if we need to free the range data + if (responseDetails.type == HTTPMemoryDownloadFreeNoCopy || responseDetails.type == HTTPMemoryDownloadFreeCopy) + free(const_cast(range->GetData())); + } + + result += HttpRangeUtils::GenerateMultipartBoundaryEnd(multipartBoundary); + + // add Content-Length header + handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_LENGTH, StringUtils::Format("%" PRIu64, static_cast(result.size()))); + + // finally create the response + return CreateMemoryDownloadResponse(request.connection, result.c_str(), result.size(), false, true, response); } int CWebServer::CreateRedirect(struct MHD_Connection *connection, const string &strURL, struct MHD_Response *&response) { response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO); if (response == NULL) + { + CLog::Log(LOGERROR, "CWebServer: failed to create HTTP redirect response to %s", strURL.c_str()); return MHD_NO; + } AddHeader(response, MHD_HTTP_HEADER_LOCATION, strURL); return MHD_YES; } -int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode) +int CWebServer::CreateFileDownloadResponse(IHTTPRequestHandler *handler, struct MHD_Response *&response) { - boost::shared_ptr file = boost::make_shared(); + if (handler == NULL) + return MHD_NO; -#ifdef WEBSERVER_DEBUG - CLog::Log(LOGDEBUG, "webserver [IN] %s", strURL.c_str()); - multimap headers; - if (GetRequestHeaderValues(connection, MHD_HEADER_KIND, headers) > 0) - { - for (multimap::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 + const HTTPRequest &request = handler->GetRequest(); + const HTTPResponseDetails &responseDetails = handler->GetResponseDetails(); + HttpResponseRanges responseRanges = handler->GetResponseData(); - if (!file->Open(strURL, READ_NO_CACHE)) + boost::shared_ptr file = boost::make_shared(); + std::string filePath = handler->GetResponseFile(); + + if (!file->Open(filePath, READ_NO_CACHE)) { - CLog::Log(LOGERROR, "WebServer: Failed to open %s", strURL.c_str()); - return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType); + CLog::Log(LOGERROR, "WebServer: Failed to open %s", filePath.c_str()); + return SendErrorResponse(request.connection, MHD_HTTP_NOT_FOUND, request.method); } - 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(); + uint64_t fileLength = static_cast(file->GetLength()); // get the MIME type for the Content-Type header - std::string ext = URIUtils::GetExtension(strURL); - StringUtils::ToLower(ext); - string mimeType = CreateMimeTypeFromExtension(ext.c_str()); + string mimeType = responseDetails.contentType; + if (mimeType.empty()) + { + std::string ext = URIUtils::GetExtension(filePath); + StringUtils::ToLower(ext); + mimeType = CreateMimeTypeFromExtension(ext.c_str()); + } - if (methodType != HEAD) + if (request.method != HEAD) { - int64_t firstPosition = 0; - int64_t lastPosition = fileLength - 1; uint64_t totalLength = 0; std::auto_ptr context(new HttpFileDownloadContext()); context->file = file; - context->rangesLength = fileLength; context->contentType = mimeType; context->boundaryWritten = false; context->writePosition = 0; - if (methodType == GET) + if (handler->IsRequestRanged()) { - bool cacheable = true; - - // handle Cache-Control - string cacheControl = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CACHE_CONTROL); - if (!cacheControl.empty()) - { - vector cacheControls = StringUtils::Split(cacheControl, ","); - for (vector::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 (!request.ranges.IsEmpty()) + context->ranges = request.ranges; + else + GetRequestedRanges(request.connection, fileLength, context->ranges); + } - if (getData) - { - // handle Range header - context->rangesLength = ParseRangeHeader(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE), fileLength, context->ranges, firstPosition, lastPosition); + 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); - // 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); + // 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; - // 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(); - } - } - } + context->ranges.GetFirstPosition(firstPosition); + context->ranges.GetLastPosition(lastPosition); } - 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->rangeCountTotal = context->ranges.Size(); + // remember the total length + totalLength = context->ranges.GetLength(); - // remember the total number of ranges - context->rangeCount = context->ranges.size(); - // remember the total length - totalLength = context->rangesLength; + // 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); - // 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; + // build part of the boundary with the optional Content-Type header + // "--\r\nContent-Type: \r\n + context->boundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundary, context->contentType); + context->boundaryEnd = HttpRangeUtils::GenerateMultipartBoundaryEnd(context->boundary); - // adjust the MIME type and range length in case of multiple ranges - // which requires multipart boundaries - if (context->rangeCount > 1) + // for every range, we need to add a boundary with header + for (HttpRanges::const_iterator range = context->ranges.Begin(); range != context->ranges.End(); ++range) { - context->boundary = GenerateMultipartBoundary(); - mimeType = "multipart/byteranges; boundary=" + context->boundary; - - // build part of the boundary with the optional Content-Type header - // "--\r\nContent-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; + // we need to temporarily add the Content-Range header to the boundary to be able to determine the length + string completeBoundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundaryWithHeader, &*range); + totalLength += completeBoundaryWithHeader.size(); - // 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----" - totalLength += strlen(HEADER_SEPARATOR) + strlen(HEADER_BOUNDARY) + context->boundary.size() + strlen(HEADER_BOUNDARY); + // 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----" + totalLength += context->boundaryEnd.size(); + } - // set the initial write position - context->writePosition = context->ranges.begin()->first; + // 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) - return MHD_NO; - - context.release(); // ownership was passed to mhd + // 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.url.c_str(), filePath.c_str()); + 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)); + handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_RANGE, HttpRangeUtils::GenerateContentRangeHeaderValue(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) + { + CLog::Log(LOGERROR, "CWebServer: failed to create a HTTP HEAD response for %s", request.url.c_str()); return MHD_NO; + } - AddHeader(response, MHD_HTTP_HEADER_CONTENT_LENGTH, contentLength); + handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_LENGTH, StringUtils::Format("%" PRId64, fileLength)); } - // 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)); + handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_TYPE, mimeType); return MHD_YES; } @@ -654,19 +877,25 @@ int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int respo } response = MHD_create_response_from_data(payloadSize, payload, MHD_NO, MHD_NO); - if (response != NULL) - return MHD_YES; + if (response == NULL) + { + CLog::Log(LOGERROR, "CWebServer: failed to create a HTTP %d error response", responseType); + return MHD_NO; + } - return MHD_NO; + return MHD_YES; } -int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection, void *data, size_t size, bool free, bool copy, struct MHD_Response *&response) +int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection, const void *data, size_t size, bool free, bool copy, struct MHD_Response *&response) { - response = MHD_create_response_from_data(size, data, free ? MHD_YES : MHD_NO, copy ? MHD_YES : MHD_NO); - if (response != NULL) - return MHD_YES; + response = MHD_create_response_from_data(size, const_cast(data), free ? MHD_YES : MHD_NO, copy ? MHD_YES : MHD_NO); + if (response == NULL) + { + CLog::Log(LOGERROR, "CWebServer: failed to create a HTTP download response"); + return MHD_NO; + } - return MHD_NO; + return MHD_YES; } int CWebServer::SendErrorResponse(struct MHD_Connection *connection, int errorType, HTTPMethod method) @@ -675,6 +904,16 @@ int CWebServer::SendErrorResponse(struct MHD_Connection *connection, int errorTy int ret = CreateErrorResponse(connection, errorType, method, response); if (ret == MHD_YES) { +#ifdef WEBSERVER_DEBUG + std::multimap headerValues; + GetRequestHeaderValues(connection, MHD_RESPONSE_HEADER_KIND, headerValues); + + CLog::Log(LOGDEBUG, "webserver [OUT] HTTP %d", errorType); + + for (std::multimap::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 + ret = MHD_queue_response(connection, errorType, response); MHD_destroy_response(response); } @@ -705,10 +944,10 @@ int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max) #endif // check if we need to add the end-boundary - if (context->rangeCount > 1 && context->ranges.empty()) + if (context->rangeCountTotal > 1 && context->ranges.IsEmpty()) { // put together the end-boundary - string endBoundary = HEADER_NEWLINE HEADER_BOUNDARY + context->boundary + HEADER_BOUNDARY; + string endBoundary = HttpRangeUtils::GenerateMultipartBoundaryEnd(context->boundary); if ((unsigned int)max != endBoundary.size()) return -1; @@ -717,19 +956,29 @@ int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max) return endBoundary.size(); } - if (context->ranges.empty()) + CHttpRange range; + if (context->ranges.IsEmpty() || !context->ranges.GetFirst(range)) return -1; - int64_t start = context->ranges.at(0).first; - int64_t end = context->ranges.at(0).second; - int64_t maximum = (int64_t)max; + uint64_t start = range.GetFirstPosition(); + uint64_t end = range.GetLastPosition(); + uint64_t maximum = (uint64_t)max; int written = 0; - if (context->rangeCount > 1 && !context->boundaryWritten) + if (context->rangeCountTotal > 1 && !context->boundaryWritten) { + // add a newline before any new multipart boundary + if (context->rangeCountTotal > context->ranges.Size()) + { + size_t newlineLength = strlen(HEADER_NEWLINE); + memcpy(buf, HEADER_NEWLINE, newlineLength); + buf += newlineLength; + written += newlineLength; + maximum -= newlineLength; + } + // put together the boundary for the current range - string boundary = context->boundaryWithHeader; - boundary += StringUtils::Format(MHD_HTTP_HEADER_CONTENT_RANGE ": " CONTENT_RANGE_FORMAT, start, end, end - start + 1) + HEADER_SEPARATOR; + string boundary = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundaryWithHeader, &range); // copy the boundary into the buffer memcpy(buf, boundary.c_str(), boundary.size()); @@ -750,8 +999,8 @@ int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max) maximum = std::min(maximum, end - context->writePosition + 1); // seek to the position if necessary - if(context->writePosition != context->file->GetPosition()) - context->file->Seek(context->writePosition); + if (context->file->GetPosition() < 0 || context->writePosition != static_cast(context->file->GetPosition())) + context->file->Seek(static_cast(context->writePosition)); // read data from the file ssize_t res = context->file->Read(buf, static_cast(maximum)); @@ -761,7 +1010,7 @@ int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max) // add the number of read bytes to the number of written bytes written += res; #ifdef WEBSERVER_DEBUG - CLog::Log(LOGDEBUG, "webserver [OUT] wrote %d bytes from %" PRId64 " in range (%" PRId64 " - %" PRId64 ")", written, context->writePosition, start, end); + CLog::Log(LOGDEBUG, "webserver [OUT] wrote %d bytes from %" PRIu64 " in range (%" PRIu64 " - %" PRIu64 ")", written, context->writePosition, start, end); #endif // update the current write position context->writePosition += res; @@ -770,7 +1019,7 @@ int CWebServer::ContentReaderCallback(void *cls, size_t pos, char *buf, int max) // remove it from the list if (context->writePosition >= end + 1) { - context->ranges.erase(context->ranges.begin()); + context->ranges.Remove(0); context->boundaryWritten = false; } @@ -978,6 +1227,16 @@ int CWebServer::GetRequestHeaderValues(struct MHD_Connection *connection, enum M return MHD_get_connection_values(connection, kind, FillArgumentMultiMap, &headerValues); } +bool CWebServer::GetRequestedRanges(struct MHD_Connection *connection, uint64_t totalLength, CHttpRanges &ranges) +{ + ranges.Clear(); + + if (connection == NULL) + return false; + + return ranges.Parse(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE), totalLength); +} + std::string CWebServer::CreateMimeTypeFromExtension(const char *ext) { if (strcmp(ext, ".kar") == 0) @@ -999,101 +1258,6 @@ int CWebServer::AddHeader(struct MHD_Response *response, const std::string &name return MHD_add_response_header(response, name.c_str(), value.c_str()); } -int64_t CWebServer::ParseRangeHeader(const std::string &rangeHeaderValue, int64_t totalLength, HttpRanges &ranges, int64_t &firstPosition, int64_t &lastPosition) -{ - firstPosition = 0; - lastPosition = totalLength - 1; - - if (rangeHeaderValue.empty() || !StringUtils::StartsWithNoCase(rangeHeaderValue, "bytes=")) - return totalLength; - - int64_t rangesLength = 0; - - // remove "bytes=" from the beginning - string rangesValue = rangeHeaderValue.substr(6); - // split the value of the "Range" header by "," - vector rangeValues = StringUtils::Split(rangesValue, ","); - for (vector::const_iterator range = rangeValues.begin(); range != rangeValues.end(); ++range) - { - // there must be a "-" in the range definition - if (range->find("-") == string::npos) - { - ranges.clear(); - return totalLength; - } - - vector positions = StringUtils::Split(*range, "-"); - if (positions.size() > 2) - { - ranges.clear(); - return totalLength; - } - - int64_t positionStart = -1; - int64_t positionEnd = -1; - if (!positions.at(0).empty()) - positionStart = str2int64(positions.at(0), -1); - if (!positions.at(1).empty()) - positionEnd = str2int64(positions.at(1), -1); - - if (positionStart < 0 && positionEnd < 0) - { - ranges.clear(); - return totalLength; - } - - // if there's no end position, use the file's length - if (positionEnd < 0) - positionEnd = totalLength - 1; - else if (positionStart < 0) - { - positionStart = totalLength - positionEnd; - positionEnd = totalLength - 1; - } - - if (positionEnd < positionStart) - { - ranges.clear(); - return totalLength; - } - - if (ranges.empty()) - { - firstPosition = positionStart; - lastPosition = positionEnd; - } - else - { - if (positionStart < firstPosition) - firstPosition = positionStart; - if (positionEnd > lastPosition) - lastPosition = positionEnd; - } - - ranges.push_back(HttpRange(positionStart, positionEnd)); - rangesLength += positionEnd - positionStart + 1; - } - - if (!ranges.empty() || rangesLength > 0) - return rangesLength; - - return totalLength; -} - -std::string CWebServer::GenerateMultipartBoundary() -{ - static char chars[] = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - // create a string of length 30 to 40 and pre-fill it with "-" - size_t count = (size_t)CUtil::GetRandomNumber() % 11 + 30; - string boundary(count, '-'); - - for (size_t i = (size_t)CUtil::GetRandomNumber() % 5 + 8; i < count; i++) - boundary.replace(i, 1, 1, chars[(size_t)CUtil::GetRandomNumber() % 64]); - - return boundary; -} - bool CWebServer::GetLastModifiedDateTime(XFILE::CFile *file, CDateTime &lastModified) { if (file == NULL) diff --git a/xbmc/network/WebServer.h b/xbmc/network/WebServer.h index c1d67b25250f5..d7cef3f716359 100644 --- a/xbmc/network/WebServer.h +++ b/xbmc/network/WebServer.h @@ -34,9 +34,6 @@ namespace XFILE } class CDateTime; -typedef std::pair HttpRange; -typedef std::vector HttpRanges; - class CWebServer : public JSONRPC::ITransportLayer { public: @@ -60,6 +57,8 @@ class CWebServer : public JSONRPC::ITransportLayer static int GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::map &headerValues); static int GetRequestHeaderValues(struct MHD_Connection *connection, enum MHD_ValueKind kind, std::multimap &headerValues); + static bool GetRequestedRanges(struct MHD_Connection *connection, uint64_t totalLength, CHttpRanges &ranges); + private: struct MHD_Daemon* StartMHD(unsigned int flags, int port); static int AskForAuthentication (struct MHD_Connection *connection); @@ -74,6 +73,7 @@ class CWebServer : public JSONRPC::ITransportLayer #else static int ContentReaderCallback (void *cls, size_t pos, char *buf, int max); #endif + static void ContentReaderFreeCallback(void *cls); #if (MHD_VERSION >= 0x00040001) static int AnswerToConnection (void *cls, struct MHD_Connection *connection, @@ -95,11 +95,15 @@ class CWebServer : public JSONRPC::ITransportLayer unsigned int size); #endif static int HandleRequest(IHTTPRequestHandler *handler); - static void ContentReaderFreeCallback (void *cls); + static int FinalizeRequest(IHTTPRequestHandler *handler, int responseStatus, struct MHD_Response *response); + + static int CreateMemoryDownloadResponse(IHTTPRequestHandler *handler, struct MHD_Response *&response); + static int CreateRangedMemoryDownloadResponse(IHTTPRequestHandler *handler, struct MHD_Response *&response); + static int CreateRedirect(struct MHD_Connection *connection, const std::string &strURL, struct MHD_Response *&response); - static int CreateFileDownloadResponse(struct MHD_Connection *connection, const std::string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode); + static int CreateFileDownloadResponse(IHTTPRequestHandler *handler, struct MHD_Response *&response); static int CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response); - static int CreateMemoryDownloadResponse(struct MHD_Connection *connection, void *data, size_t size, bool free, bool copy, struct MHD_Response *&response); + static int CreateMemoryDownloadResponse(struct MHD_Connection *connection, const void *data, size_t size, bool free, bool copy, struct MHD_Response *&response); static int SendErrorResponse(struct MHD_Connection *connection, int errorType, HTTPMethod method); @@ -110,8 +114,6 @@ class CWebServer : public JSONRPC::ITransportLayer static std::string CreateMimeTypeFromExtension(const char *ext); static int AddHeader(struct MHD_Response *response, const std::string &name, const std::string &value); - static int64_t ParseRangeHeader(const std::string &rangeHeaderValue, int64_t totalLength, HttpRanges &ranges, int64_t &firstPosition, int64_t &lastPosition); - static std::string GenerateMultipartBoundary(); static bool GetLastModifiedDateTime(XFILE::CFile *file, CDateTime &lastModified); struct MHD_Daemon *m_daemon_ip6; diff --git a/xbmc/network/httprequesthandler/HTTPFileHandler.cpp b/xbmc/network/httprequesthandler/HTTPFileHandler.cpp new file mode 100644 index 0000000000000..b76fd412878d4 --- /dev/null +++ b/xbmc/network/httprequesthandler/HTTPFileHandler.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2015 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "system.h" +#include "HTTPFileHandler.h" +#include "filesystem/File.h" +#include "utils/Mime.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +CHTTPFileHandler::CHTTPFileHandler(const HTTPRequest &request) + : IHTTPRequestHandler(request), + m_url(), + m_canHandleRanges(true), + m_canBeCached(true), + m_lastModified() +{ } + +int CHTTPFileHandler::HandleRequest() +{ + return !m_url.empty() ? MHD_YES : MHD_NO; +} + +bool CHTTPFileHandler::GetLastModifiedDate(CDateTime &lastModified) const +{ + if (!m_lastModified.IsValid()) + return false; + + lastModified = m_lastModified; + return true; +} + +void CHTTPFileHandler::SetFile(const std::string& file, int responseStatus) +{ + m_url = file; + m_response.status = responseStatus; + if (m_url.empty()) + return; + + // translate the response status into the response type + if (m_response.status == MHD_HTTP_OK) + m_response.type = HTTPFileDownload; + else if (m_response.status == MHD_HTTP_FOUND) + m_response.type = HTTPRedirect; + else + m_response.type = HTTPError; + + // try to determine some additional information if the file can be downloaded + if (m_response.type == HTTPFileDownload) + { + // determine the content type + std::string ext = URIUtils::GetExtension(m_url); + StringUtils::ToLower(ext); + m_response.contentType = CMime::GetMimeType(ext); + + // determine the last modified date + XFILE::CFile fileObj; + if (!fileObj.Open(m_url, READ_NO_CACHE)) + { + m_response.type = HTTPError; + m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR; + } + else + { + struct __stat64 statBuffer; + if (fileObj.Stat(&statBuffer) == 0) + { + struct tm *time; +#ifdef HAVE_LOCALTIME_R + struct tm result = { }; + time = localtime_r((time_t*)&statBuffer.st_mtime, &result); +#else + time = localtime((time_t *)&statBuffer.st_mtime); +#endif + if (time != NULL) + m_lastModified = *time; + } + } + } + + // disable ranges and caching if the file can't be downloaded + if (m_response.type != HTTPFileDownload) + { + m_canHandleRanges = false; + m_canBeCached = false; + } + + // disable caching if the last modified date couldn't be read + if (!m_lastModified.IsValid()) + m_canBeCached = false; +} diff --git a/xbmc/network/httprequesthandler/HTTPFileHandler.h b/xbmc/network/httprequesthandler/HTTPFileHandler.h new file mode 100644 index 0000000000000..cf7139c68f9f4 --- /dev/null +++ b/xbmc/network/httprequesthandler/HTTPFileHandler.h @@ -0,0 +1,59 @@ +#pragma once +/* + * Copyright (C) 2015 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "XBDateTime.h" +#include "network/httprequesthandler/IHTTPRequestHandler.h" + +class CHTTPFileHandler : public IHTTPRequestHandler +{ +public: + virtual ~CHTTPFileHandler() { } + + virtual int HandleRequest(); + + virtual bool CanHandleRanges() const { return m_canHandleRanges; } + virtual bool CanBeCached() const { return m_canBeCached; } + virtual bool GetLastModifiedDate(CDateTime &lastModified) const; + + virtual std::string GetRedirectUrl() const { return m_url; } + virtual std::string GetResponseFile() const { return m_url; } + +protected: + CHTTPFileHandler() { } + explicit CHTTPFileHandler(const HTTPRequest &request); + + void SetFile(const std::string& file, int responseStatus); + + void SetCanHandleRanges(bool canHandleRanges) { m_canHandleRanges = canHandleRanges; } + void SetCanBeCached(bool canBeCached) { m_canBeCached = canBeCached; } + void SetLastModifiedDate(CDateTime lastModified) { m_lastModified = lastModified; } + +private: + std::string m_url; + + bool m_canHandleRanges; + bool m_canBeCached; + + CDateTime m_lastModified; + +}; diff --git a/xbmc/network/httprequesthandler/HTTPImageHandler.cpp b/xbmc/network/httprequesthandler/HTTPImageHandler.cpp index 8ce80f0166e00..0b164b19a8f8c 100644 --- a/xbmc/network/httprequesthandler/HTTPImageHandler.cpp +++ b/xbmc/network/httprequesthandler/HTTPImageHandler.cpp @@ -25,35 +25,30 @@ using namespace std; -bool CHTTPImageHandler::CanHandleRequest(const HTTPRequest &request) +CHTTPImageHandler::CHTTPImageHandler(const HTTPRequest &request) + : CHTTPFileHandler(request) { - return (request.url.find("/image/") == 0); -} + std::string file; + int responseStatus = MHD_HTTP_BAD_REQUEST; -int CHTTPImageHandler::HandleRequest() -{ + // resolve the URL into a file path and a HTTP response status if (m_request.url.size() > 7) { - m_path = m_request.url.substr(7); + file = m_request.url.substr(7); XFILE::CImageFile imageFile; - const CURL pathToUrl(m_path); + const CURL pathToUrl(file); if (imageFile.Exists(pathToUrl)) - { - m_responseCode = MHD_HTTP_OK; - m_responseType = HTTPFileDownload; - } + responseStatus = MHD_HTTP_OK; else - { - m_responseCode = MHD_HTTP_NOT_FOUND; - m_responseType = HTTPError; - } - } - else - { - m_responseCode = MHD_HTTP_BAD_REQUEST; - m_responseType = HTTPError; + responseStatus = MHD_HTTP_NOT_FOUND; } - return MHD_YES; + // set the file and the HTTP response status + SetFile(file, responseStatus); +} + +bool CHTTPImageHandler::CanHandleRequest(const HTTPRequest &request) +{ + return request.url.find("/image/") == 0; } diff --git a/xbmc/network/httprequesthandler/HTTPImageHandler.h b/xbmc/network/httprequesthandler/HTTPImageHandler.h index 5891c14f76ba8..9224ccc4047f9 100644 --- a/xbmc/network/httprequesthandler/HTTPImageHandler.h +++ b/xbmc/network/httprequesthandler/HTTPImageHandler.h @@ -21,9 +21,9 @@ #include -#include "network/httprequesthandler/IHTTPRequestHandler.h" +#include "network/httprequesthandler/HTTPFileHandler.h" -class CHTTPImageHandler : public IHTTPRequestHandler +class CHTTPImageHandler : public CHTTPFileHandler { public: CHTTPImageHandler() { } @@ -32,17 +32,8 @@ class CHTTPImageHandler : public IHTTPRequestHandler virtual IHTTPRequestHandler* Create(const HTTPRequest &request) { return new CHTTPImageHandler(request); } virtual bool CanHandleRequest(const HTTPRequest &request); - virtual int HandleRequest(); - - virtual std::string GetResponseFile() const { return m_path; } - virtual int GetPriority() const { return 2; } protected: - CHTTPImageHandler(const HTTPRequest &request) - : IHTTPRequestHandler(request) - { } - -private: - std::string m_path; + explicit CHTTPImageHandler(const HTTPRequest &request); }; diff --git a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp index 8a90b0e2c08b5..2b325f48bd843 100644 --- a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp +++ b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp @@ -48,8 +48,8 @@ int CHTTPJsonRpcHandler::HandleRequest() if (!contentType.empty() && contentType.compare("application/json-rpc") != 0 && contentType.compare("application/json") != 0 && contentType.compare("application/jsonrequest") != 0) { - m_responseType = HTTPError; - m_responseCode = MHD_HTTP_UNSUPPORTED_MEDIA_TYPE; + m_response.type = HTTPError; + m_response.status = MHD_HTTP_UNSUPPORTED_MEDIA_TYPE; return MHD_YES; } @@ -70,25 +70,35 @@ int CHTTPJsonRpcHandler::HandleRequest() } if (isRequest) - m_response = CJSONRPC::MethodCall(m_requestData, m_request.webserver, &client); + m_responseData = CJSONRPC::MethodCall(m_requestData, m_request.webserver, &client); else { // get the whole output of JSONRPC.Introspect CVariant result; CJSONServiceDescription::Print(result, m_request.webserver, &client); - m_response = CJSONVariantWriter::Write(result, false); + m_responseData = CJSONVariantWriter::Write(result, false); } - m_responseHeaderFields.insert(pair(MHD_HTTP_HEADER_CONTENT_TYPE, "application/json")); - m_requestData.clear(); - - m_responseType = HTTPMemoryDownloadNoFreeCopy; - m_responseCode = MHD_HTTP_OK; + + m_responseRange.SetData(m_responseData.c_str(), m_responseData.size()); + + m_response.type = HTTPMemoryDownloadNoFreeCopy; + m_response.status = MHD_HTTP_OK; + m_response.contentType = "application/json"; + m_response.totalLength = m_responseData.size(); return MHD_YES; } +HttpResponseRanges CHTTPJsonRpcHandler::GetResponseData() const +{ + HttpResponseRanges ranges; + ranges.push_back(m_responseRange); + + return ranges; +} + #if (MHD_VERSION >= 0x00040001) bool CHTTPJsonRpcHandler::appendPostData(const char *data, size_t size) #else diff --git a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h index 83e81f5d1b0f8..dbd21018b6c99 100644 --- a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h +++ b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h @@ -35,13 +35,12 @@ class CHTTPJsonRpcHandler : public IHTTPRequestHandler virtual int HandleRequest(); - virtual void* GetResponseData() const { return (void *)m_response.c_str(); }; - virtual size_t GetResponseDataLength() const { return m_response.size(); } + virtual HttpResponseRanges GetResponseData() const; virtual int GetPriority() const { return 2; } protected: - CHTTPJsonRpcHandler(const HTTPRequest &request) + explicit CHTTPJsonRpcHandler(const HTTPRequest &request) : IHTTPRequestHandler(request) { } @@ -53,7 +52,8 @@ class CHTTPJsonRpcHandler : public IHTTPRequestHandler private: std::string m_requestData; - std::string m_response; + std::string m_responseData; + CHttpResponseRange m_responseRange; class CHTTPClient : public JSONRPC::IClient { diff --git a/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp b/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp index 09785211c6ac4..d6e0459fe5d3a 100644 --- a/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp +++ b/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp @@ -28,30 +28,28 @@ using namespace std; -bool CHTTPVfsHandler::CanHandleRequest(const HTTPRequest &request) +CHTTPVfsHandler::CHTTPVfsHandler(const HTTPRequest &request) + : CHTTPFileHandler(request) { - return (request.url.find("/vfs") == 0); -} + std::string file; + int responseStatus = MHD_HTTP_BAD_REQUEST; -int CHTTPVfsHandler::HandleRequest() -{ if (m_request.url.size() > 5) { - m_path = m_request.url.substr(5); + file = m_request.url.substr(5); - if (XFILE::CFile::Exists(m_path)) + if (XFILE::CFile::Exists(file)) { bool accessible = false; - if (m_path.substr(0, 8) == "image://") + if (file.substr(0, 8) == "image://") accessible = true; else { string sourceTypes[] = { "video", "music", "pictures" }; unsigned int size = sizeof(sourceTypes) / sizeof(string); - string realPath = URIUtils::GetRealPath(m_path); - // for rar:// and zip:// paths we need to extract the path to the archive - // instead of using the VFS path + string realPath = URIUtils::GetRealPath(file); + // for rar:// and zip:// paths we need to extract the path to the archive instead of using the VFS path while (URIUtils::IsInArchive(realPath)) realPath = CURL(realPath).GetHostName(); @@ -82,28 +80,20 @@ int CHTTPVfsHandler::HandleRequest() } if (accessible) - { - m_responseCode = MHD_HTTP_OK; - m_responseType = HTTPFileDownload; - } + responseStatus = MHD_HTTP_OK; // the file exists but not in one of the defined sources so we deny access to it else - { - m_responseCode = MHD_HTTP_UNAUTHORIZED; - m_responseType = HTTPError; - } + responseStatus = MHD_HTTP_UNAUTHORIZED; } else - { - m_responseCode = MHD_HTTP_NOT_FOUND; - m_responseType = HTTPError; - } - } - else - { - m_responseCode = MHD_HTTP_BAD_REQUEST; - m_responseType = HTTPError; + responseStatus = MHD_HTTP_NOT_FOUND; } - return MHD_YES; + // set the file and the HTTP response status + SetFile(file, responseStatus); +} + +bool CHTTPVfsHandler::CanHandleRequest(const HTTPRequest &request) +{ + return request.url.find("/vfs") == 0; } diff --git a/xbmc/network/httprequesthandler/HTTPVfsHandler.h b/xbmc/network/httprequesthandler/HTTPVfsHandler.h index d08b56baf4d2c..5a3ff2f3d7b7a 100644 --- a/xbmc/network/httprequesthandler/HTTPVfsHandler.h +++ b/xbmc/network/httprequesthandler/HTTPVfsHandler.h @@ -21,9 +21,9 @@ #include -#include "network/httprequesthandler/IHTTPRequestHandler.h" +#include "network/httprequesthandler/HTTPFileHandler.h" -class CHTTPVfsHandler : public IHTTPRequestHandler +class CHTTPVfsHandler : public CHTTPFileHandler { public: CHTTPVfsHandler() { } @@ -32,17 +32,8 @@ class CHTTPVfsHandler : public IHTTPRequestHandler virtual IHTTPRequestHandler* Create(const HTTPRequest &request) { return new CHTTPVfsHandler(request); } virtual bool CanHandleRequest(const HTTPRequest &request); - virtual int HandleRequest(); - - virtual std::string GetResponseFile() const { return m_path; } - virtual int GetPriority() const { return 2; } protected: - CHTTPVfsHandler(const HTTPRequest &request) - : IHTTPRequestHandler(request) - { } - -private: - std::string m_path; + explicit CHTTPVfsHandler(const HTTPRequest &request); }; diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp index a90ba214fa77d..f1e567f657dda 100644 --- a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp +++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp @@ -34,19 +34,31 @@ bool CHTTPWebinterfaceAddonsHandler::CanHandleRequest(const HTTPRequest &request int CHTTPWebinterfaceAddonsHandler::HandleRequest() { - m_response = ADDON_HEADER; + m_responseData = ADDON_HEADER; VECADDONS addons; CAddonMgr::Get().GetAddons(ADDON_WEB_INTERFACE, addons); IVECADDONS addons_it; for (addons_it=addons.begin(); addons_it!=addons.end(); addons_it++) - m_response += "
  • ID() + "/>" + (*addons_it)->Name() + "
  • \n"; + m_responseData += "
  • ID() + "/>" + (*addons_it)->Name() + "
  • \n"; - m_response += "\n"; + m_responseData += "\n"; - m_responseType = HTTPMemoryDownloadNoFreeCopy; - m_responseCode = MHD_HTTP_OK; + m_responseRange.SetData(m_responseData.c_str(), m_responseData.size()); + + m_response.type = HTTPMemoryDownloadNoFreeCopy; + m_response.status = MHD_HTTP_OK; + m_response.contentType = "text/html"; + m_response.totalLength = m_responseData.size(); return MHD_YES; } +HttpResponseRanges CHTTPWebinterfaceAddonsHandler::GetResponseData() const +{ + HttpResponseRanges ranges; + ranges.push_back(m_responseRange); + + return ranges; +} + diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h index eed668179d596..23f1c6a27ca77 100644 --- a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h +++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h @@ -34,16 +34,16 @@ class CHTTPWebinterfaceAddonsHandler : public IHTTPRequestHandler virtual int HandleRequest(); - virtual void* GetResponseData() const { return (void *)m_response.c_str(); }; - virtual size_t GetResponseDataLength() const { return m_response.size(); } + virtual HttpResponseRanges GetResponseData() const; virtual int GetPriority() const { return 1; } protected: - CHTTPWebinterfaceAddonsHandler(const HTTPRequest &request) + explicit CHTTPWebinterfaceAddonsHandler(const HTTPRequest &request) : IHTTPRequestHandler(request) { } private: - std::string m_response; + std::string m_responseData; + CHttpResponseRange m_responseRange; }; diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp index 16d5682846493..baa89a723728a 100644 --- a/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp +++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp @@ -19,11 +19,9 @@ */ #include "HTTPWebinterfaceHandler.h" -#include "Util.h" #include "addons/AddonManager.h" #include "filesystem/Directory.h" #include "filesystem/File.h" -#include "network/WebServer.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" @@ -33,27 +31,20 @@ using namespace std; using namespace ADDON; using namespace XFILE; -bool CHTTPWebinterfaceHandler::CanHandleRequest(const HTTPRequest &request) +CHTTPWebinterfaceHandler::CHTTPWebinterfaceHandler(const HTTPRequest &request) + : CHTTPFileHandler(request) { - return true; + // resolve the URL into a file path and a HTTP response status + std::string file; + int responseStatus = ResolveUrl(request.url, file); + + // set the file and the HTTP response status + SetFile(file, responseStatus); } -int CHTTPWebinterfaceHandler::HandleRequest() +bool CHTTPWebinterfaceHandler::CanHandleRequest(const HTTPRequest &request) { - m_responseCode = ResolveUrl(m_request.url, m_url); - if (m_responseCode != MHD_HTTP_OK) - { - if (m_responseCode == MHD_HTTP_FOUND) - m_responseType = HTTPRedirect; - else - m_responseType = HTTPError; - - return MHD_YES; - } - - m_responseType = HTTPFileDownload; - - return MHD_YES; + return true; } int CHTTPWebinterfaceHandler::ResolveUrl(const std::string &url, std::string &path) diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.h b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.h index 120eed9ebaedc..fb6d7c43c49ae 100644 --- a/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.h +++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.h @@ -22,9 +22,9 @@ #include #include "addons/IAddon.h" -#include "network/httprequesthandler/IHTTPRequestHandler.h" +#include "network/httprequesthandler/HTTPFileHandler.h" -class CHTTPWebinterfaceHandler : public IHTTPRequestHandler +class CHTTPWebinterfaceHandler : public CHTTPFileHandler { public: CHTTPWebinterfaceHandler() { } @@ -32,20 +32,10 @@ class CHTTPWebinterfaceHandler : public IHTTPRequestHandler virtual IHTTPRequestHandler* Create(const HTTPRequest &request) { return new CHTTPWebinterfaceHandler(request); } virtual bool CanHandleRequest(const HTTPRequest &request); - - virtual int HandleRequest(); - - virtual std::string GetRedirectUrl() const { return m_url; } - virtual std::string GetResponseFile() const { return m_url; } static int ResolveUrl(const std::string &url, std::string &path); static int ResolveUrl(const std::string &url, std::string &path, ADDON::AddonPtr &addon); protected: - CHTTPWebinterfaceHandler(const HTTPRequest &request) - : IHTTPRequestHandler(request) - { } - -private: - std::string m_url; + explicit CHTTPWebinterfaceHandler(const HTTPRequest &request); }; diff --git a/xbmc/network/httprequesthandler/IHTTPRequestHandler.cpp b/xbmc/network/httprequesthandler/IHTTPRequestHandler.cpp index 3b72607d27014..3f46ed879a890 100644 --- a/xbmc/network/httprequesthandler/IHTTPRequestHandler.cpp +++ b/xbmc/network/httprequesthandler/IHTTPRequestHandler.cpp @@ -19,14 +19,37 @@ */ #include "IHTTPRequestHandler.h" +#include "network/WebServer.h" IHTTPRequestHandler::IHTTPRequestHandler(const HTTPRequest &request) : m_request(request), - m_responseCode(MHD_HTTP_INTERNAL_SERVER_ERROR), - m_responseType(HTTPError), - m_responseHeaderFields(), + m_response(), m_postFields() -{ } +{ + m_response.type = HTTPError; + m_response.status = MHD_HTTP_INTERNAL_SERVER_ERROR; + m_response.totalLength = 0; +} + +bool IHTTPRequestHandler::HasResponseHeader(const std::string &field) const +{ + if (field.empty()) + return false; + + return m_response.headers.find(field) != m_response.headers.end(); +} + +bool IHTTPRequestHandler::AddResponseHeader(const std::string &field, const std::string &value, bool allowMultiple /* = false */) +{ + if (field.empty() || value.empty()) + return false; + + if (!allowMultiple && HasResponseHeader(field)) + return false; + + m_response.headers.insert(std::make_pair(field, value)); + return true; +} void IHTTPRequestHandler::AddPostField(const std::string &key, const std::string &value) { @@ -50,4 +73,16 @@ bool IHTTPRequestHandler::AddPostData(const char *data, unsigned int size) return appendPostData(data, size); return true; +} + +bool IHTTPRequestHandler::GetRequestedRanges(uint64_t totalLength) +{ + if (!m_ranged || m_request.webserver == NULL || m_request.connection == NULL) + return false; + + m_request.ranges.Clear(); + if (totalLength == 0) + return true; + + return m_request.webserver->GetRequestedRanges(m_request.connection, totalLength, m_request.ranges); } \ No newline at end of file diff --git a/xbmc/network/httprequesthandler/IHTTPRequestHandler.h b/xbmc/network/httprequesthandler/IHTTPRequestHandler.h index 5379997f4ccbc..9bf6cd580ce52 100644 --- a/xbmc/network/httprequesthandler/IHTTPRequestHandler.h +++ b/xbmc/network/httprequesthandler/IHTTPRequestHandler.h @@ -31,8 +31,9 @@ #include #include -#include "XBDateTime.h" +#include "utils/HttpRangeUtils.h" +class CDateTime; class CWebServer; enum HTTPMethod @@ -43,7 +44,7 @@ enum HTTPMethod HEAD }; -enum HTTPResponseType +typedef enum HTTPResponseType { HTTPNone, // creates and returns a HTTP error @@ -62,17 +63,26 @@ enum HTTPResponseType // creates a HTTP response from a buffer by copying followed by freeing the buffer // the buffer must have been malloc'ed and not new'ed HTTPMemoryDownloadFreeCopy -}; +} HTTPResponseType; typedef struct HTTPRequest { + CWebServer *webserver; struct MHD_Connection *connection; std::string url; HTTPMethod method; std::string version; - CWebServer *webserver; + CHttpRanges ranges; } HTTPRequest; +typedef struct HTTPResponseDetails { + HTTPResponseType type; + int status; + std::multimap headers; + std::string contentType; + uint64_t totalLength; +} HTTPResponseDetails; + class IHTTPRequestHandler { public: @@ -106,11 +116,6 @@ class IHTTPRequestHandler */ virtual bool CanHandleRequest(const HTTPRequest &request) = 0; - /*! - * \brief Returns the HTTP request handled by the HTTP request handler. - */ - const HTTPRequest& GetRequest() { return m_request; } - /*! * \brief Handles the HTTP request. * @@ -119,32 +124,35 @@ class IHTTPRequestHandler virtual int HandleRequest() = 0; /*! - * \brief Returns the type of the response. + * \brief Whether the HTTP response could also be provided in ranges. + */ + virtual bool CanHandleRanges() const { return false; } + + /*! + * \brief Whether the HTTP response can be cached. */ - HTTPResponseType GetResponseType() const { return m_responseType; } + virtual bool CanBeCached() const { return false; } /*! - * \brief Returns the HTTP status of the response. + * \brief Returns the maximum age (in seconds) for which the response can be cached. + * + * \details This is only used if the response can be cached. */ - int GetResponseCode() const { return m_responseCode; } + virtual int GetMaximumAgeForCaching() const { return 0; } /*! - * \brief Returns the HTTP response's header field-value pairs. + * \brief Returns the last modification date of the response data. + * + * \details This is only used if the response can be cached. */ - const std::multimap& GetResponseHeaderFields() const { return m_responseHeaderFields; }; + virtual bool GetLastModifiedDate(CDateTime &lastModified) const { return false; } /*! - * \brief Returns the raw data of the response. + * \brief Returns the ranges with raw data belonging to the response. * * \details This is only used if the response type is one of the HTTPMemoryDownload types. */ - virtual void* GetResponseData() const { return NULL; }; - /*! - * \brief Returns the length of the raw data of the response. - * - * \details This is only used if the response type is one of the HTTPMemoryDownload types. - */ - virtual size_t GetResponseDataLength() const { return 0; } + virtual HttpResponseRanges GetResponseData() const { return HttpResponseRanges(); }; /*! * \brief Returns the URL to which the request should be redirected. @@ -160,6 +168,51 @@ class IHTTPRequestHandler */ virtual std::string GetResponseFile() const { return ""; } + /*! + * \brief Returns the HTTP request handled by the HTTP request handler. + */ + const HTTPRequest& GetRequest() const { return m_request; } + + /*! + * \brief Returns true if the HTTP request is ranged, otherwise false. + */ + bool IsRequestRanged() const { return m_ranged; } + + /*! + * \brief Sets whether the HTTP request contains ranges or not + */ + void SetRequestRanged(bool ranged) { m_ranged = ranged; } + + /*! + * \brief Sets the response status of the HTTP response. + * + * \param status HTTP status of the response + */ + void SetResponseStatus(int status) { m_response.status = status; } + + /*! + * \brief Checks if the given HTTP header field is part of the response details. + * + * \param field HTTP header field name + * \return True if the header field is set, otherwise false. + */ + bool HasResponseHeader(const std::string &field) const; + + /*! + * \brief Adds the given HTTP header field and value to the response details. + * + * \param field HTTP header field name + * \param value HTTP header field value + * \param allowMultiple Whether the same header is allowed multiple times + * \return True if the header field was added, otherwise false. + */ + bool AddResponseHeader(const std::string &field, const std::string &value, bool allowMultiple = false); + + /*! + * \brief Returns the HTTP response header details. + */ + const HTTPResponseDetails& GetResponseDetails() const { return m_response; } + /*! * \brief Adds the given key-value pair extracted from the HTTP POST data. * @@ -190,11 +243,13 @@ class IHTTPRequestHandler #endif { return true; } - HTTPRequest m_request; + bool GetRequestedRanges(uint64_t totalLength); - int m_responseCode; - HTTPResponseType m_responseType; - std::multimap m_responseHeaderFields; + HTTPRequest m_request; + HTTPResponseDetails m_response; std::map m_postFields; + +private: + bool m_ranged; }; diff --git a/xbmc/network/httprequesthandler/Makefile b/xbmc/network/httprequesthandler/Makefile index aae8aa5910f3b..4fd8513966449 100644 --- a/xbmc/network/httprequesthandler/Makefile +++ b/xbmc/network/httprequesthandler/Makefile @@ -1,4 +1,5 @@ -SRCS=HTTPImageHandler.cpp \ +SRCS=HTTPFileHandler.cpp \ + HTTPImageHandler.cpp \ HTTPJsonRpcHandler.cpp \ HTTPVfsHandler.cpp \ HTTPWebinterfaceAddonsHandler.cpp \