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); }
bool HTTPRequestHandlerUtils::GetRequestedRanges(struct MHD_Connection *connection, uint64_t totalLength, CHttpRanges &ranges) { ranges.Clear(); if (connection == nullptr) return false; return ranges.Parse(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE), totalLength); }
TEST_F(TestWebServer, CanGetRangedFileRange0_2xEnd) { const std::string rangedFileContent = TEST_FILES_DATA_RANGES; const std::string range = GenerateRangeHeaderValue(0, rangedFileContent.size() * 2); CHttpRanges ranges; ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size())); // get the whole file but specify a larger range std::string result; CCurlFile curl; curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range); ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result)); CheckRangesTestFileResponse(curl, result, ranges); }
TEST_F(TestWebServer, CanGetRangedFileRange0_) { const std::string rangedFileContent = TEST_FILES_DATA_RANGES; const std::string range = "bytes=0-"; CHttpRanges ranges; ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size())); // get the whole file but specify the beginning of the range std::string result; CCurlFile curl; curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range); ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result)); CheckRangesTestFileResponse(curl, result, ranges); }
TEST_F(TestWebServer, CanGetRangedFileRange_Last) { const std::string rangedFileContent = TEST_FILES_DATA_RANGES; std::vector<std::string> rangedContent = StringUtils::Split(TEST_FILES_DATA_RANGES, ";"); const std::string range = StringUtils::Format("bytes=-%u", static_cast<unsigned int>(rangedContent.back().size())); CHttpRanges ranges; ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size())); // get the whole file but specify a larger range std::string result; CCurlFile curl; curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range); ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result)); CheckRangesTestFileResponse(curl, result, ranges); }
TEST_F(TestWebServer, CanGetRangedFileRangeFirst_Second) { const std::string rangedFileContent = TEST_FILES_DATA_RANGES; std::vector<std::string> rangedContent = StringUtils::Split(TEST_FILES_DATA_RANGES, ";"); const std::string range = GenerateRangeHeaderValue(rangedContent.front().size() + 1, rangedContent.front().size() + 1 + rangedContent.at(2).size() - 1); CHttpRanges ranges; ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size())); // get the whole file but specify a larger range std::string result; CCurlFile curl; curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range); ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result)); CheckRangesTestFileResponse(curl, result, ranges); }
TEST_F(TestWebServer, CanGetCachedRangedFileWithExactIfRange) { const std::string rangedFileContent = TEST_FILES_DATA_RANGES; const std::string range = "bytes=0-"; CHttpRanges ranges; ASSERT_TRUE(ranges.Parse(range, rangedFileContent.size())); // get the last modified date of the file CDateTime lastModified; ASSERT_TRUE(GetLastModifiedOfTestFile(TEST_FILES_RANGES, lastModified)); // get the whole file (but ranged) with an older If-Range value std::string result; CCurlFile curl; curl.SetRequestHeader(MHD_HTTP_HEADER_RANGE, range); curl.SetRequestHeader(MHD_HTTP_HEADER_IF_RANGE, lastModified.GetAsRFC1123DateTime()); ASSERT_TRUE(curl.Get(GetUrlOfTestFile(TEST_FILES_RANGES), result)); CheckRangesTestFileResponse(curl, result, ranges); }
bool CWebServer::IsRequestRanged(const HTTPRequest& request, const CDateTime &lastModified) const { // parse the Range header and store it in the request object CHttpRanges ranges; bool ranged = ranges.Parse(HTTPRequestHandlerUtils::GetRequestHeaderValue(request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE)); // handle If-Range header but only if the Range header is present if (ranged && lastModified.IsValid()) { std::string ifRange = HTTPRequestHandlerUtils::GetRequestHeaderValue(request.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(); } } return !ranges.IsEmpty(); }
void CheckRangesTestFileResponse(const CCurlFile& curl, const std::string& result, const CHttpRanges& ranges) { // get the HTTP header details const CHttpHeader& httpHeader = curl.GetHttpHeader(); // check the protocol line for the expected HTTP status std::string httpStatusString = StringUtils::Format(" %d ", MHD_HTTP_PARTIAL_CONTENT); std::string protocolLine = httpHeader.GetProtoLine(); ASSERT_TRUE(protocolLine.find(httpStatusString) != std::string::npos); // Accept-Ranges must be "bytes" EXPECT_STREQ("bytes", httpHeader.GetValue(MHD_HTTP_HEADER_ACCEPT_RANGES).c_str()); // check Last-Modified CDateTime lastModified; ASSERT_TRUE(GetLastModifiedOfTestFile(TEST_FILES_RANGES, lastModified)); ASSERT_STREQ(lastModified.GetAsRFC1123DateTime().c_str(), httpHeader.GetValue(MHD_HTTP_HEADER_LAST_MODIFIED).c_str()); // Cache-Control must contain "mag-age=0" and "no-cache" std::string cacheControl = httpHeader.GetValue(MHD_HTTP_HEADER_CACHE_CONTROL); EXPECT_TRUE(cacheControl.find("max-age=31536000") != std::string::npos); EXPECT_TRUE(cacheControl.find("public") != std::string::npos); // If there's no range Content-Length must be "20" if (ranges.IsEmpty()) { EXPECT_STREQ("20", httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str()); EXPECT_STREQ(TEST_FILES_DATA_RANGES, result.c_str()); return; } // check Content-Range uint64_t firstPosition, lastPosition; ASSERT_TRUE(ranges.GetFirstPosition(firstPosition)); ASSERT_TRUE(ranges.GetLastPosition(lastPosition)); EXPECT_STREQ(HttpRangeUtils::GenerateContentRangeHeaderValue(firstPosition, lastPosition, 20).c_str(), httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_RANGE).c_str()); std::string expectedContent = TEST_FILES_DATA_RANGES; const std::string expectedContentType = "text/plain"; if (ranges.Size() == 1) { // Content-Type must be "text/html" EXPECT_STREQ(expectedContentType.c_str(), httpHeader.GetMimeType().c_str()); // check the content CHttpRange firstRange; ASSERT_TRUE(ranges.GetFirst(firstRange)); expectedContent = expectedContent.substr(firstRange.GetFirstPosition(), firstRange.GetLength()); EXPECT_STREQ(expectedContent.c_str(), result.c_str()); // and Content-Length EXPECT_STREQ(StringUtils::Format("%u", static_cast<unsigned int>(expectedContent.size())).c_str(), httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str()); return; } // Content-Type contains the multipart boundary const std::string expectedMimeType = "multipart/byteranges"; std::string mimeType = httpHeader.GetMimeType(); ASSERT_STREQ(expectedMimeType.c_str(), mimeType.c_str()); std::string contentType = httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_TYPE); std::string contentTypeStart = expectedMimeType + "; boundary="; // it must start with "multipart/byteranges; boundary=" followed by the boundary ASSERT_EQ(0, contentType.find(contentTypeStart)); ASSERT_GT(contentType.size(), contentTypeStart.size()); // extract the boundary std::string multipartBoundary = contentType.substr(contentTypeStart.size()); ASSERT_FALSE(multipartBoundary.empty()); multipartBoundary = "--" + multipartBoundary; ASSERT_EQ(0, result.find(multipartBoundary)); std::vector<std::string> rangeParts = StringUtils::Split(result, multipartBoundary); // the first part is not really a part and is therefore empty (the place before the first boundary) ASSERT_TRUE(rangeParts.front().empty()); rangeParts.erase(rangeParts.begin()); // the last part is the end of the end multipart boundary ASSERT_STREQ("--", rangeParts.back().c_str()); rangeParts.erase(rangeParts.begin() + rangeParts.size() - 1); ASSERT_EQ(ranges.Size(), rangeParts.size()); for (size_t i = 0; i < rangeParts.size(); ++i) { std::string data = rangeParts.at(i); StringUtils::Trim(data, " \r\n"); // find the separator between header and data size_t pos = data.find("\r\n\r\n"); ASSERT_NE(std::string::npos, pos); std::string header = data.substr(0, pos + 4); data = data.substr(pos + 4); // get the expected range CHttpRange range; ASSERT_TRUE(ranges.Get(i, range)); // parse the header of the range part CHttpHeader rangeHeader; rangeHeader.Parse(header); // check Content-Type EXPECT_STREQ(expectedContentType.c_str(), rangeHeader.GetMimeType().c_str()); // parse and check Content-Range std::string contentRangeHeader = rangeHeader.GetValue(MHD_HTTP_HEADER_CONTENT_RANGE); std::vector<std::string> contentRangeHeaderParts = StringUtils::Split(contentRangeHeader, "/"); ASSERT_EQ(2, contentRangeHeaderParts.size()); // check the length of the range EXPECT_TRUE(StringUtils::IsNaturalNumber(contentRangeHeaderParts.back())); uint64_t contentRangeLength = str2uint64(contentRangeHeaderParts.back()); EXPECT_EQ(range.GetLength(), contentRangeLength); // remove the leading "bytes " string from the range definition std::string contentRangeDefinition = contentRangeHeaderParts.front(); ASSERT_EQ(0, contentRangeDefinition.find("bytes ")); contentRangeDefinition = contentRangeDefinition.substr(6); // check the start and end positions of the range std::vector<std::string> contentRangeParts = StringUtils::Split(contentRangeDefinition, "-"); ASSERT_EQ(2, contentRangeParts.size()); EXPECT_TRUE(StringUtils::IsNaturalNumber(contentRangeParts.front())); uint64_t contentRangeStart = str2uint64(contentRangeParts.front()); EXPECT_EQ(range.GetFirstPosition(), contentRangeStart); EXPECT_TRUE(StringUtils::IsNaturalNumber(contentRangeParts.back())); uint64_t contentRangeEnd = str2uint64(contentRangeParts.back()); EXPECT_EQ(range.GetLastPosition(), contentRangeEnd); // make sure the length of the content matches the one of the expected range EXPECT_EQ(range.GetLength(), data.size()); EXPECT_STREQ(expectedContent.substr(range.GetFirstPosition(), range.GetLength()).c_str(), data.c_str()); } }
TEST(TestHttpRanges, ParseUnorderedBackToBack) { const uint64_t totalLength = 5; const CHttpRange range0_0(0, 0); const CHttpRange range1_1(1, 1); const CHttpRange range0_1(0, 1); const CHttpRange range2_2(2, 2); const CHttpRange range0_2(0, 2); const CHttpRange range1_2(1, 2); const CHttpRange range3_3(3, 3); const CHttpRange range0_3(0, 3); const CHttpRange range4_4(4, 4); const CHttpRange range0_4(0, 4); const CHttpRange range3_4(3, 4); CHttpRange range; CHttpRanges ranges; EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,0-0", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_1, range); EXPECT_TRUE(ranges.Parse(RANGES_START "1-1,0-0,2-2", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_2, range); EXPECT_TRUE(ranges.Parse(RANGES_START "2-2,1-1,3-3,0-0", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_3, range); EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,1-1,0-0,2-2,3-3", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_4, range); EXPECT_TRUE(ranges.Parse(RANGES_START "3-3,0-0,4-4,1-1", totalLength)); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_1, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range3_4, range); EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,1-1,2-2", totalLength)); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range1_2, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range4_4, range); }
TEST(TestHttpRanges, ParseUnorderedNotOverlapping) { const uint64_t totalLength = 5; const CHttpRange range0_0(0, 0); const CHttpRange range0_1(0, 1); const CHttpRange range2_2(2, 2); const CHttpRange range2_(2, totalLength - 1); const CHttpRange range_1(totalLength - 1, totalLength - 1); CHttpRange range; CHttpRanges ranges; EXPECT_TRUE(ranges.Parse(RANGES_START "-1,0-0", totalLength)); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_0, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range_1, range); EXPECT_TRUE(ranges.Parse(RANGES_START "2-2,-1,0-0", totalLength)); EXPECT_EQ(3U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_0, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range2_2, range); EXPECT_TRUE(ranges.Get(2, range)); EXPECT_EQ(range_1, range); EXPECT_TRUE(ranges.Parse(RANGES_START "2-,0-0", totalLength)); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_0, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range2_, range); }
TEST(TestHttpRanges, ParseMulti) { const uint64_t totalLength = 6; const CHttpRange range0_0(0, 0); const CHttpRange range0_1(0, 1); const CHttpRange range1_3(1, 3); const CHttpRange range2_2(2, 2); const CHttpRange range4_5(4, 5); const CHttpRange range5_5(5, 5); CHttpRange range; CHttpRanges ranges; EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2", totalLength)); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_0, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range2_2, range); EXPECT_TRUE(ranges.Parse(RANGES_START "0-0,2-2,4-5", totalLength)); EXPECT_EQ(3U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_0, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range2_2, range); EXPECT_TRUE(ranges.Get(2, range)); EXPECT_EQ(range4_5, range); EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,5-5", totalLength)); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_1, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range5_5, range); EXPECT_TRUE(ranges.Parse(RANGES_START "1-3,5-5", totalLength)); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range1_3, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range5_5, range); }
TEST(TestHttpRanges, ParseSingle) { const uint64_t totalLength = 5; const CHttpRange range0_0(0, 0); const CHttpRange range0_1(0, 1); const CHttpRange range0_5(0, totalLength - 1); const CHttpRange range1_1(1, 1); const CHttpRange range1_3(1, 3); const CHttpRange range3_4(3, 4); const CHttpRange range4_4(4, 4); CHttpRange range; CHttpRanges ranges; EXPECT_TRUE(ranges.Parse(RANGES_START "0-0", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_0, range); EXPECT_TRUE(ranges.Parse(RANGES_START "0-1", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_1, range); EXPECT_TRUE(ranges.Parse(RANGES_START "0-5", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_5, range); EXPECT_TRUE(ranges.Parse(RANGES_START "1-1", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range1_1, range); EXPECT_TRUE(ranges.Parse(RANGES_START "1-3", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range1_3, range); EXPECT_TRUE(ranges.Parse(RANGES_START "3-4", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range3_4, range); EXPECT_TRUE(ranges.Parse(RANGES_START "4-4", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range4_4, range); }
TEST(TestHttpRanges, ParseInvalid) { CHttpRanges ranges; // combinations of invalid string and invalid total length EXPECT_FALSE(ranges.Parse("")); EXPECT_FALSE(ranges.Parse("", 0)); EXPECT_FALSE(ranges.Parse("", 1)); EXPECT_FALSE(ranges.Parse("test", 0)); EXPECT_FALSE(ranges.Parse(RANGES_START, 0)); // empty range definition EXPECT_FALSE(ranges.Parse(RANGES_START)); EXPECT_FALSE(ranges.Parse(RANGES_START "-")); // bad characters in range definition EXPECT_FALSE(ranges.Parse(RANGES_START "a")); EXPECT_FALSE(ranges.Parse(RANGES_START "1a")); EXPECT_FALSE(ranges.Parse(RANGES_START "1-a")); EXPECT_FALSE(ranges.Parse(RANGES_START "a-a")); EXPECT_FALSE(ranges.Parse(RANGES_START "a-1")); EXPECT_FALSE(ranges.Parse(RANGES_START "--")); EXPECT_FALSE(ranges.Parse(RANGES_START "1--")); EXPECT_FALSE(ranges.Parse(RANGES_START "1--2")); EXPECT_FALSE(ranges.Parse(RANGES_START "--2")); // combination of valid and empty range definitions EXPECT_FALSE(ranges.Parse(RANGES_START "0-1,")); EXPECT_FALSE(ranges.Parse(RANGES_START ",0-1")); // too big start position EXPECT_FALSE(ranges.Parse(RANGES_START "10-11", 5)); // end position smaller than start position EXPECT_FALSE(ranges.Parse(RANGES_START "1-0")); }
TEST(TestHttpRanges, Add) { CHttpRange range_0(0, 2); CHttpRange range_1(4, 6); CHttpRange range_2(8, 10); CHttpRanges ranges; CHttpRange range; ranges.Add(range_0); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.GetFirst(range)); EXPECT_EQ(range_0, range); EXPECT_TRUE(ranges.GetLast(range)); EXPECT_EQ(range_0, range); ranges.Add(range_1); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.GetFirst(range)); EXPECT_EQ(range_0, range); EXPECT_TRUE(ranges.GetLast(range)); EXPECT_EQ(range_1, range); ranges.Add(range_2); EXPECT_EQ(3U, ranges.Size()); EXPECT_TRUE(ranges.GetFirst(range)); EXPECT_EQ(range_0, range); EXPECT_TRUE(ranges.GetLast(range)); EXPECT_EQ(range_2, range); }
int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, unsigned int *upload_data_size, void **con_cls) #endif { if (cls == NULL || con_cls == NULL || *con_cls == NULL) { CLog::Log(LOGERROR, "CWebServer: invalid request received"); return MHD_NO; } CWebServer *server = reinterpret_cast<CWebServer*>(cls); std::auto_ptr<ConnectionHandler> conHandler(reinterpret_cast<ConnectionHandler*>(*con_cls)); HTTPMethod methodType = GetMethod(method); HTTPRequest request = { server, connection, conHandler->fullUri, url, methodType, version }; // remember if the request was new bool isNewRequest = conHandler->isNew; // because now it isn't anymore conHandler->isNew = false; // reset con_cls and set it if still necessary *con_cls = NULL; #ifdef WEBSERVER_DEBUG if (isNewRequest) { std::multimap<std::string, std::string> headerValues; GetRequestHeaderValues(connection, MHD_HEADER_KIND, headerValues); std::multimap<std::string, std::string> getValues; GetRequestHeaderValues(connection, MHD_GET_ARGUMENT_KIND, getValues); CLog::Log(LOGDEBUG, "webserver [IN] %s %s %s", version, method, request.pathUrlFull.c_str()); if (!getValues.empty()) { std::string tmp; for (std::multimap<std::string, std::string>::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<std::string, std::string>::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 if (isNewRequest) { // 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 (std::vector<IHTTPRequestHandler *>::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 IHTTPRequestHandler *handler = requestHandler->Create(request); // 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 std::string cacheControl = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CACHE_CONTROL); if (!cacheControl.empty()) { std::vector<std::string> cacheControls = StringUtils::Split(cacheControl, ","); for (std::vector<std::string>::const_iterator it = cacheControls.begin(); it != cacheControls.end(); ++it) { std::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) std::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 std::string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_MODIFIED_SINCE); std::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()) { std::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) { conHandler->requestHandler = handler; // get the content-type of the POST data std::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 (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.get()); // 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; return SendErrorResponse(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, methodType); } } } // otherwise we need to handle the POST data ourselves which is done in the next call to AnswerToConnection // as ownership of the connection handler is passed to libmicrohttpd we must not destroy it *con_cls = conHandler.release(); return MHD_YES; } return HandleRequest(handler); } } } // this is a subsequent call to AnswerToConnection for this request else { // again we need to take special care of the POST data if (methodType == POST) { 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 if (*upload_data_size > 0) { // 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 *upload_data_size = 0; // we may need to handle more POST data which is done in the next call to AnswerToConnection // as ownership of the connection handler is passed to libmicrohttpd we must not destroy it *con_cls = conHandler.release(); return MHD_YES; } // we have handled all POST data so it's time to invoke the IHTTPRequestHandler else { if (conHandler->postprocessor != NULL) MHD_destroy_post_processor(conHandler->postprocessor); return HandleRequest(conHandler->requestHandler); } } // it's unusual to get more than one call to AnswerToConnection for none-POST requests, but let's handle it anyway else { for (std::vector<IHTTPRequestHandler *>::const_iterator it = m_requestHandlers.begin(); it != m_requestHandlers.end(); ++it) { IHTTPRequestHandler *requestHandler = *it; if (requestHandler->CanHandleRequest(request)) return HandleRequest(requestHandler->Create(request)); } } } CLog::Log(LOGERROR, "CWebServer: couldn't find any request handler for %s", url); return SendErrorResponse(connection, MHD_HTTP_NOT_FOUND, methodType); }
TEST(TestHttpRanges, ParseUnorderedOverlapping) { const uint64_t totalLength = 5; const CHttpRange range0_0(0, 0); const CHttpRange range0_1(0, 1); const CHttpRange range0_2(0, 2); const CHttpRange range0_3(0, 3); const CHttpRange range0_4(0, 4); const CHttpRange range2_4(2, 4); CHttpRange range; CHttpRanges ranges; EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,0-0", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_1, range); EXPECT_TRUE(ranges.Parse(RANGES_START "0-2,0-0,0-1", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_2, range); EXPECT_TRUE(ranges.Parse(RANGES_START "0-1,1-2,0-0", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_2, range); EXPECT_TRUE(ranges.Parse(RANGES_START "0-2,0-0,1-3", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_3, range); EXPECT_TRUE(ranges.Parse(RANGES_START "2-3,1-2,0-1,3-4", totalLength)); EXPECT_EQ(1U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_4, range); EXPECT_TRUE(ranges.Parse(RANGES_START "4-4,0-0,2-4,2-3", totalLength)); EXPECT_EQ(2U, ranges.Size()); EXPECT_TRUE(ranges.Get(0, range)); EXPECT_EQ(range0_0, range); EXPECT_TRUE(ranges.Get(1, range)); EXPECT_EQ(range2_4, range); }