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