/*
* copied and amended from a simple http socket program:
* "Simple Internet client program - by Dan Drown <abob@linux.com>"
* available (last I looked) at:
* http://linux.omnipotent.net/article.php?article_id=5424
*/
#include "mhttp.h"
#ifdef DOHERROR
void herror(char * str);
void herror(char *str){
fprintf(stderr, "herror: %s\n", str);
}
#endif
bool mhttp_lets_debug; /* global debugging flag */
bool mhttp_body_set_flag; /* global body set flag */
int mhttp_hcnt,
mhttp_rcode,
mhttp_response_length;
char *mhttp_body,
*mhttp_response,
*mhttp_reason,
mhttp_resp_headers[MAX_STR],
*mhttp_headers[MAX_HEADERS],
*mhttp_buffers[MAX_BUFFERS];
char *mhttp_last_host = NULL;
int mhttp_last_port = 0;
int mhttp_last_socket = 0;
int mhttp_protocol = 0;
void mhttp_switch_debug(int set)
{
if (set > 0)
{
mhttp_lets_debug = true;
mhttp_debug("%s", "switched on debugging...");
} else {
mhttp_lets_debug = false;
}
}
void mhttp_set_protocol(int proto)
{
mhttp_protocol = proto;
}
int mhttp_get_status_code(void)
{
return mhttp_rcode;
}
int mhttp_get_response_length(void)
{
return mhttp_response_length;
}
char *mhttp_get_reason(void)
{
if (mhttp_reason != NULL){
return strdup(mhttp_reason);
} else {
return NULL;
}
}
char *mhttp_get_response(void)
{
return mhttp_response;
}
char *mhttp_get_response_headers(void)
{
return strdup(mhttp_resp_headers);
}
void mhttp_reset(void)
{
int i;
if (mhttp_response != NULL)
{
free(mhttp_response);
mhttp_response = NULL;
mhttp_debug("reset the response");
}
mhttp_response_length = 0;
if (mhttp_reason != NULL)
{
free(mhttp_reason);
mhttp_reason = NULL;
mhttp_debug("reset the reason");
}
if (mhttp_body_set_flag)
free(mhttp_body);
mhttp_body_set_flag = false;
mhttp_rcode = 0;
mhttp_debug("finished reset");
}
void mhttp_init(void)
{
int i;
for (i = 0; i < mhttp_hcnt; i++)
{
free(mhttp_headers[i]);
mhttp_debug("freeing header");
mhttp_headers[i] = NULL;
}
mhttp_hcnt = 0;
mhttp_lets_debug = false;
mhttp_protocol = 0;
mhttp_reset();
mhttp_debug("finished init");
}
void mhttp_add_header(char *hdr)
{
mhttp_headers[mhttp_hcnt++] = strdup(hdr);
mhttp_debug("request header %s", mhttp_headers[mhttp_hcnt - 1]);
mhttp_headers[mhttp_hcnt] = NULL;
}
void mhttp_set_body(char *bdy)
{
mhttp_body = strdup(bdy);
mhttp_debug("setting body: %s", mhttp_body);
mhttp_body_set_flag = true;
}
int mhttp_call(char *paction, char *purl)
{
bool found_hdrs; /* found the end of headers flag */
bool rcode_flag; /* found return code flag */
bool newcon_flag; /* new connection flag */
bool chunked; /* transfer encoding is chunked */
char *action, *url, *host, *ptr, *eomsg, *clptr;
int port,
socket_descriptor,
returnval,
len,
curr_len,
buffer_size,
this_chunk,
i,
pos;
char str[MAX_STR],
surl[MAX_STR];
memset(mhttp_resp_headers, 0, MAX_STR);
if(strlen(paction) == 0)
{
mhttp_debug("must supply an action");
return -2;
}
action = strdup(paction);
if(strlen(purl) == 0)
{
mhttp_debug("must supply a url");
return -3;
}
url = strdup(purl);
mhttp_debug("action: %s url: %s", action, url);
if (strcmp(action, "GET") != 0 &&
strcmp(action, "POST") != 0 &&
strcmp(action, "PUT") != 0 &&
strcmp(action, "DELETE") != 0 &&
strcmp(action, "HEAD") != 0)
{
mhttp_debug("must supply an action of GET, PUT, POST, DELETE, or HEAD");
return -1;
}
mhttp_debug("action is: %s", action);
mhttp_debug("url is: %s", url);
// make sure that the url starts with http://
if (strncmp(url, "http://", 7) != 0){
mhttp_debug("url must start with http:// - yep we dont support https\n");
return -4;
}
host = url+7;
memset(surl,0,sizeof(surl));
// hunt for the beginning of the uri
ptr = strstr(host, "/");
if (ptr != NULL){
*ptr = '\0';
ptr++;
sprintf(surl, "/%s", ptr);
} else {
sprintf(surl, "/");
}
mhttp_debug("hostname is: %s", host);
mhttp_debug("uri is: %s\n", surl);
// hunt for the beginning of the port
ptr = strstr(host, ":");
if (ptr != NULL){
*ptr = '\0';
ptr++;
port = atoi(ptr);
mhttp_debug("detected port: %d", port);
} else {
port = 80;
}
mhttp_debug("port number is: %d", port);
newcon_flag = false;
//hostandsocket = malloc(128);
if (mhttp_last_host != NULL &&
strcmp(host, mhttp_last_host) == 0 &&
mhttp_last_port == port){
socket_descriptor = mhttp_last_socket;
mhttp_debug("using the cached connection");
} else {
if (mhttp_last_host != NULL){
close(mhttp_last_socket);
mhttp_last_socket = 0;
mhttp_last_port = 0;
free(mhttp_last_host);
}
newcon_flag = true;
socket_descriptor = -1;
mhttp_debug("didnt find connection - creating a new one: %s/%d - %d ", host, port, socket_descriptor);
if( (socket_descriptor = mhttp_connect_inet_addr(host, port)) < 0) {
//mhttp_debug("could not create a new socket");
mhttp_debug("could not create a new socket: %d", socket_descriptor);
return socket_descriptor;
}
}
mhttp_debug("socket descriptor is: %d ", socket_descriptor);
//socket_descriptor = mhttp_connect_inet_addr(host, port);
len = 0;
// construct the query string
memset(str, 0, sizeof(str));
strcpy(str, action);
//strcpy(str+strlen(str), " http://localhost");
strcpy(str+strlen(str), " ");
strcpy(str+strlen(str), surl);
//strcpy(str+strlen(str), " HTTP/1.1\r\n");
sprintf(str+strlen(str), " HTTP/1.%d\r\n", mhttp_protocol);
mhttp_debug("adding on the headers: %d", mhttp_hcnt);
for(i = 0; i < mhttp_hcnt; i++)
{
// make sure that we dont exceed the buffer
if ((strlen(str) + strlen(mhttp_headers[i])) > MAX_BUFFERS - 1)
break;
mhttp_debug("adding header: %s", mhttp_headers[i]);
sprintf(str+strlen(str), "%s\r\n", mhttp_headers[i]);
}
// if this is a post - add the Content-Length header
if (mhttp_body_set_flag)
{
sprintf(str+strlen(str),
"Content-Length: %d\r\n\r\n", strlen(mhttp_body));
} else {
strcpy(str+strlen(str), "\r\n\r\n");
}
mhttp_debug("query string + headers are: %s", str);
returnval = write(socket_descriptor, str, strlen(str));
if(returnval < 0)
{
/* write returns -1 on error */
perror("write(query string) error");
return -5;
}
if(returnval < strlen(str))
{
/* I'm not dealing with this error, regular programs should. */
perror("the query string write was short\n");
return -6;
}
// if this is a PUT or POST - write the body
if (mhttp_body_set_flag)
{
// XXX we probably have a problem where 1024 is exceeded and need to rewrite
mhttp_debug("this is a %s.... writing the body...", action);
mhttp_debug("writing data: %s", mhttp_body);
returnval = write(socket_descriptor, mhttp_body, strlen(mhttp_body));
if(returnval < strlen(mhttp_body))
{
/* I'm not dealing with this error, regular programs should. */
fprintf(stderr, "the write of %s data was short\n", action);
return -6;
}
returnval = write(socket_descriptor, "\r\n", 2);
if(returnval < 2)
{
/* I'm not dealing with this error, regular programs should. */
fprintf(stderr, "the write of %s data - last line failed\n", action);
return -7;
}
}
/* read off the response and split out headers and content */
mhttp_debug("starting output:");
found_hdrs = false;
rcode_flag = false;
len = 0;
curr_len = 0;
chunked = false;
while((returnval = read(socket_descriptor, str, 80)) > 0)
{
*(str+returnval) = '\0';
//mhttp_debug("Line %d: %s", returnval, str);
/* detect the end of the headers */
if (!found_hdrs){
sprintf(mhttp_resp_headers+strlen(mhttp_resp_headers), "%s", str);
if ((ptr = strstr(mhttp_resp_headers, "\r\n\r\n")) ||
(ptr = strstr(mhttp_resp_headers, "\n\n"))){
found_hdrs = true;
*ptr = '\0';
mhttp_debug("found end of headers at: %d", strlen(mhttp_resp_headers));
mhttp_debug("headers are: %s", mhttp_resp_headers);
if (strncmp(ptr,"\0\n\r\n",4) == 0){
// how far along the current buffer is the eoh marker
curr_len = (strlen(mhttp_resp_headers) + 4) - curr_len;
ptr+=4;
} else {
curr_len = (strlen(mhttp_resp_headers) + 2) - curr_len;
ptr+=2;
}
// determine the Content-Length header
if ((clptr = strstr(mhttp_resp_headers, "Content-Length:")) ||
(clptr = strstr(mhttp_resp_headers, "Content-length:"))){
mhttp_debug("found content-length");
clptr += 16;
mhttp_response_length = atoi(clptr);
mhttp_debug("content length: %d", mhttp_response_length);
mhttp_response = malloc(mhttp_response_length + 2);
if (mhttp_response_length >= (returnval - curr_len)){
mhttp_debug("copying the initial part of the body");
memcpy(mhttp_response, ptr, returnval - curr_len);
len = returnval - curr_len;
} else {
// serious error - cant determine length properly
mhttp_debug("serious error - cant determine length properly");
len = 0;
return -8;
}
// look for Transfer-Encoding: chunked
} else if ((clptr = strstr(mhttp_resp_headers, "Transfer-Encoding:")) ||
(clptr = strstr(mhttp_resp_headers, "Transfer-encoding:"))){
clptr += 19;
if (strncmp(clptr, "chunked",7) == 0){
mhttp_debug("found Transfer-Encoding: chunked");
chunked = true;
// must find the end of the chunked length line
// in the remainding buffer
if (clptr = strstr(ptr, "\r\n")){
if (sscanf(ptr, "%x\r\n", &this_chunk) != 1){
mhttp_debug("count not the chunked first amount - something broken");
break;
}
// shift past the chunk size
*clptr = '\0';
curr_len += strlen(ptr) + 2;
clptr += 2;
mhttp_debug("Transfer-Encoding: chunked first buffer is %d - %d bytes left", this_chunk, returnval - curr_len);
mhttp_response = malloc(this_chunk + 2);
memcpy(mhttp_response, clptr, returnval - curr_len);
len += returnval - curr_len;
buffer_size = this_chunk + 2;
ptr = clptr;
} else {
// give up? XXX - not enuf string left to find chunk in buffer?
// get another line and try again
mhttp_debug("getting another line");
i = returnval;
if ((returnval = read(socket_descriptor, str+returnval, 80)) > 0){
returnval += i;
*(str+returnval) = '\0';
mhttp_debug("looking for first chunk at: %s", ptr);
if (clptr = strstr(ptr, "\r\n")){
if (sscanf(ptr, "%x\r\n", &this_chunk) != 1){
// cant find the next chunk
mhttp_debug("could not find first chunk - something broken");
break;
} else {
// sort out the next chunk
if (this_chunk == 0){
// final chunk
mhttp_debug("the first chunk is the final chunk");
break;
} else {
// shift past the chunk size
*clptr = '\0';
curr_len += strlen(ptr) + 2;
clptr += 2;
mhttp_debug("Transfer-Encoding: chunked first buffer is %d - %d bytes left", this_chunk, returnval - curr_len);
mhttp_response = malloc(this_chunk + 2);
memcpy(mhttp_response, clptr, returnval - curr_len);
len += returnval - curr_len;
buffer_size = this_chunk + 2;
ptr = clptr;
}
}
} else {
mhttp_debug("cannot find \\r\\n after first chunked marker - time to give up");
break;
}
} else {
// XXX no more data to read
}
}
}
} else {
mhttp_debug("didnt find content-length - must use realloc");
mhttp_response_length = 0;
mhttp_response = malloc(MAX_STR);
memcpy(mhttp_response, ptr, returnval - curr_len);
len += returnval - curr_len;
buffer_size = MAX_STR;
}
} else {
curr_len += 80;
}
} else {
// we have the headers - this is the body
if (mhttp_response_length > 0){
// make sure that it does not overflow the buffer
if (mhttp_response_length >= (len + returnval)){
memcpy(mhttp_response+len, str, returnval);
}
} else {
// we dont know how big it should be so compensate
//mhttp_debug("buffer remaining is: %d", buffer_size - len);
if (chunked){
if (len + returnval >= buffer_size - 2){
mhttp_debug("reached end of buffer - find another buffer? #%s", str+((buffer_size - 2) - len));
mhttp_debug("remainder of line is: %d - trimming is: %d", (len + returnval) - (buffer_size - 2), (buffer_size - 2) - len);
memcpy(mhttp_response+len, str, (buffer_size - 2) - len);
pos = (buffer_size - 2) - len;
len += pos;
// find the next chunk
mhttp_debug("looking for the next chunk");
if (sscanf(str+pos, "\r\n%d\r\n", &this_chunk) != 1){
// get another line and try again
mhttp_debug("getting another line");
i = returnval;
if ((returnval = read(socket_descriptor, str+returnval, 80)) > 0){
returnval += i;
*(str+returnval) = '\0';
mhttp_debug("looking for last chunk at: %s", str+pos);
if (sscanf(str+pos, "\r\n%x\r\n", &this_chunk) != 1){
// cant find the next chunk
mhttp_debug("could not find next chunk and we are out of data");
break;
} else {
// sort out the next chunk
if (this_chunk == 0){
// final chunk
mhttp_debug("we found the final chunk");
break;
} else {
// resize and paste on remainder
mhttp_debug("resize and paste on remainder for next chunk processing");
mhttp_response = realloc(mhttp_response, buffer_size + this_chunk);
buffer_size += this_chunk;
if (clptr = strstr(str+(pos + 5),"\n")){
*clptr = '\0';
clptr += 1;
i = strlen(str+pos);
i += 1 + pos;
memcpy(mhttp_response+len, clptr, returnval - i);
} else {
mhttp_debug("did not find end of line of new chunk value");
break;
}
}
}
} else {
// bad problem with trying to get a final line
mhttp_debug("read for final line failed");
break;
}
} else {
// allready have the chunk
mhttp_debug("found the next chunk marker");
if (this_chunk == 0){
// final chunk
mhttp_debug("this is the last chunk");
break;
} else {
// resize and paste on remainder
mhttp_debug("resize and paste on remainder for next chunk processing");
mhttp_response = realloc(mhttp_response, buffer_size + this_chunk);
buffer_size += this_chunk;
if (clptr = strstr(str+(pos + 5),"\n")){
*clptr = '\0';
clptr += 1;
i = strlen(str+pos);
i += 1 + pos;
memcpy(mhttp_response+len, clptr, returnval - i);
} else {
mhttp_debug("did not find end of line of new chunk value");
break;
}
}
}
} else {
// not end of chunk yet
memcpy(mhttp_response+len, str, returnval);
}
} else {
// else this is not a chunked read - so realloc when necessary
if (len + returnval > buffer_size){
mhttp_response = realloc(mhttp_response, buffer_size + MAX_STR);
buffer_size += MAX_STR;
}
memcpy(mhttp_response+len, str, returnval);
}
}
len += returnval;
}
/* find the return code */
if (!rcode_flag &&
strncmp(str, "HTTP/",5) == 0 &&
(strncmp(str+5, "0.9 ",4) == 0 ||
strncmp(str+5, "1.0 ",4) == 0 ||
strncmp(str+5, "1.1 ",4) == 0 ) ){
ptr = str+9;
*(ptr+3) = '\0';
mhttp_rcode = atoi(ptr);
rcode_flag = true;
ptr+=4;
/* find the status reason */
//eomsg = NULL;
if ((eomsg = strstr(ptr, "\r\n")) || (eomsg = strstr(ptr, "\n"))){
mhttp_debug("found end of reason line");
*eomsg = '\0';
mhttp_reason = strdup(ptr);
}
mhttp_debug("detected return code: %d - %s", mhttp_rcode, mhttp_reason);
}
// lets get out of here if we have read enough
if (mhttp_response_length > 0 && len >= mhttp_response_length )
break;
//if (returnval < 80)
// break;
}
//mhttp_debug("last line: %s ", str);
mhttp_debug("content length actually copied: %d (may include \\r\\n)", len);
mhttp_response_length = len;
/* it will be closed anyway when we exit */
//close(socket_descriptor);
if (mhttp_protocol == 0 ||
(clptr = strstr(mhttp_resp_headers, "Connection: close")) ||
(clptr = strstr(mhttp_resp_headers, "Connection: Close")) ){
close(socket_descriptor);
if (mhttp_last_host != NULL){
mhttp_last_socket = 0;
mhttp_last_port = 0;
free(mhttp_last_host);
mhttp_debug("removed socket name");
}
mhttp_debug("Closed the connection");
} else {
if (newcon_flag){
mhttp_last_socket = socket_descriptor;
mhttp_last_port = port;
mhttp_last_host = strdup(host);
mhttp_debug("Caching the connection");
} else {
mhttp_debug("connection allready cached");
}
}
mhttp_debug("all done");
/* just to be pedantic... */
return 1;
}
int mhttp_connect_inet_addr(const char *hostname, unsigned short int port)
{
int inet_socket; /* socket descriptor */
struct sockaddr_in inet_address; /* IP/port of the remote host to connect to */
if ( mhttp_build_inet_addr(&inet_address, hostname, port) < 0 )
return -1;
/* socket(domain, type, protocol) */
inet_socket = socket(PF_INET, SOCK_STREAM, 0);
mhttp_debug("socket no: %d", inet_socket);
/* domain is PF_INET(internet/IPv4 domain) *
* type is SOCK_STREAM(tcp) *
* protocol is 0(only one SOCK_STREAM type in the PF_INET domain
*/
if (inet_socket < 0)
{
/* socket returns -1 on error */
perror("socket(PF_INET, SOCK_STREAM, 0) error");
mhttp_debug("socket(PF_INET, SOCK_STREAM, 0) error");
//exit(5);
return -2;
}
/* connect(sockfd, serv_addr, addrlen) */
if(connect(inet_socket, (struct sockaddr *)&inet_address, sizeof(struct sockaddr_in)) < 0)
{
/* connect returns -1 on error */
perror("connect(...) error");
mhttp_debug("connect(...) error");
//exit(6);
return -3;
}
return inet_socket;
}
int mhttp_build_inet_addr(struct sockaddr_in *addr, const char *hostname, unsigned short int port)
{
struct hostent *host_entry;
/* gethostbyname(name) */
host_entry = gethostbyname(hostname);
if(host_entry == NULL)
{
/* gethostbyname returns NULL on error */
herror("gethostbyname failed");
mhttp_debug("gethostbyname failed");
//exit(7);
return -1;
}
/* memcpy(dest, src, length) */
memcpy(&addr->sin_addr.s_addr, host_entry->h_addr_list[0], host_entry->h_length);
/* copy the address to the sockaddr_in struct. */
/* set the family type (PF_INET) */
addr->sin_family = host_entry->h_addrtype;
/* addr->sin_port = port won't work because they are different byte
* orders
*/
addr->sin_port = htons(port);
/* just to be pedantic... */
return 1;
}
/* debug logging */
void mhttp_debug(const char *msgfmt, ...)
{
va_list ap;
char *pos, message[MAX_STR];
int sz;
time_t t;
if (!mhttp_lets_debug)
return;
/* timestamp */
t = time(NULL);
pos = ctime(&t);
sz = strlen(pos);
/* chop off the \n */
pos[sz-1]='\0';
/* insert the header */
snprintf(message, MAX_STR, "mhttp debug:%s: ", pos);
/* find the end and attach the rest of the msg */
for (pos = message; *pos != '\0'; pos++); //empty statement
sz = pos - message;
va_start(ap, msgfmt);
vsnprintf(pos, MAX_STR - sz, msgfmt, ap);
fprintf(stderr,"%s", message);
fprintf(stderr, "\n");
fflush(stderr);
}