• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Razakhel / RaZ / 22285681096

22 Feb 2026 09:26PM UTC coverage: 74.471% (+0.2%) from 74.25%
22285681096

push

github

Razakhel
[Network/HttpClient] Added proper support for chunked responses

18 of 20 new or added lines in 1 file covered. (90.0%)

8626 of 11583 relevant lines covered (74.47%)

1706.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.35
/src/RaZ/Network/HttpClient.cpp
1
#include "RaZ/Network/HttpClient.hpp"
2
#include "RaZ/Utils/Logger.hpp"
3

4
#include <algorithm>
5
#include <charconv>
6
#include <ranges>
7

8
namespace Raz {
9

10
namespace {
11

12
struct HttpResponse {
13
  unsigned short statusCode {};
14
  std::size_t contentLength {};
15
  bool isChunked = false;
16
  std::string location;
17
};
18

19
HttpResponse parseResponse(std::string_view response) {
1✔
20
  HttpResponse httpResponse {};
1✔
21

22
  // The last 4 characters (\r\n\r\n) are ignored to avoid getting two final empty lines
23
  for (const auto line : std::views::split(response.substr(0, response.size() - 4), std::string_view("\r\n"))) {
25✔
24
    const std::string_view header(line.data(), line.size());
12✔
25

26
    if (header.starts_with("HTTP"))
12✔
27
      std::from_chars(header.data() + 9, header.data() + 12, httpResponse.statusCode);
1✔
28
    else if (header.starts_with("Content-Length"))
11✔
29
      std::from_chars(header.data() + 15, header.data() + header.size(), httpResponse.contentLength);
×
30
    else if (header.starts_with("Transfer-Encoding") && header.find("chunked") != std::string::npos)
11✔
31
      httpResponse.isChunked = true;
1✔
32
    else if (header.starts_with("Location"))
10✔
33
      httpResponse.location = header.substr(10);
×
34
  }
35

36
  return httpResponse;
1✔
37
}
×
38

39
} // namespace
40

41
void HttpClient::connect(std::string host) {
4✔
42
  m_host = std::move(host);
4✔
43
  m_tcpClient.connect(m_host, 80);
4✔
44
}
2✔
45

46
std::string HttpClient::get(std::string_view resource) {
1✔
47
  Logger::debug("[HttpClient] Sending GET '{}'...", resource);
1✔
48

49
  const std::string request = std::format("GET {} HTTP/1.1\r\nHost: {}\r\n\r\n", resource, m_host);
1✔
50
  m_tcpClient.send(request);
1✔
51

52
  const std::string headers = m_tcpClient.receiveUntil("\r\n\r\n", true);
1✔
53
  const auto [statusCode, contentLength, isChunked, location] = parseResponse(headers);
1✔
54

55
  if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307 || statusCode == 308)
1✔
56
    return get(location); // TODO: redirections can happen indefinitely in some cases
×
57

58
  if (contentLength > 0)
1✔
59
    return m_tcpClient.receiveExactly(contentLength);
×
60

61
  if (isChunked)
1✔
62
    return receiveChunked();
1✔
63

64
  Logger::error("[HttpClient] Failed to get resource '{}': unsupported response method", resource);
×
65
  return {};
×
66
}
1✔
67

68
std::string HttpClient::receiveChunked() {
1✔
69
  const auto receiveCrlf = [this] () {
2✔
70
    if (const std::string chunkEnd = m_tcpClient.receiveExactly(2); chunkEnd != "\r\n")
2✔
71
      throw std::runtime_error(std::format("[HttpClient] Ill-formed end of chunk: expected CRLF ('\\r\\n'), got '{}'", chunkEnd));
2✔
72
  };
2✔
73

74
  std::string data;
1✔
75

76
  while (true) {
77
    const std::string chunkSizeStr = m_tcpClient.receiveUntil("\r\n");
2✔
78

79
    if (chunkSizeStr == "0\r\n") {
2✔
80
      receiveCrlf();
1✔
81
      break;
1✔
82
    }
83

84
    std::size_t chunkSize {};
1✔
85

86
    if (std::from_chars(chunkSizeStr.data(), chunkSizeStr.data() + chunkSizeStr.size() - 2, chunkSize, 16).ec != std::errc())
1✔
NEW
87
      throw std::runtime_error(std::format("[HttpClient] Failed to parse chunk size: '{}'", chunkSizeStr));
×
88

89
    data.append(m_tcpClient.receiveExactly(chunkSize));
1✔
90
    receiveCrlf();
1✔
91
  }
3✔
92

93
  return data;
2✔
NEW
94
}
×
95

96
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc