31{
32 struct addrinfo hints{};
33 hints.ai_family = AF_UNSPEC;
34 hints.ai_socktype = SOCK_STREAM;
35 struct addrinfo* res = nullptr;
36 if (getaddrinfo(host.c_str(), "80", &hints, &res) != 0 || !res) return {};
37
38 int fd = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
39 if (fd < 0) { freeaddrinfo(res); return {}; }
40
41
42 struct timeval tv{};
43 tv.tv_sec = timeout.count() / 1000;
44 tv.tv_usec = (timeout.count() % 1000) * 1000;
45 ::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
46 ::setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
47
48 if (::connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
49 ::close(fd); freeaddrinfo(res); return {};
50 }
51 freeaddrinfo(res);
52
53 std::string req = "GET " + path + " HTTP/1.0\r\n";
54 req += "Host: " + host + "\r\n";
55 req += "User-Agent: BastionGuard-SC/1.0\r\n";
56 req += "Connection: close\r\n\r\n";
57 if (::send(fd, req.data(), req.size(), 0) < 0) { ::close(fd); return {}; }
58
59 std::string response;
60 char buf[4096];
61 while (true) {
62 ssize_t n = ::recv(fd, buf, sizeof(buf), 0);
63 if (n <= 0) break;
64 response.append(buf, static_cast<size_t>(n));
65 if (response.size() > 64 * 1024) break;
66 }
67 ::close(fd);
68
69
70 const auto sep = response.find("\r\n\r\n");
71 if (sep == std::string::npos) return {};
72 return response.substr(sep + 4);
73}