What to do when http header wrongly reports content-length - c

I am trying to download web pages over https by first downloading the headers with a HEAD request, then parsing to obtain the Content-Length and then using the Content-Length plus some space for headers to allocate memory for a buffer to store results from a GET request. It seems that stackoverflow.com gives a Content-Length that is too small and thus my code segfaults.
I've tried looking through stack overflow past questions to see how to go about dynamically allocating memory to handle pages which misreport their Content-Length but haven't been able to find any suitable answers.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define MAX_HEADER_SIZE 8192
/**
* Main SSL demonstration code entry point
*/
int main() {
char* host_and_port = "stackoverflow.com:443";
char* head_request = "HEAD / HTTP/1.1\r\nHost: stackoverflow.com\r\n\r\n";
char* get_request = "GET / HTTP/1.1\r\nHost: stackoverflow.com\r\n\r\n";
char* store_path = "mycert.pem";
char *header_token, *line_token, content_length_line[1024];
char *cmp = "\r\n";
char *html;
char *get;
int content_length;
size_t i = 0;
char buffer[MAX_HEADER_SIZE];
buffer[0] = 0;
BIO* bio;
SSL_CTX* ctx = NULL;
SSL* ssl = NULL;
/* initilise the OpenSSL library */
SSL_load_error_strings();
SSL_library_init();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
bio = NULL;
int r = 0;
/* Set up the SSL pointers */
ctx = SSL_CTX_new(TLS_client_method());
ssl = NULL;
r = SSL_CTX_load_verify_locations(ctx, store_path, NULL);
if (r == 0) {
fprintf(stdout,"Unable to load the trust store from %s.\n", store_path);
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
}
/* Setting up the BIO SSL object */
bio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(bio, &ssl);
if (!(ssl)) {
printf("Unable to allocate SSL pointer.\n");
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
bio = NULL;
}
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
/* Attempt to connect */
BIO_set_conn_hostname(bio, host_and_port);
/* Verify the connection opened and perform the handshake */
if (BIO_do_connect(bio) < 1) {
fprintf(stdout, "Unable to connect BIO.%s\n", host_and_port);
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
bio = NULL;
}
if (SSL_get_verify_result(ssl) != X509_V_OK) {
printf("Unable to verify connection result.\n");
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
}
if (bio == NULL)
return (EXIT_FAILURE);
r = -1;
while (r < 0) {
r = BIO_write(bio, head_request, strlen(head_request));
if (r <= 0) {
if (!BIO_should_retry(bio)) {
printf("BIO_read should retry test failed.\n");
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
continue;
}
/* It would be prudent to check the reason for the retry and handle
* it appropriately here */
}
}
r = -1;
while (r < 0) {
r = BIO_read(bio, buffer, MAX_HEADER_SIZE);
if (r == 0) {
printf("Reached the end of the data stream.\n");
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
continue;
} else if (r < 0) {
if (!BIO_should_retry(bio)) {
printf("BIO_read should retry test failed.\n");
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
continue;
}
/* It would be prudent to check the reason for the retry and handle
* it appropriately here */
}
};
printf("%s\r\n", buffer);
header_token = strtok(buffer, cmp);
while (header_token != NULL)
{
//printf ("header_token: %s\n\n", header_token);
if (strncmp(header_token, "Content-Length:", strlen("Content-Length:")) == 0
|| strncmp(header_token, "content-length:", strlen("content-length:")) == 0)
{
//printf ("header_token %s is equal to Content-Length:\n", header_token);
strcpy(content_length_line, header_token);
}
header_token = strtok(NULL, cmp);
}
if (strlen(content_length_line) > 0)
{
line_token = strtok(content_length_line, " ");
line_token = strtok(NULL, " ");
content_length = atoi(line_token);
printf ("Content-Length = %d\n", content_length);
}
//char get[content_length + MAX_HEADER_SIZE];
get = malloc((content_length + MAX_HEADER_SIZE)*sizeof(char));
if (get == NULL) {
fprintf(stdout, "Out of memory\n");
return (EXIT_FAILURE);
}
r = -1;
while (r < 0) {
r = BIO_write(bio, get_request, strlen(get_request));
if (r <= 0) {
if (!BIO_should_retry(bio)) {
printf("BIO_read should retry test failed.\n");
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
continue;
}
/* It would be prudent to check the reason for the retry and handle
* it appropriately here */
}
}
r = -1;
while (r) {
while (r < 0) {
r = BIO_read(bio, buffer, 4096);
if (r == 0) {
printf("Reached the end of the data stream.\n");
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
continue;
} else if (r < 0) {
if (!BIO_should_retry(bio)) {
printf("BIO_read should retry test failed.\n");
fprintf(stdout, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
fprintf(stdout, "%s\n", ERR_error_string(ERR_get_error(), NULL));
ERR_print_errors_fp(stdout);
continue;
}
/* It would be prudent to check the reason for the retry and handle
* it appropriately here */
}
};
printf("Received %d bytes\n",r);
printf("Received total of %ld bytes of %d\n", i+r, content_length);
memcpy(get+i, buffer, r);
i += r;
}
printf("%s\r\n", buffer);
/* clean up the SSL context resources for the encrypted link */
SSL_CTX_free(ctx);
free(get);
return (EXIT_SUCCESS);
}
I would usually expect to be able to print out the full web page but because of the erroneous Content-Length I get the following output and segfault.
Received 1752 bytes
Received total of 248784 bytes of 105585
Program received signal SIGSEGV, Segmentation fault.
__memmove_sse2_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:404
404 ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S: No such file or directory.
How should I handle pages that give incorrect Content-Length?

The Content-length in the response to a HEAD request is of no relevance. Only the Content-length in the response containing the actual body is relevant (i.e. response to GET, POST...). And this Content-length should be used to read the HTTP body, i.e. first read the HTTP header, determine the length and then read the body as specified. Even if more data could be read they don't belong to the response body.
Apart from that you are doing a HTTP/1.1 request. This means that the server might use Transfer-Encoding: chunked in which case the value of Content-length is irrelevant too. Instead chunked encoding takes preference and you need to read all the chunks of the body based on the length of each given chunk.

Related

Socket not completely filling receive buffer

I'm building my own netcat style listener in C for a project that I'm working on.
The client (linux in this case) connects to the listener using netcat, and from the listener I'm able to send linux commands back. When running commands which give a small output (e.g. whoami, uname, pwd) the output comes back fine. However, when running a command that can give a substantial output (e.g. ls -la), I only get partial output until I send another command, which then means subsequent commands are executing the previous command. Screenshot:
Fig 1: Start of output
Fig 2: Entering another command to force output to finish
Fig 3: Now I'm one command behind, giving incorrect output.
Code as follows (includes/defines/other functions left out for brevity):
int main(int argc, char *argv[])
{
char readbuff[262144];
char user_input[1024] = "";
struct sockaddr_in srv, cln;
int bnd, checkrtr, len, lstn, new_sfd, rd, result, sfd, val;
if(argc != 2)
{
fprintf(stderr, "[*]Usage: %s <target_router>\n", argv[0]);
return -1;
}
check_router(argv[1]);
// Start a listener on port 8888
// Create the socket
sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1)
{
fprintf(stderr, "\n[*]socket: %s (%d)", strerror(errno), errno);
return -1;
}
else
{
fprintf(stdout, "\n[*]Socket created.");
}
val = 1;
result = 0;
result = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
if(result == -1)
{
fprintf(stderr, "\n[*]setsockopt: %s (%d)", strerror(errno), errno);
}
else
{
fprintf(stdout, "\n[*]Address reuse set");
}
srv.sin_family = AF_INET;
srv.sin_addr.s_addr = inet_addr(IP);
srv.sin_port = htons(atoi(LPORT));
len = sizeof(srv);
bnd = bind(sfd, (struct sockaddr*)&srv, len);
if(bnd != 0)
{
fprintf(stderr, "\n[*]bind: %s (%d)", strerror(errno), errno);
return -1;
}
else
{
fprintf(stdout, "\n[*]Socket bound");
}
lstn = listen(sfd, 10);
if(lstn != 0)
{
fprintf(stderr, "\n[*]listen: %s (%d)", strerror(errno), errno);
return -1;
}
else
{
fprintf(stdout, "\n[*]Server listening on %s:%s", IP, LPORT);
}
socklen_t len_c = sizeof(cln);
new_sfd = accept(sfd, (struct sockaddr*)&cln, &len_c);
if(new_sfd == -1)
{
fprintf(stderr, "\n[*]accept: %s (%d)", strerror(errno), errno);
return -1;
}
else
{
char *ip_c = inet_ntoa(cln.sin_addr);
fprintf(stdout, "\n[*]New connection from client: %s:%d\n", ip_c, ntohs(cln.sin_port));
while(1)
{
memset(readbuff, 0x00, sizeof(readbuff));
memset(user_input, 0x00, sizeof(user_input));
fgets(user_input, sizeof(user_input), stdin);
if(user_input[0] == '\n')
{
continue;
}
int send_data = send(new_sfd, user_input, sizeof(user_input), 0);
if(send_data == -1)
{
fprintf(stderr, "\n[*]send: %s (%d)", strerror(errno), errno);
continue;
}
rd = read(new_sfd, readbuff, sizeof(readbuff));
fprintf(stdout, "\n size of rd: %d", rd);
fprintf(stdout, "\n size of readbuff: %ld", sizeof(readbuff));
if(rd > 0)
{
fprintf(stdout, "\n%s", readbuff);
}
else if(rd == 0)
{
fprintf(stdout, "\n[*]Client connection closed.\n");
continue;
//break;
}
else
{
fprintf(stderr, "\n[*]recv: %s (%d)", strerror(errno), errno);
continue;
}
}
}
}
Is anyone able to give me a reason why the output stops partway through please?
That's quite to be expected.
A single read can only read as much data as has been received so far (or otherwise it would need to bend time and space). The other end may also not have sent off everything they have to send by the time you issue read, so a single read won't be able to read everything either (because, well, it may not have been sent).
Since TCP is a stream protocol, you will need to devise a way to know how many bytes to expect for a single "message" from the other end, and read exactly that many bytes so you know you've gotten everything the other end has to say; I'd recommend some sort of Type-Length-Value scheme, even if you didn't need more than 1 type at present.

