refactor: network - harden TCP/IP and unify HTTP clients
This commit is contained in:
@@ -1272,197 +1272,17 @@ inline uint32_t next_request_id() {
|
||||
return request_id++;
|
||||
}
|
||||
|
||||
inline int send_all_plain(int fd, const uint8_t* data, int len) {
|
||||
static constexpr int MAX_SEND_CHUNK = 32768;
|
||||
uint64_t deadline = montauk::get_milliseconds() + 15000;
|
||||
int off = 0;
|
||||
while (off < len) {
|
||||
int chunk = len - off;
|
||||
if (chunk > MAX_SEND_CHUNK) chunk = MAX_SEND_CHUNK;
|
||||
|
||||
int n = montauk::send(fd, data + off, (uint32_t)chunk);
|
||||
if (n > 0) {
|
||||
off += n;
|
||||
deadline = montauk::get_milliseconds() + 15000;
|
||||
continue;
|
||||
}
|
||||
if (n < 0) return -1;
|
||||
|
||||
uint32_t sig = montauk::wait_handle(fd,
|
||||
montauk::abi::IPC_SIGNAL_WRITABLE | montauk::abi::IPC_SIGNAL_PEER_CLOSED,
|
||||
1000);
|
||||
if (sig & montauk::abi::IPC_SIGNAL_PEER_CLOSED) return -1;
|
||||
if (montauk::get_milliseconds() >= deadline) return -1;
|
||||
montauk::sleep_ms(1);
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
inline bool response_has_no_body(int status) {
|
||||
return (status >= 100 && status < 200) || status == 204 || status == 304;
|
||||
}
|
||||
|
||||
inline int parse_content_length_value(const http::Response* resp) {
|
||||
char value[32] = {};
|
||||
if (!http::get_header(resp, "Content-Length", value, sizeof(value))) return -1;
|
||||
char* end = nullptr;
|
||||
long n = strtol(value, &end, 10);
|
||||
if (end == value || n < 0) return -1;
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
inline bool chunked_body_complete(const char* src, int src_len) {
|
||||
if (src == nullptr || src_len <= 0) return false;
|
||||
|
||||
int pos = 0;
|
||||
while (pos < src_len) {
|
||||
int line_start = pos;
|
||||
while (pos < src_len && src[pos] != '\n') pos++;
|
||||
if (pos >= src_len) return false;
|
||||
|
||||
int line_end = pos;
|
||||
pos++;
|
||||
while (line_end > line_start && (src[line_end - 1] == '\r' || src[line_end - 1] == '\n'))
|
||||
line_end--;
|
||||
|
||||
char hex[16] = {};
|
||||
int hex_pos = 0;
|
||||
for (int i = line_start; i < line_end && hex_pos < (int)sizeof(hex) - 1; i++) {
|
||||
if (src[i] == ';') break;
|
||||
hex[hex_pos++] = src[i];
|
||||
}
|
||||
if (hex_pos == 0) return false;
|
||||
|
||||
char* end = nullptr;
|
||||
unsigned long chunk = strtoul(hex, &end, 16);
|
||||
if (end == hex) return false;
|
||||
|
||||
if (chunk == 0) {
|
||||
if (pos >= src_len) return false;
|
||||
if (src[pos] == '\n') return true;
|
||||
if (src[pos] == '\r' && pos + 1 < src_len && src[pos + 1] == '\n') return true;
|
||||
return http::find_header_block_end(src + pos, src + src_len) != nullptr;
|
||||
}
|
||||
|
||||
if (pos + (int)chunk > src_len) return false;
|
||||
pos += (int)chunk;
|
||||
if (pos < src_len && src[pos] == '\r') pos++;
|
||||
if (pos < src_len && src[pos] == '\n') pos++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool response_is_chunked(const http::Response* resp);
|
||||
|
||||
inline bool http_response_complete(char* buf, int len, bool peer_closed) {
|
||||
if (buf == nullptr || len <= 0) return false;
|
||||
|
||||
http::Response resp = {};
|
||||
if (http::parse_response(buf, len, &resp) < 0) return false;
|
||||
if (resp.body == nullptr) return false;
|
||||
if (response_has_no_body(resp.status)) return true;
|
||||
if (response_is_chunked(&resp)) return chunked_body_complete(resp.body, resp.body_len);
|
||||
|
||||
int content_length = parse_content_length_value(&resp);
|
||||
if (content_length >= 0) return resp.body_len >= content_length;
|
||||
return peer_closed;
|
||||
}
|
||||
|
||||
inline int recv_http_plain(int fd, char* buf, int cap) {
|
||||
int total = 0;
|
||||
bool peer_closed = false;
|
||||
uint64_t deadline = montauk::get_milliseconds() + 90000;
|
||||
|
||||
while (total < cap - 1) {
|
||||
if (http_response_complete(buf, total, peer_closed)) break;
|
||||
if (montauk::get_milliseconds() >= deadline) break;
|
||||
|
||||
uint32_t sig = montauk::wait_handle(fd,
|
||||
montauk::abi::IPC_SIGNAL_READABLE | montauk::abi::IPC_SIGNAL_PEER_CLOSED,
|
||||
1000);
|
||||
if (sig == 0) continue;
|
||||
|
||||
if (sig & montauk::abi::IPC_SIGNAL_PEER_CLOSED)
|
||||
peer_closed = true;
|
||||
if (!(sig & montauk::abi::IPC_SIGNAL_READABLE)) {
|
||||
if (peer_closed) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
int n = montauk::recv(fd, buf + total, (uint32_t)(cap - 1 - total));
|
||||
if (n < 0) break;
|
||||
if (n == 0) {
|
||||
if (peer_closed) break;
|
||||
continue;
|
||||
}
|
||||
total += n;
|
||||
deadline = montauk::get_milliseconds() + 90000;
|
||||
}
|
||||
buf[total] = '\0';
|
||||
return total;
|
||||
}
|
||||
|
||||
inline bool response_is_chunked(const http::Response* resp) {
|
||||
char value[64] = {};
|
||||
if (!http::get_header(resp, "Transfer-Encoding", value, sizeof(value))) return false;
|
||||
for (int i = 0; value[i]; i++) {
|
||||
if (value[i] >= 'A' && value[i] <= 'Z') value[i] = (char)(value[i] - 'A' + 'a');
|
||||
}
|
||||
return strstr(value, "chunked") != nullptr;
|
||||
}
|
||||
|
||||
inline bool extract_http_body(const http::Response* resp, uint8_t** out_body, int* out_len) {
|
||||
if (out_body) *out_body = nullptr;
|
||||
if (out_len) *out_len = 0;
|
||||
if (resp == nullptr || resp->body == nullptr || resp->body_len < 0) return false;
|
||||
|
||||
if (!response_is_chunked(resp)) {
|
||||
uint8_t* body = (uint8_t*)malloc((size_t)(resp->body_len > 0 ? resp->body_len : 1));
|
||||
if (!body) return false;
|
||||
if (resp->body_len > 0) memcpy(body, resp->body, (size_t)resp->body_len);
|
||||
if (out_body) *out_body = body;
|
||||
if (out_len) *out_len = resp->body_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* src = resp->body;
|
||||
int src_len = resp->body_len;
|
||||
int pos = 0;
|
||||
int out_pos = 0;
|
||||
uint8_t* body = (uint8_t*)malloc((size_t)src_len);
|
||||
uint8_t* body = (uint8_t*)malloc((size_t)(resp->body_len > 0 ? resp->body_len : 1));
|
||||
if (!body) return false;
|
||||
|
||||
while (pos < src_len) {
|
||||
int line_start = pos;
|
||||
while (pos < src_len && src[pos] != '\n') pos++;
|
||||
int line_end = pos;
|
||||
if (pos < src_len && src[pos] == '\n') pos++;
|
||||
while (line_end > line_start && (src[line_end - 1] == '\r' || src[line_end - 1] == '\n'))
|
||||
line_end--;
|
||||
|
||||
char hex[16] = {};
|
||||
int hex_pos = 0;
|
||||
for (int i = line_start; i < line_end && hex_pos < (int)sizeof(hex) - 1; i++) {
|
||||
if (src[i] == ';') break;
|
||||
hex[hex_pos++] = src[i];
|
||||
}
|
||||
unsigned long chunk = strtoul(hex, nullptr, 16);
|
||||
if (chunk == 0) {
|
||||
if (out_body) *out_body = body;
|
||||
if (out_len) *out_len = out_pos;
|
||||
return true;
|
||||
}
|
||||
if (pos + (int)chunk > src_len) break;
|
||||
memcpy(body + out_pos, src + pos, chunk);
|
||||
out_pos += (int)chunk;
|
||||
pos += (int)chunk;
|
||||
if (pos < src_len && src[pos] == '\r') pos++;
|
||||
if (pos < src_len && src[pos] == '\n') pos++;
|
||||
}
|
||||
|
||||
free(body);
|
||||
return false;
|
||||
if (resp->body_len > 0) memcpy(body, resp->body, (size_t)resp->body_len);
|
||||
if (out_body) *out_body = body;
|
||||
if (out_len) *out_len = resp->body_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ipp_http_post(const IppUri* uri,
|
||||
@@ -1487,86 +1307,39 @@ inline bool ipp_http_post(const IppUri* uri,
|
||||
else
|
||||
snprintf(host_header, sizeof(host_header), "%s:%u", uri->host, (unsigned)uri->port);
|
||||
|
||||
int req_cap = body_len + 1024;
|
||||
char* req = (char*)malloc((size_t)req_cap);
|
||||
if (!req) {
|
||||
safe_copy(err, err_len, "out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
int req_len = snprintf(req, (size_t)req_cap,
|
||||
"POST %s HTTP/1.1\r\n"
|
||||
"Host: %s\r\n"
|
||||
"User-Agent: MontaukOS Print/1.0\r\n"
|
||||
"Content-Type: application/ipp\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n",
|
||||
uri->path, host_header, body_len);
|
||||
if (req_len < 0 || req_len + body_len >= req_cap) {
|
||||
free(req);
|
||||
safe_copy(err, err_len, "IPP request is too large");
|
||||
return false;
|
||||
}
|
||||
memcpy(req + req_len, body, (size_t)body_len);
|
||||
req_len += body_len;
|
||||
|
||||
char* raw = (char*)malloc(HTTP_RESPONSE_MAX);
|
||||
if (!raw) {
|
||||
free(req);
|
||||
safe_copy(err, err_len, "out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
int raw_len = -1;
|
||||
tls::TrustAnchors tas = {};
|
||||
if (uri->use_tls) {
|
||||
tls::TrustAnchors tas = tls::load_trust_anchors();
|
||||
raw_len = tls::https_fetch(uri->host, uri->ip, uri->port, req, req_len,
|
||||
tas, raw, HTTP_RESPONSE_MAX - 1);
|
||||
if (tas.anchors) free(tas.anchors);
|
||||
} else {
|
||||
int fd = montauk::socket(montauk::abi::SOCK_TCP);
|
||||
if (fd < 0) {
|
||||
tas = tls::load_trust_anchors();
|
||||
if (tas.count == 0) {
|
||||
free(raw);
|
||||
free(req);
|
||||
snprintf(err, (size_t)err_len, "failed to create socket for %s:%u", uri->host, (unsigned)uri->port);
|
||||
safe_copy(err, err_len, "no CA certificates loaded");
|
||||
return false;
|
||||
}
|
||||
if (montauk::connect(fd, uri->ip, uri->port) < 0) {
|
||||
montauk::closesocket(fd);
|
||||
free(raw);
|
||||
free(req);
|
||||
snprintf(err, (size_t)err_len, "failed to connect to %s (%s):%u",
|
||||
uri->host, ip_text, (unsigned)uri->port);
|
||||
return false;
|
||||
}
|
||||
if (send_all_plain(fd, (const uint8_t*)req, req_len) < 0) {
|
||||
montauk::closesocket(fd);
|
||||
free(raw);
|
||||
free(req);
|
||||
snprintf(err, (size_t)err_len, "failed to send print request to %s (%s):%u",
|
||||
uri->host, ip_text, (unsigned)uri->port);
|
||||
return false;
|
||||
}
|
||||
raw_len = recv_http_plain(fd, raw, HTTP_RESPONSE_MAX);
|
||||
montauk::closesocket(fd);
|
||||
}
|
||||
|
||||
free(req);
|
||||
|
||||
if (raw_len <= 0) {
|
||||
http::RequestOptions options;
|
||||
options.secure = uri->use_tls;
|
||||
options.port = uri->port;
|
||||
options.resolved_ip = uri->ip;
|
||||
options.host_header = host_header;
|
||||
options.extra_headers = "User-Agent: MontaukOS Print/1.0\r\n";
|
||||
options.timeout_ms = 90000;
|
||||
http::Response resp = http::request_into(
|
||||
"POST", uri->host, uri->path, "application/ipp",
|
||||
(const char*)body, body_len, uri->use_tls ? &tas : nullptr,
|
||||
raw, HTTP_RESPONSE_MAX, options);
|
||||
tls::free_trust_anchors(&tas);
|
||||
if (resp.error != http::Error::NONE) {
|
||||
free(raw);
|
||||
snprintf(err, (size_t)err_len, "printer returned no response from %s (%s):%u",
|
||||
uri->host, ip_text, (unsigned)uri->port);
|
||||
return false;
|
||||
}
|
||||
|
||||
raw[raw_len] = '\0';
|
||||
http::Response resp = {};
|
||||
if (http::parse_response(raw, raw_len, &resp) < 0) {
|
||||
free(raw);
|
||||
snprintf(err, (size_t)err_len, "printer returned no final HTTP response from %s (%s):%u",
|
||||
uri->host, ip_text, (unsigned)uri->port);
|
||||
snprintf(err, (size_t)err_len, "%s from %s (%s):%u",
|
||||
http::error_string(resp.error), uri->host, ip_text,
|
||||
(unsigned)uri->port);
|
||||
return false;
|
||||
}
|
||||
if (out_http_status) *out_http_status = resp.status;
|
||||
|
||||
Reference in New Issue
Block a user