Example #1
0
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();
}
Example #2
0
  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);
  }
Example #3
0
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};
}
Example #4
0
/* 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;
  }
}
Example #5
0
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);
}
Example #6
0
  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);
  }
Example #7
0
  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());
    }
  }