doubango/trunk/tinyHTTP/src/thttp_session.c

508 lines
13 KiB
C

/*
* Copyright (C) 2009 Mamadou Diop.
*
* Contact: Mamadou Diop <diopmamadou(at)doubango.org>
*
* 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_session.c
* @brief HTTP/HTTPS session.
*
* @author Mamadou Diop <diopmamadou(at)doubango.org>
*
* @date Created: Sat Nov 8 16:54:58 2009 mdiop
*/
#include "tinyhttp/thttp_session.h"
#include "thttp.h"
#include "tinyhttp/thttp_action.h"
#include "tinyhttp/headers/thttp_header_Dummy.h"
#include "tinyhttp/headers/thttp_header_WWW_Authenticate.h"
#include "tsk_debug.h"
/**@defgroup thttp_session_group HTTP Session
*/
int thttp_session_signal(thttp_session_t *self, thttp_action_type_t atype);
/**Sets parameters.
*/
int __thttp_session_set(thttp_session_t *self, va_list* app)
{
thttp_session_param_type_t curr;
if(!self){
return -1;
}
while((curr=va_arg(*app, thttp_session_param_type_t)) != httpp_null){
switch(curr){
case httpp_option:
{ /* (thttp_session_option_t)ID_ENUM, (const char*)VALUE_STR */
thttp_session_option_t id = va_arg(*app, thttp_session_option_t);
const char* value = va_arg(*app, const char *);
tsk_options_add_option(&self->options, id, value);
break;
}
case httpp_header:
{ /* (const char*)NAME_STR, (const char*)VALUE_STR */
const char* name = va_arg(*app, const char *);
const char* value = va_arg(*app, const char *);
if(value == ((const char*)-1)){ /* UNSET */
tsk_params_remove_param(self->headers, name);
}
else{ /* SET */
tsk_params_add_param(&self->headers, name, value);
}
break;
}
case httpp_cred:
{ /* (const char*)USERNAME_STR, (const char*)PASSWORD_STR */
tsk_strupdate(&self->cred.usename, va_arg(*app, const char *));
tsk_strupdate(&self->cred.password, va_arg(*app, const char *));
break;
}
case httpp_userdata:
{ /* (const void*)USERDATA_PTR */
self->userdata = va_arg(*app, const void *);
break;
}
default:
{ /* va_list will be unsafe => exit */
TSK_DEBUG_ERROR("NOT SUPPORTED.");
goto bail;
}
} /* sxitch */
} /* while */
return 0;
bail:
return -2;
}
/**@ingroup thttp_session_group
* Creates new session.
* @param stack The HTTP/HTTPS @a stack to use. The @a stack shall be created using @ref thttp_stack_create.
* @param ... Any @b THTTP_SESSION_SET_*() macros. MUST ends with @ref THTTP_SESSION_SET_NULL().
* @retval A pointer to the newly created session.
* A session is a well-defined object.
*
* @code
thttp_session_handle_t * session = thttp_session_create(stack,
// session-level parameters
THTTP_SESSION_SET_PARAM("timeout", "6000"),
// session-level headers
THTTP_SESSION_SET_HEADER("Pragma", "No-Cache"),
THTTP_SESSION_SET_HEADER("Connection", "Keep-Alive"),
THTTP_SESSION_SET_HEADER("User-Agent", "doubango 1.0"),
THTTP_SESSION_SET_NULL());
* @endcode
*
* @sa @ref thttp_session_set
*/
thttp_session_handle_t* thttp_session_create(const thttp_stack_handle_t* stack, ...)
{
thttp_session_handle_t* ret = tsk_null;
if((ret = tsk_object_new(thttp_session_def_t, stack))){
va_list ap;
va_start(ap, stack);
if(__thttp_session_set(ret, &ap)){
TSK_OBJECT_SAFE_FREE(ret);
}
va_end(ap);
}
else{
TSK_DEBUG_ERROR("failed to create new HTTP/HTTPS session.");
}
return ret;
}
/**@ingroup thttp_session_group
* Updates the session parameters.
* @param self The session to update. The session shall be created using @ref thttp_session_create().
* @param ... Any @b THTTP_SESSION_SET_*() macros. MUST ends with @ref THTTP_SESSION_SET_NULL().
* @retval Zero if succeed and non zero error code otherwise.
*
* @code
int ret = thttp_session_set(session,
// session-level parameters
THTTP_SESSION_SET_OPTION(THTTP_SESSION_OPTION_TIMEOUT, "6000"),
// session-level headers
THTTP_SESSION_SET_HEADER("Pragma", "No-Cache"),
THTTP_SESSION_SET_HEADER("Connection", "Keep-Alive"),
THTTP_SESSION_SET_HEADER("User-Agent", "doubango 1.0"),
THTTP_SESSION_SET_NULL());
* @endcode
*
* @sa @ref thttp_session_create
*/
int thttp_session_set(thttp_session_handle_t *self, ...)
{
if(self){
int ret;
va_list ap;
thttp_session_t *session = self;
if(session->id == THTTP_SESSION_INVALID_ID){
TSK_DEBUG_ERROR("Using invalid session.");
return -2;
}
va_start(ap, self);
ret = __thttp_session_set(session, &ap);
va_end(ap);
return ret;
}
return -1;
}
/**@ingroup thttp_session_group
* Gets the session id.
* @param self The session for which to get the id.
* @retval The id of the session.
*/
thttp_session_id_t thttp_session_get_id(const thttp_session_handle_t *self)
{
const thttp_session_t *session = self;
if(session){
return session->id;
}
return THTTP_SESSION_INVALID_ID;
}
/**@ingroup thttp_session_group
* Gets the user context (user/application data).
* @param self A pointer to the session from which to get the context.
* @retval A pointer to the context. Previously defined by using @ref THTTP_SESSION_SET_CONTEXT() macro.
* @sa @ref THTTP_SESSION_SET_CONTEXT
*/
const void* thttp_session_get_userdata(const thttp_session_handle_t *self)
{
if(self){
return ((const thttp_session_t*)self)->userdata;
}
return tsk_null;
}
/** Updates authentications headers.
*/
int thttp_session_update_challenges(thttp_session_t *self, const thttp_response_t* response, tsk_bool_t answered)
{
int ret = 0;
tsk_size_t i;
tsk_list_item_t *item;
thttp_challenge_t *challenge;
const thttp_header_WWW_Authenticate_t *WWW_Authenticate;
const thttp_header_Proxy_Authenticate_t *Proxy_Authenticate;
tsk_safeobj_lock(self);
/* RFC 2617 - Digest Operation
* (A) The client response to a WWW-Authenticate challenge for a protection
space starts an authentication session with that protection space.
The authentication session lasts until the client receives another
WWW-Authenticate challenge from any server in the protection space.
(B) The server may return a 401 response with a new nonce value, causing the client
to retry the request; by specifying stale=TRUE with this response,
the server tells the client to retry with the new nonce, but without
prompting for a new username and password.
*/
/* RFC 2617 - 1.2 Access Authentication Framework
The realm directive (case-insensitive) is required for all authentication schemes that issue a challenge.
*/
/* FIXME: As we perform the same task ==> Use only one loop.
*/
for(i =0; (WWW_Authenticate = (const thttp_header_WWW_Authenticate_t*)thttp_message_get_headerAt(response, thttp_htype_WWW_Authenticate, i)); i++)
{
tsk_bool_t isnew = tsk_true;
tsk_list_foreach(item, self->challenges)
{
challenge = item->data;
if(challenge->isproxy){
continue;
}
if(tsk_striequals(challenge->realm, WWW_Authenticate->realm) && (WWW_Authenticate->stale || !answered)){
/*== (B) ==*/
if((ret = thttp_challenge_update(challenge,
WWW_Authenticate->scheme,
WWW_Authenticate->realm,
WWW_Authenticate->nonce,
WWW_Authenticate->opaque,
WWW_Authenticate->algorithm,
WWW_Authenticate->qop))){
return ret;
}
else{
isnew = tsk_false;
continue;
}
}
else{
ret = -1;
goto bail;
}
}
if(isnew){
if((challenge = thttp_challenge_create(tsk_false, /* Not proxy */
WWW_Authenticate->scheme,
WWW_Authenticate->realm,
WWW_Authenticate->nonce,
WWW_Authenticate->opaque,
WWW_Authenticate->algorithm,
WWW_Authenticate->qop))){
tsk_list_push_back_data(self->challenges, (void**)&challenge);
}
else{
ret = -1;
goto bail;
}
}
}
for(i=0; (Proxy_Authenticate = (const thttp_header_Proxy_Authenticate_t*)thttp_message_get_headerAt(response, thttp_htype_Proxy_Authenticate, i)); i++)
{
tsk_bool_t isnew = tsk_true;
tsk_list_foreach(item, self->challenges){
challenge = item->data;
if(!challenge->isproxy){
continue;
}
if(tsk_striequals(challenge->realm, Proxy_Authenticate->realm) && (Proxy_Authenticate->stale || !answered)){
/*== (B) ==*/
if((ret = thttp_challenge_update(challenge,
Proxy_Authenticate->scheme,
Proxy_Authenticate->realm,
Proxy_Authenticate->nonce,
Proxy_Authenticate->opaque,
Proxy_Authenticate->algorithm,
Proxy_Authenticate->qop)))
{
goto bail;
}
else{
isnew = tsk_false;
continue;
}
}
else{
ret = -1;
goto bail;
}
}
if(isnew){
if((challenge = thttp_challenge_create(tsk_true, /* Proxy */
Proxy_Authenticate->scheme,
Proxy_Authenticate->realm,
Proxy_Authenticate->nonce,
Proxy_Authenticate->opaque,
Proxy_Authenticate->algorithm,
Proxy_Authenticate->qop)))
{
tsk_list_push_back_data(self->challenges, (void**)&challenge);
}
else{
ret = -1;
goto bail;
}
}
}
bail:
tsk_safeobj_unlock(self);
return ret;
}
/* internal function */
int thttp_session_signal(thttp_session_t *self, thttp_action_type_t atype)
{
tsk_list_item_t *item;
if(!self){
TSK_DEBUG_ERROR("Invalid parameter");
return -1;
}
tsk_safeobj_lock(self);
again:
tsk_list_foreach(item, self->dialogs){
item = tsk_object_ref(item);
thttp_dialog_fsm_act((thttp_dialog_t*)item->data, atype, tsk_null, tsk_null);
/* As the above action could terminate the dialog (which means change the content of self->dialogs)
* => list becomes unsafe */
if(!(item = tsk_object_unref(item))){
goto again;
}
}
switch(atype){
case thttp_thttp_atype_closed:
self->fd = TNET_INVALID_FD;
break;
default:
break;
}
tsk_safeobj_unlock(self);
return 0;
}
/** Signals to all dialogs that the connection have been closed. */
int thttp_session_signal_closed(thttp_session_t *self)
{
return thttp_session_signal(self, thttp_thttp_atype_closed);
}
/** Signals to all dialogss that we got an error */
int thttp_session_signal_error(thttp_session_t *self)
{
return thttp_session_signal(self, thttp_atype_error);
}
/** Retrieves a session by fd */
thttp_session_t* thttp_session_get_by_fd(thttp_sessions_L_t* sessions, tnet_fd_t fd)
{
thttp_session_t* ret = tsk_null;
const tsk_list_item_t *item;
if(!sessions){
goto bail;
}
tsk_list_foreach(item, sessions){
if(((thttp_session_t*)item->data)->fd == fd){
ret = tsk_object_ref(item->data);
goto bail;
}
}
bail:
return ret;
}
//========================================================
// HTTP SESSION object definition
//
static tsk_object_t* _thttp_session_create(tsk_object_t * self, va_list * app)
{
thttp_session_t *session = self;
static thttp_session_id_t unique_id = THTTP_SESSION_INVALID_ID;
if(session){
tsk_safeobj_init(session);
session->stack = va_arg(*app, const thttp_stack_handle_t*);
session->options = tsk_list_create();
session->headers = tsk_list_create();
session->challenges = tsk_list_create();
session->dialogs = tsk_list_create();
session->fd = TNET_INVALID_FD;
session->id = THTTP_SESSION_INVALID_ID;
/* add the session to the stack */
if(session->stack){
session->id = ++unique_id;
tsk_list_push_back_data(session->stack->sessions, (void**)&session);
}
}
return self;
}
static tsk_object_t* thttp_session_destroy(tsk_object_t * self)
{
thttp_session_t *session = self;
if(session){
TSK_DEBUG_INFO("*** HTTP/HTTPS Session destroyed ***");
/* remove from the stack */
if(session->stack){
tsk_list_remove_item_by_data(session->stack->sessions, session);
}
TSK_OBJECT_SAFE_FREE(session->options);
TSK_OBJECT_SAFE_FREE(session->headers);
TSK_OBJECT_SAFE_FREE(session->challenges);
TSK_OBJECT_SAFE_FREE(session->dialogs);
// cred
TSK_FREE(session->cred.usename);
TSK_FREE(session->cred.password);
// fd
if(session->fd != TNET_INVALID_FD){
if(tnet_transport_remove_socket(session->stack->transport, &session->fd)){
tnet_sockfd_close(&session->fd);
}
}
tsk_safeobj_deinit(session);
}
return self;
}
static int thttp_session_cmp(const tsk_object_t *_session1, const tsk_object_t *_session2)
{
const thttp_session_t *session1 = _session1;
const thttp_session_t *session2 = _session2;
if(session1 && session2){
return (int)(session1->id-session2->id);
}
return -1;
}
static const tsk_object_def_t thttp_session_def_s =
{
sizeof(thttp_session_t),
_thttp_session_create,
thttp_session_destroy,
thttp_session_cmp,
};
const tsk_object_def_t *thttp_session_def_t = &thttp_session_def_s;