| 147 | } |
| 148 | } |
| 149 | |
| 150 | HttpRequest read_http_request(SocketHandle socket) { |
| 151 | std::string data; |
| 152 | std::array<char, 8192> buffer{}; |
| 153 | size_t header_end = std::string::npos; |
| 154 | while (header_end == std::string::npos) { |
| 155 | #ifdef _WIN32 |
| 156 | const int received = recv(socket, buffer.data(), static_cast<int>(buffer.size()), 0); |
| 157 | #else |
| 158 | const ssize_t received = recv(socket, buffer.data(), buffer.size(), 0); |
| 159 | #endif |
| 160 | if (received <= 0) { |
| 161 | throw std::runtime_error("socket receive failed before HTTP headers"); |
| 162 | } |
| 163 | data.append(buffer.data(), static_cast<size_t>(received)); |
| 164 | header_end = data.find("\r\n\r\n"); |
| 165 | if (data.size() > 1024 * 1024 && header_end == std::string::npos) { |
| 166 | throw std::runtime_error("HTTP headers exceed 1 MiB"); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | std::istringstream header_stream(data.substr(0, header_end)); |
| 171 | HttpRequest request; |
| 172 | std::string line; |
| 173 | if (!std::getline(header_stream, line)) { |
| 174 | throw std::runtime_error("empty HTTP request"); |
| 175 | } |
| 176 | if (!line.empty() && line.back() == '\r') { |
| 177 | line.pop_back(); |
| 178 | } |
| 179 | std::istringstream request_line(line); |
| 180 | request_line >> request.method >> request.path; |
| 181 | if (request.method.empty() || request.path.empty()) { |
| 182 | throw std::runtime_error("invalid HTTP request line"); |
| 183 | } |
| 184 | const auto query = request.path.find('?'); |
| 185 | if (query != std::string::npos) { |
| 186 | request.query = request.path.substr(query + 1); |
| 187 | request.path = request.path.substr(0, query); |
| 188 | } |
| 189 | |
| 190 | while (std::getline(header_stream, line)) { |
| 191 | if (!line.empty() && line.back() == '\r') { |
| 192 | line.pop_back(); |
| 193 | } |
| 194 | const auto pos = line.find(':'); |
| 195 | if (pos == std::string::npos) { |
| 196 | continue; |
| 197 | } |
| 198 | request.headers[lower_ascii(trim(line.substr(0, pos)))] = trim(line.substr(pos + 1)); |
| 199 | } |
| 200 | |
| 201 | size_t content_length = 0; |
| 202 | if (const auto it = request.headers.find("content-length"); it != request.headers.end()) { |
| 203 | content_length = static_cast<size_t>(std::stoull(it->second)); |
| 204 | } |
| 205 | request.body = data.substr(header_end + 4); |
| 206 | while (request.body.size() < content_length) { |
no test coverage detected