ZeroTierOne/attic/historic/anode/libanode/impl/http_client.c
2017-05-04 10:42:22 -07:00

559 lines
22 KiB
C

/* libanode: the Anode C reference implementation
* Copyright (C) 2009-2010 Adam Ierymenko <adam.ierymenko@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include "http_client.h"
#include "misc.h"
#include "types.h"
/* How much to increment read buffer at each capacity top? */
#define ANODE_HTTP_CAPACITY_INCREMENT 4096
static void AnodeHttpClient_close_and_fail(struct AnodeHttpClient *client)
{
if (client->impl.tcp_connection) {
client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
}
client->response.data_length = 0;
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
if (client->handler)
client->handler(client);
}
static void AnodeHttpClient_do_initiate_client(struct AnodeHttpClient *client)
{
const char *method = "";
long l,i;
switch(client->method) {
case ANODE_HTTP_GET: method = "GET"; break;
case ANODE_HTTP_HEAD: method = "HEAD"; break;
case ANODE_HTTP_POST: method = "POST"; break;
}
client->impl.outbuf_len = snprintf((char *)client->impl.outbuf,sizeof(client->impl.outbuf),
"%s %s%s%s HTTP/1.1\r\nHost: %s:%d\r\n%s",
method,
client->uri.path,
((client->uri.query[0]) ? "?" : ""),
client->uri.query,
client->uri.host,
((client->uri.port > 0) ? client->uri.port : 80),
((client->keepalive) ? "" : "Connection: close\r\n")
);
if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
AnodeHttpClient_close_and_fail(client);
return;
}
if (client->method == ANODE_HTTP_POST) {
if ((client->data)&&(client->data_length)) {
client->impl.outbuf_len += snprintf((char *)client->impl.outbuf + client->impl.outbuf_len,sizeof(client->impl.outbuf) - client->impl.outbuf_len,
"Content-Type: %s\r\n",
(client->data_content_type ? client->data_content_type : "application/x-www-form-urlencoded")
);
if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
AnodeHttpClient_close_and_fail(client);
return;
}
client->impl.outbuf_len += snprintf((char *)client->impl.outbuf + client->impl.outbuf_len,sizeof(client->impl.outbuf) - client->impl.outbuf_len,
"Content-Length: %u\r\n",
client->data_length
);
if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
AnodeHttpClient_close_and_fail(client);
return;
}
} else {
client->impl.outbuf_len += snprintf((char *)client->impl.outbuf + client->impl.outbuf_len,sizeof(client->impl.outbuf) - client->impl.outbuf_len,
"Content-Length: 0\r\n"
);
if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
AnodeHttpClient_close_and_fail(client);
return;
}
}
}
l = AnodeDictionary_write(&(client->headers),(char *)client->impl.outbuf + client->impl.outbuf_len,(long)(sizeof(client->impl.outbuf) - client->impl.outbuf_len - 2),"\r\n",": ");
if (l < 0) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
AnodeHttpClient_close_and_fail(client);
return;
}
client->impl.outbuf_len += (unsigned int)l;
if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) { /* sanity check */
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
AnodeHttpClient_close_and_fail(client);
return;
}
client->impl.outbuf[client->impl.outbuf_len++] = '\r';
client->impl.outbuf[client->impl.outbuf_len++] = '\n';
if ((client->method == ANODE_HTTP_POST)&&(client->data)&&(client->data_length)) {
i = sizeof(client->impl.outbuf) - client->impl.outbuf_len;
if (i > client->data_length)
i = client->data_length;
Anode_memcpy((client->impl.outbuf + client->impl.outbuf_len),client->data,i);
client->impl.request_data_ptr += i;
client->impl.outbuf_len += i;
}
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_SEND;
client->impl.transport_engine->tcp_start_writing(client->impl.transport_engine,client->impl.tcp_connection);
}
static void AnodeHttpClient_tcp_outgoing_connect_handler(
AnodeTransportEngine *transport,
AnodeTransportTcpConnection *connection,
int error_code)
{
struct AnodeHttpClient *client;
if (!(client = (struct AnodeHttpClient *)(connection->ptr)))
return;
if ((client->impl.phase == ANODE_HTTP_REQUEST_PHASE_CONNECT)&&(!client->impl.freed)) {
if (error_code) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_CONNECT_FAILED;
AnodeHttpClient_close_and_fail(client);
} else {
client->impl.tcp_connection = connection;
AnodeHttpClient_do_initiate_client(client);
}
} else transport->tcp_close(transport,connection);
}
static void AnodeHttpClient_tcp_connection_terminated_handler(
AnodeTransportEngine *transport,
AnodeTransportTcpConnection *connection,
int error_code)
{
struct AnodeHttpClient *client;
if (!(client = (struct AnodeHttpClient *)(connection->ptr)))
return;
if (client->impl.freed)
return;
client->response.data_length = 0;
client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
if ((client->impl.phase != ANODE_HTTP_REQUEST_PHASE_KEEPALIVE)&&(client->impl.phase != ANODE_HTTP_REQUEST_PHASE_CLOSED)) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_SERVER_CLOSED_CONNECTION;
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
AnodeHttpClient_close_and_fail(client);
} else client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
}
static void AnodeHttpClient_tcp_receive_handler(
AnodeTransportEngine *transport,
AnodeTransportTcpConnection *connection,
void *data,
unsigned int data_length)
{
struct AnodeHttpClient *client;
char *p1,*p2;
unsigned int i;
long l;
if (!(client = (struct AnodeHttpClient *)(connection->ptr)))
return;
if (client->impl.freed) {
transport->tcp_close(transport,connection);
return;
}
if (!client->response.data)
client->response.data = malloc(client->impl.response_data_capacity = ANODE_HTTP_CAPACITY_INCREMENT);
i = 0;
while (i < data_length) {
switch(client->impl.read_mode) {
case ANODE_HTTP_READ_MODE_WAITING:
for(;i<data_length;++i) {
if (((const char *)data)[i] == '\n') {
((char *)client->response.data)[client->response.data_length] = (char)0;
client->response.data_length = 0;
p1 = (char *)Anode_strchr((char *)client->response.data,' ');
if (!p1)
p1 = (char *)Anode_strchr((char *)client->response.data,'\t');
if (p1) {
while ((*p1 == ' ')||(*p1 == '\t')) ++p1;
if (!*p1) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_INVALID_RESPONSE;
AnodeHttpClient_close_and_fail(client);
return;
}
p2 = p1 + 1;
while (*p2) {
if ((*p2 == ' ')||(*p2 == '\t')||(*p2 == '\r')||(*p2 == '\n')) {
*p2 = (char)0;
break;
} else ++p2;
}
client->response.code = (int)strtol(p1,(char **)0,10);
client->impl.read_mode = ANODE_HTTP_READ_MODE_HEADERS;
++i; break; /* Exit inner for() */
}
} else {
((char *)client->response.data)[client->response.data_length++] = ((const char *)data)[i];
if (client->response.data_length >= client->impl.response_data_capacity)
client->response.data = realloc(client->response.data,client->impl.response_data_capacity += ANODE_HTTP_CAPACITY_INCREMENT);
}
}
break;
case ANODE_HTTP_READ_MODE_HEADERS:
case ANODE_HTTP_READ_MODE_CHUNKED_FOOTER:
for(;i<data_length;++i) {
if (((const char *)data)[i] == '\n') {
client->impl.header_line_buf[client->impl.header_line_buf_ptr] = (char)0;
client->impl.header_line_buf_ptr = 0;
if ((!client->impl.header_line_buf[0])||((client->impl.header_line_buf[0] == '\r')&&(!client->impl.header_line_buf[1]))) {
/* If the line is empty (or is empty with \r\n as the
* line terminator), we're at the end. */
if (client->impl.read_mode == ANODE_HTTP_READ_MODE_CHUNKED_FOOTER) {
/* If this is a chunked footer, we finally end the
* chunked response. */
client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
if (client->keepalive)
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_KEEPALIVE;
else {
client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
}
if (client->handler)
client->handler(client);
if (client->impl.freed)
return;
} else {
/* Otherwise, this is a regular header block */
if (client->response.code == 100) {
/* Ignore 100 Continue messages */
client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
++i; break; /* Exit inner for() */
} else if ((client->response.code == 200)&&(client->method != ANODE_HTTP_HEAD)) {
/* Other messages get their headers parsed to determine
* how to read them. */
p1 = (char *)AnodeDictionary_get(&(client->response.headers),"transfer-encoding");
if ((p1)&&(Anode_strcaseeq(p1,"chunked"))) {
/* Chunked encoding enters chunked mode */
client->impl.header_line_buf_ptr = 0;
client->impl.read_mode = ANODE_HTTP_READ_MODE_CHUNKED_CHUNK_SIZE;
++i; break; /* Exit inner for() */
} else {
/* Else we must have a Content-Length header */
p1 = (char *)AnodeDictionary_get(&(client->response.headers),"content-length");
if (!p1) {
/* No chunked or content length is not supported */
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_INVALID_RESPONSE;
AnodeHttpClient_close_and_fail(client);
return;
} else {
/* Enter block read mode with content length */
l = strtol(p1,(char **)0,10);
if (l <= 0) {
/* Zero length data is all done... */
client->impl.expecting_response_length = 0;
client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
if (client->keepalive)
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_KEEPALIVE;
else {
client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
}
if (client->handler)
client->handler(client);
if (client->impl.freed)
return;
++i; break; /* Exit inner for() */
} else {
/* Else start reading... */
client->impl.expecting_response_length = (unsigned int)l;
client->impl.read_mode = ANODE_HTTP_READ_MODE_BLOCK;
++i; break; /* Exit inner for() */
}
}
}
} else {
/* HEAD clients or non-200 codes get headers only */
client->impl.expecting_response_length = 0;
client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
if (client->keepalive)
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_KEEPALIVE;
else {
client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
}
if (client->handler)
client->handler(client);
if (client->impl.freed)
return;
++i; break; /* Exit inner for() */
}
}
} else {
/* Otherwise this is another header, add to dictionary */
AnodeDictionary_read(
&(client->response.headers),
client->impl.header_line_buf,
"\r\n",
": \t",
"",
(char)0,
1,
1
);
}
} else {
client->impl.header_line_buf[client->impl.header_line_buf_ptr++] = ((const char *)data)[i];
if (client->impl.header_line_buf_ptr >= sizeof(client->impl.header_line_buf)) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_INVALID_RESPONSE;
AnodeHttpClient_close_and_fail(client);
return;
}
}
}
break;
case ANODE_HTTP_READ_MODE_BLOCK:
if ((client->response.data_length + client->impl.expecting_response_length) > client->impl.response_data_capacity)
client->response.data = realloc(client->response.data,client->impl.response_data_capacity = (client->response.data_length + client->impl.expecting_response_length));
for(;((i<data_length)&&(client->impl.expecting_response_length));++i) {
((char *)client->response.data)[client->response.data_length++] = ((const char *)data)[i];
--client->impl.expecting_response_length;
}
if (!client->impl.expecting_response_length) {
client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
if (client->keepalive)
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_KEEPALIVE;
else {
client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
}
if (client->handler)
client->handler(client);
if (client->impl.freed)
return;
}
break;
case ANODE_HTTP_READ_MODE_CHUNKED_CHUNK_SIZE:
for(;i<data_length;++i) {
if (((const char *)data)[i] == '\n') {
client->impl.header_line_buf[client->impl.header_line_buf_ptr] = (char)0;
client->impl.header_line_buf_ptr = 0;
p1 = client->impl.header_line_buf;
while (*p1) {
if ((*p1 == ';')||(*p1 == ' ')||(*p1 == '\r')||(*p1 == '\n')||(*p1 == '\t')) {
*p1 = (char)0;
break;
} else ++p1;
}
if (client->impl.header_line_buf[0]) {
l = strtol(client->impl.header_line_buf,(char **)0,16);
if (l <= 0) {
/* Zero length ends chunked and enters footer mode */
client->impl.expecting_response_length = 0;
client->impl.read_mode = ANODE_HTTP_READ_MODE_CHUNKED_FOOTER;
} else {
/* Otherwise the next chunk is to be read */
client->impl.expecting_response_length = (unsigned int)l;
client->impl.read_mode = ANODE_HTTP_READ_MODE_CHUNKED_DATA;
}
++i; break; /* Exit inner for() */
}
} else {
client->impl.header_line_buf[client->impl.header_line_buf_ptr++] = ((const char *)data)[i];
if (client->impl.header_line_buf_ptr >= sizeof(client->impl.header_line_buf)) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_INVALID_RESPONSE;
AnodeHttpClient_close_and_fail(client);
return;
}
}
}
break;
case ANODE_HTTP_READ_MODE_CHUNKED_DATA:
if ((client->response.data_length + client->impl.expecting_response_length) > client->impl.response_data_capacity)
client->response.data = realloc(client->response.data,client->impl.response_data_capacity = (client->response.data_length + client->impl.expecting_response_length));
for(;((i<data_length)&&(client->impl.expecting_response_length));++i) {
((char *)client->response.data)[client->response.data_length++] = ((const char *)data)[i];
--client->impl.expecting_response_length;
}
if (!client->impl.expecting_response_length)
client->impl.read_mode = ANODE_HTTP_READ_MODE_CHUNKED_CHUNK_SIZE;
break;
}
}
}
static void AnodeHttpClient_tcp_available_for_write_handler(
AnodeTransportEngine *transport,
AnodeTransportTcpConnection *connection)
{
struct AnodeHttpClient *client;
unsigned int i,j;
int n;
if (!(client = (struct AnodeHttpClient *)(connection->ptr)))
return;
if (client->impl.freed) {
transport->tcp_close(transport,connection);
return;
}
if (client->impl.phase == ANODE_HTTP_REQUEST_PHASE_SEND) {
n = client->impl.transport_engine->tcp_send(client->impl.transport_engine,client->impl.tcp_connection,(const void *)client->impl.outbuf,(int)client->impl.outbuf_len);
if (n < 0) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_SERVER_CLOSED_CONNECTION;
AnodeHttpClient_close_and_fail(client);
} else if (n > 0) {
for(i=0,j=(client->impl.outbuf_len - (unsigned int)n);i<j;++i)
client->impl.outbuf[i] = client->impl.outbuf[i + (unsigned int)n];
client->impl.outbuf_len -= (unsigned int)n;
if ((client->method == ANODE_HTTP_POST)&&(client->data)&&(client->data_length)) {
i = sizeof(client->impl.outbuf) - client->impl.outbuf_len;
j = client->data_length - client->impl.request_data_ptr;
if (i > j)
i = j;
Anode_memcpy((client->impl.outbuf + client->impl.outbuf_len),client->data,i);
client->impl.request_data_ptr += i;
client->impl.outbuf_len += i;
}
if (!client->impl.outbuf_len) {
client->impl.transport_engine->tcp_stop_writing(client->impl.transport_engine,client->impl.tcp_connection);
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_RECEIVE;
}
}
} else client->impl.transport_engine->tcp_stop_writing(client->impl.transport_engine,client->impl.tcp_connection);
}
static void AnodeHttpClient_dns_result_handler(
AnodeTransportEngine *transport,
void *ptr,
int error_code,
const char *name,
const AnodeTransportIpAddress *ip_addresses,
unsigned int ip_address_count,
const AnodeAddress *anode_address)
{
struct AnodeHttpClient *client;
AnodeTransportIpEndpoint to_endpoint;
if (!(client = (struct AnodeHttpClient *)ptr))
return;
if (client->impl.freed)
return;
if ((error_code)||(!ip_address_count)) {
if (client->impl.phase == ANODE_HTTP_REQUEST_PHASE_RESOLVE) {
client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_DNS_RESOLVE_FAILED;
AnodeHttpClient_close_and_fail(client);
}
} else {
client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CONNECT;
Anode_memcpy(&to_endpoint.address,ip_addresses,sizeof(AnodeTransportIpAddress));
to_endpoint.port = (client->uri.port > 0) ? client->uri.port : 80;
client->impl.transport_engine->tcp_connect(
client->impl.transport_engine,
client,
&AnodeHttpClient_tcp_outgoing_connect_handler,
&AnodeHttpClient_tcp_connection_terminated_handler,
&AnodeHttpClient_tcp_receive_handler,
&AnodeHttpClient_tcp_available_for_write_handler,
&to_endpoint);
}
}
struct AnodeHttpClient *AnodeHttpClient_new(AnodeTransportEngine *transport_engine)
{
struct AnodeHttpClient *req = malloc(sizeof(struct AnodeHttpClient));
Anode_zero(req,sizeof(struct AnodeHttpClient));
AnodeDictionary_init(&(req->headers),0);
AnodeDictionary_init(&(req->response.headers),0);
req->impl.transport_engine = transport_engine;
return req;
}
void AnodeHttpClient_send(struct AnodeHttpClient *client)
{
client->response.code = 0;
client->response.data_length = 0;
AnodeDictionary_clear(&(client->response.headers));
client->impl.request_data_ptr = 0;
client->impl.expecting_response_length = 0;
client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
client->impl.outbuf_len = 0;
if (!client->impl.tcp_connection) {
client->impl.transport_engine->dns_resolve(
client->impl.transport_engine,
&AnodeHttpClient_dns_result_handler,
client,
client->uri.host,
ANODE_TRANSPORT_DNS_QUERY_ALWAYS,
ANODE_TRANSPORT_DNS_QUERY_IF_NO_PREVIOUS,
ANODE_TRANSPORT_DNS_QUERY_NEVER);
} else AnodeHttpClient_do_initiate_client(client);
}
void AnodeHttpClient_free(struct AnodeHttpClient *client)
{
AnodeDictionary_destroy(&(client->headers));
AnodeDictionary_destroy(&(client->response.headers));
if (client->impl.tcp_connection) {
client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
}
if (client->response.data)
free(client->response.data);
client->impl.freed = 1;
client->impl.transport_engine->run_later(client->impl.transport_engine,client,&free);
}