program crashes when encrypting system files

I wrote this code to encrypt and decrypt the contents of folder, both function works well when encrypting normal files but when im changing the folder to system folder, program crashes and when i check the latest file before crashing, im not able to open some of them (File is Open in Another Program), in some of them i just can't make changes.
i'm handling all errors i think, but it still keep crashing when it reachs to opened file by another program, how to solve this problem to ignore these types of files and keep continue instead of crashing?
and the structure is not important to post i think.
char ListFiles(const wchar_t* folder, CIPHER* conf)
{
wchar_t wildcard[MAX_PATH + 1];
swprintf(wildcard, sizeof(wildcard) / sizeof(*wildcard), L"%s\\*", folder);
WIN32_FIND_DATAW fd;
HANDLE handle = FindFirstFileW(wildcard, &fd);
if (handle == INVALID_HANDLE_VALUE) return 1;
do
{
if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0)
continue;
wchar_t path[MAX_PATH + 1];
swprintf(path, sizeof(path) / sizeof(*path), L"%s\\%s", folder, fd.cFileName);
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
ListFiles(path, &conf);
if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE && !(fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
{
wprintf(L"%s\n", path);
FILE* f_dec;
FILE* f_input = _wfopen(path, L"rb");
FILE* f_enc = _wfopen(wcscat(path, L".encrypted"), L"wb");
if (!f_input || !f_enc) {
fprintf(stderr, "fopen error: %s\n", strerror(errno));
continue;
}
conf->encrypt = 1; // encryption
if (AES_L(conf, f_input, f_enc) != 0)
continue;
f_enc = _wfopen(path, L"rb");
f_dec = _wfopen(wcscat(path, L".decrypted"), L"wb");
if (!f_dec || !f_enc) {
fprintf(stderr, "ERROR: fopen error: %s\n", strerror(errno));
continue;
}
conf->encrypt = 0; // decryption
if (AES_L(conf, f_enc, f_dec) != 0)
continue;
puts("\n\n");
}
} while (FindNextFileW(handle, &fd));
FindClose(handle);
return 0;
}
char AES_L(CIPHER* params, FILE* ifp, FILE* ofp)
{
unsigned int inlen, outlen;
unsigned char* inbuf = (unsigned char*)malloc(params->bufsize);
unsigned char* outbuf = (unsigned char*)malloc(params->bufsize + EVP_MAX_BLOCK_LENGTH);
if (inbuf == NULL || outbuf == NULL)
{
printf("memory cannot be allocated\n");
cleanup(ifp, ofp, inbuf, outbuf);
return 1;
}
EVP_CIPHER_CTX* ctx;
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
fprintf(stderr, "ERROR: EVP_CIPHER_CTX_new failed. OpenSSL error: %s\n",
ERR_error_string(ERR_get_error(), NULL));
cleanup(ifp, ofp, inbuf, outbuf);
return 1;
}
if (!EVP_CipherInit_ex(ctx, params->cipher_type, NULL, params->key, params->iv, params->encrypt)) {
fprintf(stderr, "ERROR: EVP_CipherInit_ex failed. OpenSSL error: %s\n",
ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(ctx);
cleanup(ifp, ofp, inbuf, outbuf);
return 1;
}
while (1) {
// Read in data in blocks until EOF. Update the ciphering with each read.
inlen = fread(inbuf, sizeof(*inbuf), params->bufsize, ifp);
if (ferror(ifp)) {
fprintf(stderr, "ERROR: fread error: %s\n", strerror(errno));
EVP_CIPHER_CTX_cleanup(ctx);
cleanup(ifp, ofp, inbuf, outbuf, errno);
return 1;
}
if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
fprintf(stderr, "ERROR: EVP_CipherUpdate failed. OpenSSL error: %s\n",
ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(ctx);
cleanup(ifp, ofp, inbuf, outbuf);
return 1;
}
fwrite(outbuf, sizeof(*outbuf), outlen, ofp);
if (ferror(ofp)) {
fprintf(stderr, "ERROR: fwrite error: %s\n", strerror(errno));
EVP_CIPHER_CTX_cleanup(ctx);
cleanup(ifp, ofp, inbuf, outbuf, errno);
return 1;
}
if (inlen < params->bufsize) /* Reached End of file */
break;
}
/* Now cipher the final block and write it out to file */
if (!EVP_CipherFinal_ex(ctx, outbuf, &outlen)) {
fprintf(stderr, "ERROR: EVP_CipherFinal_ex failed. OpenSSL error: %s\n",
ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(ctx);
cleanup(ifp, ofp, inbuf, outbuf);
return 1;
}
fwrite(outbuf, sizeof(*outbuf), outlen, ofp);
if (ferror(ofp)) {
fprintf(stderr, "ERROR: fwrite error: %s\n", strerror(errno));
EVP_CIPHER_CTX_cleanup(ctx);
cleanup(ifp, ofp, inbuf, outbuf);
return 1;
}
EVP_CIPHER_CTX_cleanup(ctx);
cleanup(ifp, ofp, inbuf, outbuf);
return 0;
}
update:
void cleanup(FILE* ifp, FILE* ofp, unsigned char* inputBuf, unsigned char* outputBuf)
{
free(inputBuf);
free(outputBuf);
fclose(ifp);
fclose(ofp);
}
typedef struct {
unsigned int key_size;
unsigned int block_size;
unsigned int bufsize;
unsigned char* key;
unsigned char* iv;
unsigned int encrypt;
const EVP_CIPHER* cipher_type;
} CIPHER;
I see some problems with your code, although it's hard to know if they can lead to a crash without the rest of the code (for instance, we can't see the cleanup method), or how you create and initialize params.
The first problem is that you may be leaking file handles. When you open the files in ListFiles you open them in pairs, then check if any of them is NULL and if one is, you go on with the loop.
FILE* f_input = _wfopen(path, L"rb");
FILE* f_enc = _wfopen(wcscat(path, L".encrypted"), L"wb");
if (!f_input || !f_enc) {
fprintf(stderr, "fopen error: %s\n", strerror(errno));
continue;
}
What if f_input is opened correctly but f_enc fails? The source file would remain open until the program ends. You should check each of them separately.
A similar problem arises when you allocate memory in AES_L:
unsigned char* inbuf = (unsigned char*)malloc(params->bufsize);
unsigned char* outbuf = (unsigned char*)malloc(params->bufsize + EVP_MAX_BLOCK_LENGTH);
if (inbuf == NULL || outbuf == NULL)
{
printf("memory cannot be allocated\n");
return 1;
}
If one of the buffers (probably inbuf) is allocated correctly but outbuf fails, you would be leaking memory because you don't free the buffer that was allocated correctly (although, to be honest, I don't think this is directly the problem with the crash because this would happen in situations when you are already very low on memory).
Another problem may appear when you create the path for the encrypted and decrypted files. The path buffer is of size MAX_PATH+1, which is enough for the original file name, but then you perform a couple of wcscat operations that lead additional data to be added to the path. What if the original file name was already almost in the MAX_PATH limit? When you performed the wcscat you would be overflowing a buffer in the stack, which may also lead to a crash.
And finally, ListFiles is recursive, so if there are many nested calls you may be running out of stack, which would also lead to a crash (in fact, from the problems I mention I think it's the main suspect). I would make it iterative.
Anyway, it's very difficult to know if the crash is due to these problems, and your best option is running it in the debugger. The error message from the crash will tell you a lot of information to identify the reason.

Error malloc(): memory corruption

I want to receive messages response from server so I wrote the function bellow:
char * receive_response(SSL *ssl, BIO *outbio) {
int bytes;
int received = 0;
char *resp;
resp = (char *) malloc(4096*sizeof(char));
bytes = SSL_read(ssl, resp, 4096);
resp[strlen(resp)] = '\0';
if (bytes < 0) {
BIO_printf(outbio, "\nError reading...\n");
exit(1);
}
received += bytes;
BIO_printf(outbio, "Received...%d bytes\n", received);
BIO_printf(outbio, "%s", resp);
BIO_printf(outbio, "Receive DONE\n");
return resp;
}
But I get the error: malloc():memory corruption when I run it.
The strange thing is it occurs when I call this function at the second times in main. It's ok at the first time. Please help me to understand it.
Your string is not yet terminated with a '\0', so you can't call strlen on it:
char * receive_response(SSL *ssl, BIO *outbio) {
int bytes;
int received = 0;
char *resp;
// add one extra room if 4096 bytes are effectivly got
resp = malloc(4096+1);
if (NULL == resp)
{
perror("malloc");
exit(1);
}
bytes = SSL_read(ssl, resp, 4096);
if (bytes < 0) {
BIO_printf(outbio, "\nError reading...\n");
exit(1);
}
resp[bytes] = '\0';
received += bytes;
BIO_printf(outbio, "Received...%d bytes\n", received);
BIO_printf(outbio, "%s", resp);
BIO_printf(outbio, "Receive DONE\n");
return resp;
}
Another solution could be to called calloc instead of malloc...

Read Register via Modbus

I am trying to write a c program to read data for morningstar sunsaver MPPT.
This is the simple program I found in net. But my program is unable to read data from register.
#include <stdlib.h>
#include <errno.h>
#include "src/modbus.h"
int main(void)
{
modbus_t *ctx;
uint16_t tab_reg[64];
int rc;
int i;
ctx = modbus_new_rtu("/dev/ttyS0", 115200, 'N',8,1);
if (ctx == NULL) {
fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
rc = modbus_read_registers(ctx, 0, 10, tab_reg);
if (rc == -1) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
return -1;
}
for (i=0; i < rc; i++) {
printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]);
}
modbus_close(ctx);
modbus_free(ctx);
}
It does not work for me. I get the following error message:
Bad file descriptor
By reading the documentation from LibModBus, I think you're missing a call to modbus_connect.
Try connecting before reading registers:
ctx = modbus_new_rtu("/dev/ttyS0", 115200, 'N',8,1);
if (ctx == NULL) {
fprintf(stderr, "Creation failed: %s\n", modbus_strerror(errno));
return -1;
}
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
Also, remember to modbus_close and modbus_free your context before exiting due to further error conditions. For example:
rc = modbus_read_registers(ctx, 0, 10, tab_reg);
if (rc == -1) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
modbus_close(ctx);
modbus_free(ctx);
return -1;
}
It turned out to be trying to read from wrong Serial port.
Reading from /dev/ttyS3 worked.
I later realize that serial port are from /dev/ttyS0 .. /dev/ttyS9

