Here is a piece of code that writes an HTTP response on a socket based on a simple struct
void write_response(request *req, response *resp, int socket) {
char *raw_resp;
int bytes = 0;
asprintf(&raw_resp, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
bytes += strlen(raw_resp);
for (int i = 0; i < resp->header_count; i++) {
asprintf(&raw_resp, "%s%s", raw_resp, resp->headers[i]);
bytes += strlen(resp->headers[i]);
}
if (resp->content != NULL) {
asprintf(&raw_resp, "%s\r\n", raw_resp);
raw_resp = realloc(raw_resp, bytes + 2 + resp->content->size);
memcpy(&raw_resp[strlen(raw_resp)], resp->content->data,
resp->content->size);
bytes += (resp->content->size + 2);
}
write(socket, raw_resp, bytes);
free(raw_resp);
}
Basically, it adds the HTTP request line first, then the headers and finally if needed the body.
However, valgrind is reporting a Invalid free() / delete / delete[] / realloc()
and 18 bytes in 1 blocks are definitely lost in loss record 2 of 4
(memory leaks) on the first 2 asprintf but weirdly not on the 3rd one.
Am I using asprintf right?
Your code does not free
all the strings allocated by asprintf
. Given how you use asprintf
to perform dynamic string concatenation, fixing this problem is a bit cumbersome. Note that you do not handle memory allocation failure either. You could use asprintf
return value to detect that and update bytes
without the need for an extra strlen()
call.
/* return the number of bytes written or -1 in case of error */
int write_response(request *req, response *resp, int socket) {
char *raw_resp, *new_p;
int bytes;
bytes = asprintf(&raw_resp, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
if (bytes < 0)
return -1;
for (int i = 0; i < resp->header_count; i++) {
bytes = asprintf(&new_p, "%s%s", raw_resp, resp->headers[i]);
free(raw_resp);
raw_resp = newp;
if (bytes < 0)
return -1;
}
if (resp->content != NULL) {
bytes = asprintf(&new_p, "%s\r\n", raw_resp);
free(raw_resp);
raw_resp = newp;
if (bytes < 0)
return -1;
new_p = realloc(raw_resp, bytes + resp->content->size);
if (new_p == NULL) {
free(raw_resp);
return -1;
}
raw_resp = new_p;
memcpy(raw_resp + bytes, resp->content->data, resp->content->size);
bytes += resp->content->size;
}
bytes = write(socket, raw_resp, bytes);
free(raw_resp);
return bytes;
}
Remarks:
Using asprintf
to perform string concatenation with allocation seems inefficient, simply use strlen
, realloc
and memcpy
.
asprintf()
is non-standard, it is not be available on all platforms.
Unless you are requested to issue a single call to write
, it might be more efficient to write the contents separately to avoid the extra call to realloc()
for a potentially large amount of memory.
It might also be more efficient to compute the length of the headers in a initial phase with snprintf
and strlen
and allocate the space for the headers directly to the full size, or not even allocate if below a reasonable threshold (4K) using a local array.
Here is a modified version:
int write_response(request *req, response *resp, int socket) {
char buffer[4096];
char *raw_resp, *allocated = NULL;
int bytes, pos;
bytes = snprintf(NULL, 0, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
for (int i = 0; i < resp->header_count; i++)
bytes += strlen(resp->headers[i]);
if (resp->content != NULL)
bytes += 2 + resp->content->size;
/* need an extra byte for `snprintf` null terminator
if no headers and no contents */
if (bytes < sizeof(buffer)) {
raw_resp = buffer;
} else {
raw_resp = allocated = malloc(bytes + 1):
if (raw_resp == NULL)
return -1;
}
pos = snprintf(raw_resp, bytes, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
for (int i = 0; i < resp->header_count; i++) {
int len = strlen(resp->headers[i]);
memcpy(raw_resp + pos, resp->headers[i], len);
pos += len;
}
if (resp->content != NULL) {
raw_resp[pos++] = '\r';
raw_resp[pos++] = '\n';
memcpy(raw_resp + pos, resp->content->data, resp->content->size);
pos += resp->content->size;
}
bytes = write(socket, raw_resp, bytes);
free(allocated);
return bytes;
}