| 935 | } |
| 936 | |
| 937 | HTTPResponse HTTPClient::ReadResponse() |
| 938 | { |
| 939 | HTTPResponse response; |
| 940 | std::string buffer; |
| 941 | const auto deadline{std::chrono::steady_clock::now() + m_timeout}; |
| 942 | |
| 943 | // Read data until we have complete headers |
| 944 | size_t headers_end = 0; |
| 945 | |
| 946 | while (headers_end == 0) { |
| 947 | if (auto result{Recv(deadline)}) { |
| 948 | buffer.append(*result); |
| 949 | } else { |
| 950 | std::this_thread::yield(); |
| 951 | continue; |
| 952 | } |
| 953 | |
| 954 | // Check for header terminator |
| 955 | size_t pos = buffer.find("\r\n\r\n"); |
| 956 | if (pos != std::string::npos) { |
| 957 | headers_end = pos + 4; |
| 958 | } |
| 959 | } |
| 960 | |
| 961 | // Parse http status |
| 962 | util::LineReader reader(std::string_view{buffer.data(), headers_end}, headers_end); |
| 963 | auto status_line = reader.ReadLine(); |
| 964 | if (!status_line) { |
| 965 | throw HTTPError{"Failed to read status line"}; |
| 966 | } |
| 967 | |
| 968 | const std::string_view status_str = *status_line; |
| 969 | // Minimum status line is "HTTP/X.Y NNN" (e.g. "HTTP/1.1 200"), 12 characters. |
| 970 | if (status_str.size() < 12 || !status_str.starts_with("HTTP/")) { |
| 971 | throw HTTPError{"Invalid status line"}; |
| 972 | } |
| 973 | |
| 974 | size_t space1 = status_str.find(' '); |
| 975 | if (space1 == std::string::npos || space1 + 4 > status_str.size()) { |
| 976 | throw HTTPError{"Invalid status line format"}; |
| 977 | } |
| 978 | |
| 979 | const std::string_view status_code_str = status_str.substr(space1 + 1, 3); |
| 980 | auto status_code = ToIntegral<int>(status_code_str); |
| 981 | if (!status_code) { |
| 982 | throw HTTPError{"Invalid status code"}; |
| 983 | } |
| 984 | response.status = *status_code; |
| 985 | |
| 986 | HTTPResponseHeaders headers; |
| 987 | headers.Read(reader); |
| 988 | |
| 989 | // Determine body length |
| 990 | size_t content_length = 0; |
| 991 | bool chunked = false; |
| 992 | |
| 993 | // RFC 9112 §6.3 says responses with both Transfer-Encoding and Content-Length |
| 994 | // must be rejected. We are more lenient: Transfer-Encoding takes precedence |