Linux sockets with C: recv() not returning a delayed chunk

A friend of mine asked for help with a programming exercise. He's trying to create a simple HTTP client for chunked encoding. Last chunk delayed by 1000 msec doesn't get blocked by select (select doesn't timeout either). I tried removing select and busy-looping recv(), but it seems as the last chunk never arrives (even though it does).
The code is far from clean, filled with what I'd call rather creative choices. But it seems to work for all the other chunks. I just can't wrap my head around what might cause a delayed chunk to break this thing.
Any ideas?
#define _POSIX_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <time.h>
int main(int argc, char *argv[])
{
// check that parameters exist
if (argc != 4) {
fprintf(stderr, "Wrong number of parameters\n");
return 1;
}
// create addrinfo structure
struct addrinfo info;
memset(&info, 0, sizeof info); // set values to 0
info.ai_family = AF_UNSPEC; // unspecified IP version
info.ai_socktype = SOCK_STREAM; // TCP
struct addrinfo *results; // results structure list
// get info using command line parameters
int value = getaddrinfo(argv[1], argv[2], &info, &results);
// check return value
if (value != 0) {
fprintf(stderr, "Failed to call getaddrinfo\n");
return 1;
}
// create socket
int sock = socket(results->ai_family, results->ai_socktype,
results->ai_protocol);
// check return value
if (sock == -1) {
fprintf(stderr, "Failed to create socket\n");
return 1;
} else {
// connect to remote host
int con = connect(sock, results->ai_addr, results->ai_addrlen);
// check return value
if (con == -1) {
fprintf(stderr, "Failed to connect to host\n");
freeaddrinfo(results);
return 1;
}
}
// free memory
freeaddrinfo(results);
// create request
char request[1000];
strcpy(request, "GET ");
strcat(request, argv[3]);
strcat(request, " HTTP/1.1\r\n");
strcat(request, "Host: ");
strcat(request, argv[1]);
strcat(request, "\r\n");
strcat(request, "Accept-Encoding: chunked\r\n\n\n");
// send request
int sent_bytes = send(sock, request, strlen(request) + 1, 0);
// check return value
if (sent_bytes == -1) {
fprintf(stderr, "Failed to send to remote host\n");
return 1;
}
printf("\n\nSending...\n\n%s", request);
// receive status
char status[13];
int bytes = recv(sock, status, sizeof status, 0);
printf("\nReceiving...\n\n");
// check return value
if (bytes == -1) {
fprintf(stderr, "Failed to receive\n");
return 1;
}
else if (bytes == 0) {
fprintf(stderr, "Host closed the connection\n");
return 1;
}
status[12] = '\0';
if (strstr(status, "HTTP/1.1 404")) {
fprintf(stderr, "404 Not found.\n\n");
return 1;
} else if (!(strstr(status, "HTTP/1.1 200"))) {
fprintf(stderr, "Unknown response.\n\n");
return 1;
}
// status should now be 200 OK
char next[2];
next[1] = '\0';
// check encoding
char* encoding = (char*)calloc(1, sizeof(char));
while (1) {
char *temp = (char*)realloc(encoding, strlen(encoding) + 2);
if (temp == NULL) {
fprintf(stderr, "Failed to realloc\n");
free(encoding);
return 1;
}
encoding = temp;
bytes = recv(sock, next, 1, 0);
if (bytes == -1) {
fprintf(stderr, "Failed to receive\n");
free(encoding);
return 1;
}
else if (bytes == 0) {
fprintf(stderr, "Host closed the connection\n");
free(encoding);
return 1;
}
memcpy(encoding + strlen(encoding), next, 2);
if (strstr(encoding, "transfer-encoding: chunked")) {
free(encoding);
break;
}
if (strstr(encoding, "\r\n\r\n")) {
free(encoding);
fprintf(stderr, "Unsupported encoding\n");
return 1;
}
}
// encoding should now be chunked
// read until message chunks begin
char* rest = (char*)calloc(1, sizeof(char));
while (1) {
char *temp = (char*)realloc(rest, strlen(rest) + 2);
if (temp == NULL) {
fprintf(stderr, "Failed to realloc\n");
free(rest);
return 1;
}
rest = temp;
bytes = recv(sock, next, 1, 0);
if (bytes == -1) {
fprintf(stderr, "Failed to receive\n");
free(rest);
return 1;
}
else if (bytes == 0) {
fprintf(stderr, "Host closed the connection\n");
free(rest);
return 1;
}
memcpy(rest + strlen(rest), next, 2);
if (strstr(rest, "\r\n\r\n")) {
free(rest);
break;
}
}
// read chunks
char* response = (char*)calloc(1, sizeof(char));
while (1) {
char chunksize_string[10];
chunksize_string[0] = '\0';
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
tv.tv_sec = 10;
tv.tv_usec = 500000;
// read chunksize
while (1) {
int rv = select(sock + 1, &readfds, 0, 0, &tv);
if (rv == -1) {
fprintf(stderr, "Error in select\n");
return 1;
} else if (rv == 0) {
fprintf(stderr, "Timeout occured\n");
return 1;
}
bytes = recv(sock, next, 1, MSG_WAITALL);
if (bytes == -1) {
fprintf(stderr, "Failed to receive\n");
free(response);
return 1;
}
else if (bytes == 0) {
fprintf(stderr, "Host closed the connection\n");
free(response);
return 1;
}
memcpy(chunksize_string + strlen(chunksize_string), next, 2);
if (strstr(next, "\n")) {
break;
}
}
unsigned int chunksize;
sscanf(chunksize_string, "%x\r\n", &chunksize);
if (chunksize == 0) {
break;
}
// read chunk
char chunk[chunksize + 1];
bytes = recv(sock, chunk, chunksize, 0);
if (bytes == -1) {
fprintf(stderr, "Failed to receive\n");
free(response);
return 1;
}
else if (bytes == 0) {
fprintf(stderr, "Host closed the connection\n");
free(response);
return 1;
}
chunk[chunksize] = '\0';
// reallocate space in response
char *temp = (char*)realloc(response, strlen(response) + chunksize + 1);
if (temp == NULL) {
fprintf(stderr, "Failed to realloc\n");
free(response);
return 1;
}
response = temp;
// add chunk to response
memcpy(response + strlen(response), chunk, chunksize + 1);
// read "\r\n"
char t[2];
bytes = recv(sock, t, 2, 0);
if (bytes == -1) {
fprintf(stderr, "Failed to receive\n");
free(rest);
return 1;
}
}
printf("%s", response);
free(response);
// close the connection
close(sock);
return 0;
}
The node.js server is as follows:
var restify = require('restify');
var server = restify.createServer();
server.get('/', function(request, response, next) {
response.status(200);
response.header('transfer-encoding', 'chunked');
response.write('First line\n');
response.write('Second line\n');
response.write('Third line first part --');
response.write('and a second part\n');
setTimeout(function() {
response.end('Delayed line\n');
}, 1000);
return next();
});
server.listen(9999);
That code is indeed a bit dodgy. Cant suggest any surefire fix, but these will help:
1) Initialize all variables and reserved space before use. E.g., char request[1000] = {0};, unsigned int chunksize = 0; etc
2) Fix the use of select(). You have select inside while() where you are reading 1 byte at a time, so it will probably loop multiple times. Take into account, that select modifies the fdset and the timeout parameter. Those need to be set correctly for every iteration of the loop.
3) Fix the use of recv(). In reading the chunk, the code assumes recv returns "chunksize" amount of data. At least check that it does. Same applies when receiving the status.
p.s. Compile with all warnings enabled and read the compiler output. Fix all suggested errors.

Resources