EV-ClickHouse
view release on metacpan or search on metacpan
xs/proto_http.c view on Meta::CPAN
const char *p = buf;
const char *end = buf + len;
int status;
/* skip "HTTP/1.x " */
while (p < end && *p != ' ') p++;
if (p >= end) return 0;
p++;
status = atoi(p);
if (status < 100 || status > 599) return 500; /* treat malformed as server error */
return status;
}
/* Find header value (case-insensitive). Returns pointer into buf or NULL. */
static const char* find_header(const char *headers, size_t headers_len,
const char *name, size_t *value_len) {
size_t name_len = strlen(name);
const char *p = headers;
const char *end = headers + headers_len;
while (p < end) {
const char *line_end = p;
while (line_end < end && *line_end != '\r') line_end++;
if ((size_t)(line_end - p) > name_len + 1 && p[name_len] == ':') {
int match = 1;
size_t i;
for (i = 0; i < name_len; i++) {
if (tolower((unsigned char)p[i]) != tolower((unsigned char)name[i])) {
match = 0;
break;
}
}
if (match) {
const char *val = p + name_len + 1;
while (val < line_end && *val == ' ') val++;
*value_len = line_end - val;
return val;
}
}
/* advance past \r\n */
if (line_end + 2 <= end) p = line_end + 2;
else break;
}
return NULL;
}
/* Parse a complete HTTP response from recv_buf. */
static void process_http_response(ev_clickhouse_t *self) {
size_t hdr_end;
int status;
const char *val;
size_t val_len;
size_t content_length = 0;
int chunked = 0;
int is_gzip = 0;
const char *body;
size_t body_len;
char *decoded = NULL;
size_t decoded_len = 0;
size_t decoded_cap = 0;
if (self->recv_len == 0 || self->send_count == 0) return;
/* find headers end */
hdr_end = find_header_end(self->recv_buf, self->recv_len);
if (hdr_end == 0) return; /* need more data */
/* parse status */
status = parse_http_status(self->recv_buf, hdr_end);
/* parse Content-Length */
val = find_header(self->recv_buf, hdr_end, "Content-Length", &val_len);
if (val) {
content_length = (size_t)strtoul(val, NULL, 10);
}
/* check Transfer-Encoding: chunked */
val = find_header(self->recv_buf, hdr_end, "Transfer-Encoding", &val_len);
if (val && val_len >= 7 && strncasecmp(val, "chunked", 7) == 0) {
chunked = 1;
}
/* check Content-Encoding: gzip */
val = find_header(self->recv_buf, hdr_end, "Content-Encoding", &val_len);
if (val && val_len >= 4 && strncasecmp(val, "gzip", 4) == 0) {
is_gzip = 1;
}
/* parse X-ClickHouse-Summary: {"read_rows":"N","read_bytes":"N",...}
* to populate profile_rows/profile_bytes for HTTP, mirroring the
* native protocol's SERVER_PROFILE_INFO packet. */
val = find_header(self->recv_buf, hdr_end, "X-ClickHouse-Summary", &val_len);
if (val && val_len > 12) {
size_t i;
for (i = 0; i + 12 < val_len; i++) {
if (val[i] != '"') continue;
/* val isn't NUL-terminated (it points into recv_buf), so use
* parse_uint_within which takes an explicit length cap rather
* than scanning until a non-digit/NUL like strtoull would. */
if (i + 13 < val_len
&& memcmp(val + i + 1, "read_rows\":\"", 12) == 0)
self->profile_rows = parse_uint_within(val + i + 13,
val_len - (i + 13));
else if (i + 14 < val_len
&& memcmp(val + i + 1, "read_bytes\":\"", 13) == 0)
self->profile_bytes = parse_uint_within(val + i + 14,
val_len - (i + 14));
}
}
size_t consumed;
if (chunked) {
/* decode chunked transfer encoding */
const char *cp = self->recv_buf + hdr_end;
const char *cp_end = self->recv_buf + self->recv_len;
{
int chunked_complete = 0;
while (cp < cp_end) {
/* read chunk size */
const char *nl = cp;
unsigned long chunk_size;
while (nl < cp_end && *nl != '\r') nl++;
if (nl + 2 > cp_end) goto need_more; /* need more data */
chunk_size = strtoul(cp, NULL, 16);
cp = nl + 2; /* skip \r\n */
if (chunk_size == 0) {
/* terminal chunk; skip trailing \r\n */
if (cp + 2 > cp_end) goto need_more;
cp += 2;
chunked_complete = 1;
break;
}
if ((size_t)(cp_end - cp) < 2
|| chunk_size > (size_t)(cp_end - cp) - 2) goto need_more;
/* guard against overflow and unbounded growth â
* close connection since remaining chunks would
* corrupt the stream for subsequent requests.
* Apply both the hard ceiling and the user's
* opt-in max_recv_buffer (when set) to the
* post-decode body size. */
size_t cap = CH_MAX_DECOMPRESS_SIZE;
if (self->max_recv_buffer > 0
&& self->max_recv_buffer < cap)
cap = self->max_recv_buffer;
if (decoded_len + chunk_size < decoded_len
|| decoded_len + chunk_size > cap) {
if (decoded) Safefree(decoded);
self->send_count--;
teardown_after_deliver(self,
"chunked response too large", "connection closed");
return;
}
if (decoded == NULL) {
decoded_cap = chunk_size + 256;
Newx(decoded, decoded_cap, char);
} else if (decoded_len + chunk_size > decoded_cap) {
decoded_cap = (decoded_len + chunk_size) * 2;
Renew(decoded, decoded_cap, char);
}
Copy(cp, decoded + decoded_len, chunk_size, char);
decoded_len += chunk_size;
cp += chunk_size + 2; /* skip chunk data + \r\n */
}
if (!chunked_complete) goto need_more;
}
body = decoded;
body_len = decoded_len;
consumed = cp - self->recv_buf;
} else {
/* Content-Length based â bound content_length before any pointer
* arithmetic so a malicious huge value cannot overflow
* hdr_end + content_length and slip past the need-more check. */
size_t cl_cap = CH_MAX_DECOMPRESS_SIZE;
if (self->max_recv_buffer > 0 && self->max_recv_buffer < cl_cap)
cl_cap = self->max_recv_buffer;
if (content_length > cl_cap) {
self->send_count--;
teardown_after_deliver(self,
"Content-Length exceeds limit", "connection closed");
return;
}
if (self->recv_len < hdr_end + content_length) return; /* need more data */
body = self->recv_buf + hdr_end;
body_len = content_length;
consumed = hdr_end + content_length;
}
/* deliver response (body, body_len, consumed are set; decoded may be NULL) */
self->send_count--;
if (status == 200) {
char *final_body = (char *)body;
size_t final_len = body_len;
if (is_gzip && body_len > 0) {
size_t dec_len;
char *dec = gzip_decompress(body, body_len, &dec_len);
if (!dec) {
if (decoded) Safefree(decoded);
recv_consume(self, consumed);
int destroyed = deliver_error(self, "gzip decompression failed");
if (destroyed) return;
goto done;
}
/* Apply user's opt-in max_recv_buffer to the gzip-decoded
* body too â same trip-wire semantics as the chunked path
* and the on_readable raw-recv check. Tear the connection
* down on overflow so subsequent queries can't slip past
* the cap on the same socket (matches the chunked path's
* behaviour and the POD contract). */
if (self->max_recv_buffer > 0
&& dec_len > self->max_recv_buffer) {
Safefree(dec);
if (decoded) Safefree(decoded);
teardown_after_deliver(self,
"gzip body exceeds max_recv_buffer", "connection closed");
return;
}
final_body = dec;
final_len = dec_len;
}
{
int is_raw = peek_cb_raw(self);
int destroyed;
if (is_raw) {
/* raw mode â deliver body as scalar, skip TSV parsing */
destroyed = deliver_raw_body(self, final_body, final_len);
} else {
AV *rows = NULL;
if (final_len > 0)
rows = parse_tab_separated(final_body, final_len);
destroyed = deliver_rows(self, rows);
}
if (final_body != body) Safefree(final_body);
if (decoded) Safefree(decoded);
recv_consume(self, consumed);
if (destroyed) return;
}
} else {
/* error */
char *errmsg = format_http_error(self, status, body, body_len, is_gzip);
if (decoded) Safefree(decoded);
recv_consume(self, consumed);
int destroyed = deliver_error(self, errmsg);
Safefree(errmsg);
if (destroyed) return;
}
if (self->magic != EV_CH_MAGIC) return;
done:
/* Stop query timeout timer on response */
stop_timing(self);
pipeline_advance(self);
return;
need_more:
/* incomplete response â keep reading */
if (decoded) Safefree(decoded);
return;
}
( run in 0.491 second using v1.01-cache-2.11-cpan-f4a522933cf )