TEST_F(TestWebServer, CanGetJsonRpcResponse) { // initialized JSON-RPC JSONRPC::CJSONRPC::Initialize(); std::string result; CCurlFile curl; curl.SetMimeType("application/json"); ASSERT_TRUE(curl.Post(GetUrl(TEST_URL_JSONRPC), "{ \"jsonrpc\": \"2.0\", \"method\": \"JSONRPC.Version\", \"id\": 1 }", result)); ASSERT_FALSE(result.empty()); // parse the JSON-RPC response CVariant resultObj = CJSONVariantParser::Parse(reinterpret_cast<const unsigned char*>(result.c_str()), result.size()); // make sure it's an object ASSERT_TRUE(resultObj.isObject()); // get the HTTP header details const CHttpHeader& httpHeader = curl.GetHttpHeader(); // Content-Type must be "application/json" EXPECT_STREQ("application/json", httpHeader.GetMimeType().c_str()); // Accept-Ranges must be "none" EXPECT_STREQ("none", httpHeader.GetValue(MHD_HTTP_HEADER_ACCEPT_RANGES).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=0") != std::string::npos); EXPECT_TRUE(cacheControl.find("no-cache") != std::string::npos); // uninitialize JSON-RPC JSONRPC::CJSONRPC::Cleanup(); }
void CheckRangesTestFileResponse(const CCurlFile& curl, int httpStatus = MHD_HTTP_OK, bool empty = false) { // 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 ", httpStatus); std::string protocolLine = httpHeader.GetProtoLine(); ASSERT_TRUE(protocolLine.find(httpStatusString) != std::string::npos); // Content-Type must be "text/html" EXPECT_STREQ("text/plain", httpHeader.GetMimeType().c_str()); // check Content-Length if (empty) EXPECT_STREQ("0", httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str()); else EXPECT_STREQ("20", httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str()); // 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); }
CRepository::ResolveResult CRepository::ResolvePathAndHash(const AddonPtr& addon) const { std::string const& path = addon->Path(); auto dirIt = std::find_if(m_dirs.begin(), m_dirs.end(), [&path](DirInfo const& dir) { return URIUtils::PathHasParent(path, dir.datadir, true); }); if (dirIt == m_dirs.end()) { CLog::Log(LOGERROR, "Requested path {} not found in known repository directories", path); return {}; } if (dirIt->hashType == CDigest::Type::INVALID) { // We have a path, but need no hash return {path, {}}; } // Do not follow mirror redirect, we want the headers of the redirect response CURL url{path}; url.SetProtocolOption("redirect-limit", "0"); CCurlFile file; if (!file.Open(url)) { CLog::Log(LOGERROR, "Could not fetch addon location and hash from {}", path); return {}; } std::string hashTypeStr = CDigest::TypeToString(dirIt->hashType); // Return the location from the header so we don't have to look it up again // (saves one request per addon install) std::string location = file.GetRedirectURL(); // content-* headers are base64, convert to base16 TypedDigest hash{dirIt->hashType, StringUtils::ToHexadecimal(Base64::Decode(file.GetHttpHeader().GetValue(std::string("content-") + hashTypeStr)))}; if (hash.Empty()) { // Expected hash, but none found -> fall back to old method if (!FetchChecksum(path + "." + hashTypeStr, hash.value) || hash.Empty()) { CLog::Log(LOGERROR, "Failed to find hash for {} from HTTP header and in separate file", path); return {}; } } if (location.empty()) { // Fall back to original URL if we do not get a redirect location = path; } CLog::Log(LOGDEBUG, "Resolved addon path {} to {} hash {}", path, location, hash.value); return {location, hash}; }
/* STATIC FUNCTIONS */ bool CCurlFile::GetHttpHeader(const CURL &url, CHttpHeader &headers) { try { CCurlFile file; if(file.Stat(url, NULL) == 0) { headers = file.GetHttpHeader(); return true; } return false; } catch(...) { CLog::Log(LOGERROR, "%s - Exception thrown while trying to retrieve header url: %s", __FUNCTION__, url.Get().c_str()); return false; } }
TEST_F(TestWebServer, CanGetJsonRpcApiDescription) { std::string result; CCurlFile curl; ASSERT_TRUE(curl.Get(GetUrl(TEST_URL_JSONRPC), result)); ASSERT_FALSE(result.empty()); // get the HTTP header details const CHttpHeader& httpHeader = curl.GetHttpHeader(); // Content-Type must be "application/json" EXPECT_STREQ("application/json", httpHeader.GetMimeType().c_str()); // Accept-Ranges must be "none" EXPECT_STREQ("none", httpHeader.GetValue(MHD_HTTP_HEADER_ACCEPT_RANGES).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=0") != std::string::npos); EXPECT_TRUE(cacheControl.find("no-cache") != std::string::npos); }
void CheckHtmlTestFileResponse(const CCurlFile& curl) { // get the HTTP header details const CHttpHeader& httpHeader = curl.GetHttpHeader(); // Content-Type must be "text/html" EXPECT_STREQ("text/html", httpHeader.GetMimeType().c_str()); // Content-Length must be "4" EXPECT_STREQ("4", httpHeader.GetValue(MHD_HTTP_HEADER_CONTENT_LENGTH).c_str()); // 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_HTML, 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=0") != std::string::npos); EXPECT_TRUE(cacheControl.find("no-cache") != std::string::npos); }
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()); } }