495 lines
11 KiB
C
495 lines
11 KiB
C
/*
|
|
* Copyright (C) 2009 Mamadou Diop.
|
|
*
|
|
* Contact: Mamadou Diop <diopmamadou@yahoo.fr>
|
|
*
|
|
* This file is part of Open Source Doubango Framework.
|
|
*
|
|
* DOUBANGO 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.
|
|
*
|
|
* DOUBANGO 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 DOUBANGO.
|
|
*
|
|
*/
|
|
/**@file thttp.c
|
|
* @brief HTTP (RFC 2616) and HTTP basic/digest authetication (RFC 2617) implementations.
|
|
*
|
|
* @author Mamadou Diop <diopmamadou(at)yahoo.fr>
|
|
*
|
|
* @date Created: Sat Nov 8 16:54:58 2009 mdiop
|
|
*/
|
|
#include "thttp.h"
|
|
#include "tinyHTTP/thttp_event.h"
|
|
#include "tinyHTTP/thttp_message.h"
|
|
|
|
#include "tnet.h"
|
|
#include "tnet_transport.h"
|
|
|
|
#include "tsk_runnable.h"
|
|
#include "tsk_time.h"
|
|
#include "tsk_debug.h"
|
|
#include "tsk_memory.h"
|
|
#include "tsk_string.h"
|
|
#include "tsk_fsm.h"
|
|
|
|
//#include <stdarg.h>
|
|
//#include <string.h>
|
|
#include <stdlib.h> /* srand */
|
|
|
|
|
|
/** @mainpage TinyHTTP API Overview
|
|
*
|
|
* This file is an overview of <b>tinyHTTP</b> API.
|
|
*
|
|
* <b>tinyHTTP</b> is a HTTP (RFC 2616) stack.
|
|
*/
|
|
|
|
// KeepAlive : http://www.io.com/~maus/HttpKeepAlive.html
|
|
|
|
static unsigned __thttp_initialized = 0;
|
|
|
|
typedef struct thttp_stack_s
|
|
{
|
|
TSK_DECLARE_OBJECT;
|
|
|
|
thttp_stack_callback callback;
|
|
tsk_fsm_t *fsm;
|
|
|
|
/* Identity */
|
|
char* username;
|
|
char* password;
|
|
char* proxy_host;
|
|
int proxy_port;
|
|
char* proxy_username;
|
|
char* proxy_password;
|
|
char* user_agent;
|
|
|
|
/* Network */
|
|
char* local_ip;
|
|
int local_port;
|
|
tnet_transport_t *transport;
|
|
|
|
TSK_DECLARE_SAFEOBJ;
|
|
thttp_operation_handles_L_t* ops;
|
|
}
|
|
thttp_stack_t;
|
|
|
|
thttp_operation_handle_t* thttp_stack_get_op(thttp_stack_handle_t *self, tnet_fd_t fd);
|
|
|
|
static int thttp_transport_layer_stream_cb(const tnet_transport_event_t* e)
|
|
{
|
|
int ret = -1;
|
|
// tsk_ragel_state_t state;
|
|
// thttp_message_t *message = THTTP_NULL;
|
|
// int endOfheaders = -1;
|
|
// const thttp_t *stack = e->callback_data;
|
|
//
|
|
// switch(e->type){
|
|
// case event_data: {
|
|
// break;
|
|
// }
|
|
// case event_closed:
|
|
// case event_connected:
|
|
// default:{
|
|
// return 0;
|
|
// }
|
|
// }
|
|
//
|
|
//
|
|
// /* Check if buffer is too big to be valid (have we missed some chuncks?) */
|
|
// if(TSK_BUFFER_SIZE(transport->buff_stream) >= 0xFFFF){
|
|
// tsk_buffer_cleanup(transport->buff_stream);
|
|
// }
|
|
//
|
|
// /* Append new content. */
|
|
// tsk_buffer_append(transport->buff_stream, e->data, e->size);
|
|
//
|
|
// /* Check if we have all HTTP headers. */
|
|
// if((endOfheaders = tsk_strindexOf(TSK_BUFFER_DATA(transport->buff_stream),TSK_BUFFER_SIZE(transport->buff_stream), "\r\n\r\n"/*2CRLF*/)) < 0){
|
|
// TSK_DEBUG_INFO("No all HTTP headers in the TCP buffer.");
|
|
// goto bail;
|
|
// }
|
|
//
|
|
// /* If we are there this mean that we have all HTTP headers.
|
|
// * ==> Parse the HTTP message without the content.
|
|
// */
|
|
// tsk_ragel_state_init(&state, TSK_BUFFER_DATA(transport->buff_stream), endOfheaders + 4/*2CRLF*/);
|
|
// if(thttp_message_parse(&state, &message, THTTP_FALSE/* do not extract the content */) == THTTP_TRUE
|
|
// && message->firstVia && message->Call_ID && message->CSeq && message->From && message->To)
|
|
// {
|
|
// size_t clen = THTTP_MESSAGE_CONTENT_LENGTH(message); /* MUST have content-length header (see RFC 3261 - 7.5). If no CL header then the macro return zero. */
|
|
// if(clen == 0){ /* No content */
|
|
// tsk_buffer_remove(transport->buff_stream, 0, (endOfheaders + 4/*2CRLF*/)); /* Remove HTTP headers and CRLF */
|
|
// }
|
|
// else{ /* There is a content */
|
|
// if((endOfheaders + 4/*2CRLF*/ + clen) > TSK_BUFFER_SIZE(transport->buff_stream)){ /* There is content but not all the content. */
|
|
// TSK_DEBUG_INFO("No all HTTP content in the TCP buffer.");
|
|
// goto bail;
|
|
// }
|
|
// else{
|
|
// /* Add the content to the message. */
|
|
// thttp_message_add_content(message, THTTP_NULL, TSK_BUFFER_TO_U8(transport->buff_stream) + endOfheaders + 4/*2CRLF*/, clen);
|
|
// /* Remove HTTP headers, CRLF and the content. */
|
|
// tsk_buffer_remove(transport->buff_stream, 0, (endOfheaders + 4/*2CRLF*/ + clen));
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// if(message){
|
|
// /* Handle the incoming message. */
|
|
// ret = thttp_transport_layer_handle_incoming_msg(transport, message);
|
|
// /* Set fd */
|
|
// message->sockfd = e->fd;
|
|
// }
|
|
// else ret = -15;
|
|
//
|
|
//bail:
|
|
// TSK_OBJECT_SAFE_FREE(message);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __thttp_stack_set(thttp_stack_t *self, va_list values)
|
|
{
|
|
thttp_stack_param_type_t curr;
|
|
|
|
while((curr=va_arg(values, thttp_stack_param_type_t)) != pname_null)
|
|
{
|
|
switch(curr)
|
|
{
|
|
//
|
|
// Identity
|
|
//
|
|
case pname_usr:
|
|
{ /* USRNAME_STR, PASSWORD_STR */
|
|
tsk_strupdate(&self->username, va_arg(values, const char*));
|
|
tsk_strupdate(&self->password, va_arg(values, const char*));
|
|
break;
|
|
}
|
|
case pname_proxy:
|
|
{ /* HOST_STR, PORT_INT, USRNAME_STR, PASSWORD_STR */
|
|
tsk_strupdate(&self->proxy_host, va_arg(values, const char*));
|
|
self->proxy_port = va_arg(values, int);
|
|
tsk_strupdate(&self->proxy_username, va_arg(values, const char*));
|
|
tsk_strupdate(&self->proxy_password, va_arg(values, const char*));
|
|
break;
|
|
}
|
|
case pname_useragent:
|
|
{ /* UA_STR */
|
|
tsk_strupdate(&self->user_agent, va_arg(values, const char*));
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Network
|
|
//
|
|
case pname_local_ip:
|
|
{ /* STR */
|
|
tsk_strupdate(&self->local_ip, va_arg(values, const char*));
|
|
break;
|
|
}
|
|
case pname_local_port:
|
|
{ /* INT */
|
|
self->local_port = va_arg(values, int);
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
{
|
|
TSK_DEBUG_WARN("Found unknown pname.");
|
|
goto bail;
|
|
}
|
|
|
|
}/* switch */
|
|
}/* while */
|
|
|
|
bail:
|
|
return 0;
|
|
}
|
|
|
|
int thttp_global_init()
|
|
{
|
|
if(!__thttp_initialized)
|
|
{
|
|
srand((unsigned int) tsk_time_epoch());
|
|
if(!tnet_startup()){
|
|
__thttp_initialized = 1;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int thttp_global_deinit()
|
|
{
|
|
if(__thttp_initialized)
|
|
{
|
|
if(!tnet_cleanup()){
|
|
__thttp_initialized = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
thttp_stack_handle_t *thttp_stack_create(thttp_stack_callback callback, ...)
|
|
{
|
|
thttp_stack_t* stack = tsk_object_new(thttp_stack_def_t);
|
|
va_list params;
|
|
|
|
if(!stack){
|
|
return 0;
|
|
}
|
|
stack->local_ip = TNET_SOCKET_HOST_ANY;
|
|
stack->local_port = TNET_SOCKET_PORT_ANY;
|
|
|
|
stack->callback = callback;
|
|
va_start(params, callback);
|
|
if(__thttp_stack_set(stack, params)){
|
|
// Delete the stack?
|
|
}
|
|
va_end(params);
|
|
|
|
return stack;
|
|
}
|
|
|
|
int thttp_stack_start(thttp_stack_handle_t *self)
|
|
{
|
|
thttp_stack_t *stack = self;
|
|
|
|
if(!stack){ // check if running
|
|
return -1;
|
|
}
|
|
|
|
stack->transport = TNET_TRANSPORT_CREATE(stack->local_ip, stack->local_port, tnet_socket_type_tcp_ipv4, "HTTP/HTTPS transport");
|
|
tnet_transport_set_callback(stack->transport, TNET_TRANSPORT_CB_F(thttp_transport_layer_stream_cb), self);
|
|
|
|
return tnet_transport_start(stack->transport);
|
|
}
|
|
|
|
int thttp_stack_set(thttp_stack_handle_t *self, ...)
|
|
{
|
|
if(self){
|
|
int ret;
|
|
thttp_stack_t *stack = self;
|
|
|
|
va_list params;
|
|
va_start(params, self);
|
|
ret = __thttp_stack_set(stack, params);
|
|
va_end(params);
|
|
return ret;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int thttp_stack_stop(thttp_stack_handle_t *self)
|
|
{
|
|
thttp_stack_t *stack = self;
|
|
|
|
if(!stack){
|
|
return -1;
|
|
}
|
|
return tnet_transport_start(stack->transport);
|
|
}
|
|
|
|
int thttp_stack_send(thttp_stack_handle_t *self, thttp_operation_handle_t* op, const thttp_message_t* message)
|
|
{
|
|
int ret = -1;
|
|
tsk_buffer_t* output = TSK_BUFFER_CREATE_NULL();
|
|
tnet_socket_type_t type;
|
|
thttp_stack_t *stack;
|
|
tnet_fd_t fd;
|
|
|
|
if(!self || !op || !message->url){ /* Only requests are supported for now. */
|
|
goto bail;
|
|
}
|
|
|
|
stack = self;
|
|
type = tnet_transport_get_type(stack->transport);
|
|
|
|
/* Serialize the message and send it */
|
|
if((ret = thttp_message_tostring(message, output))){
|
|
goto bail;
|
|
}
|
|
else{
|
|
if(message->url->type == url_https){
|
|
TNET_SOCKET_TYPE_SET_TLS(type);
|
|
}
|
|
else{
|
|
TNET_SOCKET_TYPE_SET_TCP(type);
|
|
}
|
|
}
|
|
|
|
/* Gets the fd associated to this operation */
|
|
fd = thttp_operation_get_fd(op);
|
|
|
|
if(fd == TNET_INVALID_FD){
|
|
if((fd = tnet_transport_connectto(stack->transport, message->url->host, message->url->port, type)) == TNET_INVALID_FD){
|
|
goto bail;
|
|
}
|
|
/* Wait for the socket for writability */
|
|
if((ret = tnet_sockfd_waitUntilWritable(fd, TNET_CONNECT_TIMEOUT))){
|
|
TSK_DEBUG_ERROR("%d milliseconds elapsed and the socket is still not connected.", TNET_CONNECT_TIMEOUT);
|
|
tnet_transport_remove_socket(stack->transport, fd);
|
|
goto bail;
|
|
}
|
|
else{
|
|
thttp_operation_set_fd(op, fd);
|
|
}
|
|
}
|
|
|
|
if(tnet_transport_send(stack->transport, fd, output->data, output->size)){
|
|
ret = 0;
|
|
}
|
|
else{
|
|
ret = -15;
|
|
}
|
|
|
|
bail:
|
|
TSK_OBJECT_SAFE_FREE(output);
|
|
return ret;
|
|
}
|
|
|
|
thttp_operation_handle_t* thttp_stack_get_op(thttp_stack_handle_t *self, tnet_fd_t fd)
|
|
{
|
|
thttp_operation_handle_t* ret = 0;
|
|
thttp_stack_t *stack = self;
|
|
tsk_list_item_t *item;
|
|
|
|
if(!stack || !stack->ops){
|
|
return 0;
|
|
}
|
|
|
|
tsk_safeobj_lock(stack);
|
|
|
|
tsk_list_foreach(item, stack->ops)
|
|
{
|
|
if(thttp_operation_get_fd(item->data) == fd){
|
|
ret = item->data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
tsk_safeobj_unlock(stack);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int thttp_stack_add_op(thttp_stack_handle_t *self, thttp_operation_handle_t* op)
|
|
{
|
|
thttp_stack_t *stack = self;
|
|
|
|
if(!stack || !stack->ops || !op){
|
|
return -1;
|
|
}
|
|
|
|
tsk_safeobj_lock(stack);
|
|
/* ref() called by the operation's ctor,
|
|
unref will be called when removed from the list. */
|
|
tsk_list_push_back_data(stack->ops, &op);
|
|
tsk_safeobj_unlock(stack);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int thttp_stack_remove_op(thttp_stack_handle_t *self, thttp_operation_handle_t* op)
|
|
{
|
|
thttp_stack_t *stack = self;
|
|
|
|
if(!stack || !stack->ops || !op){
|
|
return -1;
|
|
}
|
|
|
|
tsk_safeobj_lock(stack);
|
|
tsk_list_remove_item_by_data(stack->ops, op);
|
|
tsk_safeobj_unlock(stack);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//========================================================
|
|
// HTTP stack object definition
|
|
//
|
|
static void* _thttp_stack_create(void * self, va_list * app)
|
|
{
|
|
thttp_stack_t *stack = self;
|
|
if(stack){
|
|
tsk_safeobj_init(stack);
|
|
|
|
stack->ops = TSK_LIST_CREATE_AS_NOT_OWNER();
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static void* thttp_stack_destroy(void * self)
|
|
{
|
|
thttp_stack_t *stack = self;
|
|
if(stack){
|
|
TSK_OBJECT_SAFE_FREE(stack->fsm);
|
|
|
|
/* Identity */
|
|
TSK_FREE(stack->username);
|
|
TSK_FREE(stack->password);
|
|
TSK_FREE(stack->proxy_host);
|
|
TSK_FREE(stack->proxy_username);
|
|
TSK_FREE(stack->proxy_password);
|
|
TSK_FREE(stack->user_agent);
|
|
|
|
/* Network */
|
|
TSK_FREE(stack->local_ip);
|
|
TSK_OBJECT_SAFE_FREE(stack->transport);
|
|
|
|
/* Operations */
|
|
tsk_safeobj_lock(stack);
|
|
TSK_OBJECT_SAFE_FREE(stack->ops);
|
|
tsk_safeobj_unlock(stack);
|
|
|
|
tsk_safeobj_deinit(stack);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static const tsk_object_def_t thttp_stack_def_s =
|
|
{
|
|
sizeof(thttp_stack_t),
|
|
_thttp_stack_create,
|
|
thttp_stack_destroy,
|
|
0,
|
|
};
|
|
const void *thttp_stack_def_t = &thttp_stack_def_s; |