aboutsummaryrefslogtreecommitdiffstats
path: root/lib/http_client.h
blob: d73894a4e81597c3c3ad0fddc085b7364b23262c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
  /********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2002-2005 Wilmer van der Gaast and others                *
  \********************************************************************/

/* HTTP(S) module                                                       */

/*
  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 2 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 with
  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  Suite 330, Boston, MA  02111-1307  USA
*/

/* http_client allows you to talk (asynchronously, again) to HTTP servers.
   In the "background" it will send the whole query and wait for a complete
   response to come back. Right now it's only used by the MSN Passport
   authentication code, but it might be useful for other things too (for
   example the AIM usericon patch uses this so icons can be stored on
   webservers instead of the local filesystem).
   
   Didn't test this too much, but it seems to work well. Just don't look
   at the code that handles HTTP 30x redirects. ;-) The function is
   probably not very useful for downloading lots of data since it keeps 
   everything in a memory buffer until the download is completed (and
   can't pass any data or whatever before then). It's very useful for
   doing quick requests without blocking the whole program, though. */

#include <glib.h>
#include "ssl_client.h"

struct http_request;

/* Your callback function should look like this: */
typedef void (*http_input_function)( struct http_request * );

/* This structure will be filled in by the http_dorequest* functions, and
   it will be passed to the callback function. Use the data field to add
   your own data. */
struct http_request
{
	char *request;          /* The request to send to the server. */
	int request_length;     /* Its size. */
	int status_code;        /* The numeric HTTP status code. (Or -1
	                           if something really went wrong) */
	char *status_string;    /* The error text. */
	char *reply_headers;
	char *reply_body;
	int body_size;          /* The number of bytes in reply_body. */
	int finished;           /* Set to non-0 if the request was completed
	                           successfully. */
	
	http_input_function func;
	gpointer data;
	
	/* Please don't touch the things down here, you shouldn't need them. */
	
	void *ssl;
	int fd;
	
	int inpa;
	int bytes_written;
	int bytes_read;
};

/* The _url variant is probably more useful than the raw version. The raw
   version is probably only useful if you want to do POST requests or if
   you want to add some extra headers. As you can see, HTTPS connections
   are also supported (using ssl_client). */
void *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data );
void *http_dorequest_url( char *url_string, http_input_function func, gpointer data );

void http_free( struct http_request *req );
522'>522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
/***************************************************************************\
*                                                                           *
*  BitlBee - An IRC to IM gateway                                           *
*  Simple module to facilitate twitter functionality.                       *
*                                                                           *
*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
*                                                                           *
*  This library is free software; you can redistribute it and/or            *
*  modify it under the terms of the GNU Lesser General Public               *
*  License as published by the Free Software Foundation, version            *
*  2.1.                                                                     *
*                                                                           *
*  This library 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        *
*  Lesser General Public License for more details.                          *
*                                                                           *
*  You should have received a copy of the GNU Lesser General Public License *
*  along with this library; if not, write to the Free Software Foundation,  *
*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
*                                                                           *
****************************************************************************/

/* For strptime(): */
#define _XOPEN_SOURCE

#include "twitter_http.h"
#include "twitter.h"
#include "bitlbee.h"
#include "url.h"
#include "misc.h"
#include "base64.h"
#include "xmltree.h"
#include "twitter_lib.h"
#include <ctype.h>
#include <errno.h>

#define TXL_STATUS 1
#define TXL_USER 2
#define TXL_ID 3

struct twitter_xml_list {
	int type;
	int next_cursor;
	GSList *list;
	gpointer data;
};

struct twitter_xml_user {
	char *name;
	char *screen_name;
};

struct twitter_xml_status {
	time_t created_at;
	char *text;
	struct twitter_xml_user *user;
	guint64 id;
};

/**
 * Frees a twitter_xml_user struct.
 */
static void txu_free(struct twitter_xml_user *txu)
{
	g_free(txu->name);
	g_free(txu->screen_name);
	g_free(txu);
}


/**
 * Frees a twitter_xml_status struct.
 */
static void txs_free(struct twitter_xml_status *txs)
{
	g_free(txs->text);
	txu_free(txs->user);
	g_free(txs);
}

/**
 * Free a twitter_xml_list struct.
 * type is the type of list the struct holds.
 */
static void txl_free(struct twitter_xml_list *txl)
{
	GSList *l;
	for ( l = txl->list; l ; l = g_slist_next(l) )
		if (txl->type == TXL_STATUS)
			txs_free((struct twitter_xml_status *)l->data);
		else if (txl->type == TXL_ID)
			g_free(l->data);
	g_slist_free(txl->list);
}

/**
 * Add a buddy if it is not allready added, set the status to logged in.
 */
static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
{
	struct twitter_data *td = ic->proto_data;

	// Check if the buddy is allready in the buddy list.
	if (!bee_user_by_handle( ic->bee, ic, name ))
	{
		char *mode = set_getstr(&ic->acc->set, "mode");
		
		// The buddy is not in the list, add the buddy and set the status to logged in.
		imcb_add_buddy( ic, name, NULL );
		imcb_rename_buddy( ic, name, fullname );
		if (g_strcasecmp(mode, "chat") == 0)
			imcb_chat_add_buddy( td->home_timeline_gc, name );
		else if (g_strcasecmp(mode, "many") == 0)
			imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
	}
}

static void twitter_http_get_friends_ids(struct http_request *req);

/**
 * Get the friends ids.
 */
void twitter_get_friends_ids(struct im_connection *ic, int next_cursor)
{
	struct twitter_data *td = ic->proto_data;

	// Primitive, but hey! It works...	
	char* args[2];
	args[0] = "cursor";
	args[1] = g_strdup_printf ("%d", next_cursor);
	twitter_http(TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, td->user, td->pass, td->oauth_info, args, 2);

	g_free(args[1]);
}

/**
 * Function to help fill a list.
 */
static xt_status twitter_xt_next_cursor( struct xt_node *node, struct twitter_xml_list *txl )
{
	// Do something with the cursor.
	txl->next_cursor = node->text != NULL ? atoi(node->text) : -1;

	return XT_HANDLED;
}

/**
 * Fill a list of ids.
 */
static xt_status twitter_xt_get_friends_id_list( struct xt_node *node, struct twitter_xml_list *txl )
{
	struct xt_node *child;
	
	// Set the list type.
	txl->type = TXL_ID;

	// The root <statuses> node should hold the list of statuses <status>
	// Walk over the nodes children.
	for( child = node->children ; child ; child = child->next )
	{
		if ( g_strcasecmp( "id", child->name ) == 0)
		{
			// Add the item to the list.
			txl->list = g_slist_append (txl->list, g_memdup( child->text, child->text_len + 1 ));
		}
		else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
		{
			twitter_xt_next_cursor(child, txl);
		}
	}

	return XT_HANDLED;
}

/**
 * Callback for getting the friends ids.
 */
static void twitter_http_get_friends_ids(struct http_request *req)
{
	struct im_connection *ic;
	struct xt_parser *parser;
	struct twitter_xml_list *txl;
	struct twitter_data *td;

	ic = req->data;

	// Check if the connection is still active.
	if( !g_slist_find( twitter_connections, ic ) )
		return;
	
	td = ic->proto_data;

	// Check if the HTTP request went well.
	if (req->status_code != 200) {
		// It didn't go well, output the error and return.
		if (++td->http_fails >= 5)
			imcb_error(ic, "Could not retrieve friends. HTTP STATUS: %d", req->status_code);
		
		return;
	} else {
		td->http_fails = 0;
	}

	txl = g_new0(struct twitter_xml_list, 1);

	// Parse the data.
	parser = xt_new( NULL, txl );
	xt_feed( parser, req->reply_body, req->body_size );
	twitter_xt_get_friends_id_list(parser->root, txl);
	xt_free( parser );

	if (txl->next_cursor)
		twitter_get_friends_ids(ic, txl->next_cursor);

	txl_free(txl);
	g_free(txl);
}

/**
 * Function to fill a twitter_xml_user struct.
 * It sets:
 *  - the name and
 *  - the screen_name.
 */
static xt_status twitter_xt_get_user( struct xt_node *node, struct twitter_xml_user *txu )
{
	struct xt_node *child;

	// Walk over the nodes children.
	for( child = node->children ; child ; child = child->next )
	{
		if ( g_strcasecmp( "name", child->name ) == 0)
		{
			txu->name = g_memdup( child->text, child->text_len + 1 );
		}
		else if (g_strcasecmp( "screen_name", child->name ) == 0)
		{
			txu->screen_name = g_memdup( child->text, child->text_len + 1 );
		}
	}
	return XT_HANDLED;
}

/**
 * Function to fill a twitter_xml_list struct.
 * It sets:
 *  - all <user>s from the <users> element.
 */
static xt_status twitter_xt_get_users( struct xt_node *node, struct twitter_xml_list *txl )
{
	struct twitter_xml_user *txu;
	struct xt_node *child;

	// Set the type of the list.
	txl->type = TXL_USER;

	// The root <users> node should hold the list of users <user>
	// Walk over the nodes children.
	for( child = node->children ; child ; child = child->next )
	{
		if ( g_strcasecmp( "user", child->name ) == 0)
		{
			txu = g_new0(struct twitter_xml_user, 1);
			twitter_xt_get_user(child, txu);
			// Put the item in the front of the list.
			txl->list = g_slist_prepend (txl->list, txu);
		}
	}

	return XT_HANDLED;
}

/**
 * Function to fill a twitter_xml_list struct.
 * It calls twitter_xt_get_users to get the <user>s from a <users> element.
 * It sets:
 *  - the next_cursor.
 */
static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_xml_list *txl )
{
	struct xt_node *child;

	// Set the type of the list.
	txl->type = TXL_USER;

	// The root <user_list> node should hold a users <users> element
	// Walk over the nodes children.
	for( child = node->children ; child ; child = child->next )
	{
		if ( g_strcasecmp( "users", child->name ) == 0)
		{
			twitter_xt_get_users(child, txl);
		}
		else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
		{
			twitter_xt_next_cursor(child, txl);
		}
	}

	return XT_HANDLED;
}


/**
 * Function to fill a twitter_xml_status struct.
 * It sets:
 *  - the status text and
 *  - the created_at timestamp and
 *  - the status id and
 *  - the user in a twitter_xml_user struct.
 */
static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml_status *txs )
{
	struct xt_node *child;

	// Walk over the nodes children.
	for( child = node->children ; child ; child = child->next )
	{
		if ( g_strcasecmp( "text", child->name ) == 0)
		{
			txs->text = g_memdup( child->text, child->text_len + 1 );
		}
		else if (g_strcasecmp( "created_at", child->name ) == 0)
		{
			struct tm parsed;
			
			/* Very sensitive to changes to the formatting of
			   this field. :-( Also assumes the timezone used
			   is UTC since C time handling functions suck. */
			if( strptime( child->text, "%a %b %d %H:%M:%S %z %Y", &parsed ) != NULL )
				txs->created_at = mktime_utc( &parsed );
		}
		else if (g_strcasecmp( "user", child->name ) == 0)
		{
			txs->user = g_new0(struct twitter_xml_user, 1);
			twitter_xt_get_user( child, txs->user );
		}
		else if (g_strcasecmp( "id", child->name ) == 0)
		{
			txs->id = g_ascii_strtoull (child->text, NULL, 10);
		}
	}
	return XT_HANDLED;
}

/**
 * Function to fill a twitter_xml_list struct.
 * It sets:
 *  - all <status>es within the <status> element and
 *  - the next_cursor.
 */
static xt_status twitter_xt_get_status_list( struct xt_node *node, struct twitter_xml_list *txl )
{
	struct twitter_xml_status *txs;
	struct xt_node *child;

	// Set the type of the list.
	txl->type = TXL_STATUS;

	// The root <statuses> node should hold the list of statuses <status>
	// Walk over the nodes children.
	for( child = node->children ; child ; child = child->next )
	{
		if ( g_strcasecmp( "status", child->name ) == 0)
		{
			txs = g_new0(struct twitter_xml_status, 1);
			twitter_xt_get_status(child, txs);
			// Put the item in the front of the list.
			txl->list = g_slist_prepend (txl->list, txs);
		}
		else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
		{
			twitter_xt_next_cursor(child, txl);
		}
	}

	return XT_HANDLED;
}

static void twitter_http_get_home_timeline(struct http_request *req);

/**
 * Get the timeline.
 */
void twitter_get_home_timeline(struct im_connection *ic, int next_cursor)
{
	struct twitter_data *td = ic->proto_data;

	char* args[4];
	args[0] = "cursor";
	args[1] = g_strdup_printf ("%d", next_cursor);
	if (td->home_timeline_id) {
		args[2] = "since_id";
		args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id);
	}

	twitter_http(TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, td->user, td->pass, td->oauth_info, args, td->home_timeline_id ? 4 : 2);

	g_free(args[1]);
	if (td->home_timeline_id) {
		g_free(args[3]);
	}
}

/**
 * Function that is called to see the statuses in a groupchat window.
 */
static void twitter_groupchat(struct im_connection *ic, GSList *list)
{
	struct twitter_data *td = ic->proto_data;
	GSList *l = NULL;
	struct twitter_xml_status *status;
	struct groupchat *gc;

	// Create a new groupchat if it does not exsist.
	if (!td->home_timeline_gc)
	{   
		char *name_hint = g_strdup_printf( "Twitter_%s", ic->acc->user );
		td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
		imcb_chat_name_hint( gc, name_hint );
		g_free( name_hint );
		// Add the current user to the chat...
		imcb_chat_add_buddy( gc, ic->acc->user );
	}
	else
	{   
		gc = td->home_timeline_gc;
	}

	for ( l = list; l ; l = g_slist_next(l) )
	{
		status = l->data;
		twitter_add_buddy(ic, status->user->screen_name, status->user->name);
		
		// Say it!
		if (g_strcasecmp(td->user, status->user->screen_name) == 0)
			imcb_chat_log (gc, "Your Tweet: %s", status->text);
		else
			imcb_chat_msg (gc, status->user->screen_name, status->text, 0, status->created_at );
		
		// Update the home_timeline_id to hold the highest id, so that by the next request
		// we won't pick up the updates allready in the list.
		td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
	}
}

/**
 * Function that is called to see statuses as private messages.
 */
static void twitter_private_message_chat(struct im_connection *ic, GSList *list)
{
	struct twitter_data *td = ic->proto_data;
	GSList *l = NULL;
	struct twitter_xml_status *status;
	char from[MAX_STRING];
	gboolean mode_one;
	
	mode_one = g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) == 0;

	if( mode_one )
	{
		g_snprintf( from, sizeof( from ) - 1, "twitter_%s", ic->acc->user );
		from[MAX_STRING-1] = '\0';
	}
	
	for ( l = list; l ; l = g_slist_next(l) )
	{
		char *text = NULL;
		
		status = l->data;
		
		if( mode_one )
			text = g_strdup_printf( "\002<\002%s\002>\002 %s",
			                        status->user->screen_name, status->text );
		else
			twitter_add_buddy(ic, status->user->screen_name, status->user->name);
		
		imcb_buddy_msg( ic,
		                mode_one ? from : status->user->screen_name,
		                mode_one ? text : status->text,
		                0, status->created_at );
		
		// Update the home_timeline_id to hold the highest id, so that by the next request
		// we won't pick up the updates allready in the list.
		td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
		
		g_free( text );
	}
}

/**
 * Callback for getting the home timeline.
 */
static void twitter_http_get_home_timeline(struct http_request *req)
{
	struct im_connection *ic = req->data;
	struct twitter_data *td;
	struct xt_parser *parser;
	struct twitter_xml_list *txl;

	// Check if the connection is still active.
	if( !g_slist_find( twitter_connections, ic ) )
		return;
	
	td = ic->proto_data;

	// Check if the HTTP request went well.
	if (req->status_code == 200)
	{
		td->http_fails = 0;
		if (!(ic->flags & OPT_LOGGED_IN))
			imcb_connected(ic);
	}
	else if (req->status_code == 401)
	{
		imcb_error( ic, "Authentication failure" );
		imc_logout( ic, FALSE );
		return;
	}
	else
	{
		// It didn't go well, output the error and return.
		if (++td->http_fails >= 5)
			imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ". HTTP STATUS: %d", req->status_code);
		
		return;
	}

	txl = g_new0(struct twitter_xml_list, 1);
	txl->list = NULL;

	// Parse the data.
	parser = xt_new( NULL, txl );
	xt_feed( parser, req->reply_body, req->body_size );
	// The root <statuses> node should hold the list of statuses <status>
	twitter_xt_get_status_list(parser->root, txl);
	xt_free( parser );

	// See if the user wants to see the messages in a groupchat window or as private messages.
	if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
		twitter_groupchat(ic, txl->list);
	else
		twitter_private_message_chat(ic, txl->list);

	// Free the structure.	
	txl_free(txl);
	g_free(txl);
}

/**
 * Callback for getting (twitter)friends...
 *
 * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has 
 * hundreds of friends?" you wonder? You probably not, since you are reading the source of 
 * BitlBee... Get a life and meet new people!
 */
static void twitter_http_get_statuses_friends(struct http_request *req)
{
	struct im_connection *ic = req->data;
	struct twitter_data *td;
	struct xt_parser *parser;
	struct twitter_xml_list *txl;
	GSList *l = NULL;
	struct twitter_xml_user *user;

	// Check if the connection is still active.
	if( !g_slist_find( twitter_connections, ic ) )
		return;
	
	td = ic->proto_data;
	
	// Check if the HTTP request went well.
	if (req->status_code != 200) {
		// It didn't go well, output the error and return.
		if (++td->http_fails >= 5)
			imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL " HTTP STATUS: %d", req->status_code);
		
		return;
	} else {
		td->http_fails = 0;
	}

	txl = g_new0(struct twitter_xml_list, 1);
	txl->list = NULL;

	// Parse the data.
	parser = xt_new( NULL, txl );
	xt_feed( parser, req->reply_body, req->body_size );

	// Get the user list from the parsed xml feed.
	twitter_xt_get_user_list(parser->root, txl);
	xt_free( parser );

	// Add the users as buddies.
	for ( l = txl->list; l ; l = g_slist_next(l) )
	{
		user = l->data;
		twitter_add_buddy(ic, user->screen_name, user->name);
	}

	// if the next_cursor is set to something bigger then 0 there are more friends to gather.
	if (txl->next_cursor > 0)
		twitter_get_statuses_friends(ic, txl->next_cursor);

	// Free the structure.
	txl_free(txl);
	g_free(txl);
}

/**
 * Get the friends.
 */
void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor)
{
	struct twitter_data *td = ic->proto_data;

	char* args[2];
	args[0] = "cursor";
	args[1] = g_strdup_printf ("%d", next_cursor);

	twitter_http(TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, td->user, td->pass, td->oauth_info, args, 2);

	g_free(args[1]);
}

/**
 * Callback after sending a new update to twitter.
 */
static void twitter_http_post_status(struct http_request *req)
{
	struct im_connection *ic = req->data;

	// Check if the connection is still active.
	if( !g_slist_find( twitter_connections, ic ) )
		return;

	// Check if the HTTP request went well.
	if (req->status_code != 200) {
		// It didn't go well, output the error and return.
		imcb_error(ic, "Could not post message... HTTP STATUS: %d", req->status_code);
		return;
	}
}

/**
 * Function to POST a new status to twitter.
 */ 
void twitter_post_status(struct im_connection *ic, char* msg)
{
	struct twitter_data *td = ic->proto_data;

	char* args[2];
	args[0] = "status";
	args[1] = msg;
	twitter_http(TWITTER_STATUS_UPDATE_URL, twitter_http_post_status, ic, 1, td->user, td->pass, td->oauth_info, args, 2);
//	g_free(args[1]);
}


/**
 * Function to POST a new message to twitter.
 */
void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
{
	struct twitter_data *td = ic->proto_data;

	char* args[4];
	args[0] = "screen_name";
	args[1] = who;
	args[2] = "text";
	args[3] = msg;
	// Use the same callback as for twitter_post_status, since it does basically the same.
	twitter_http(TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post_status, ic, 1, td->user, td->pass, td->oauth_info, args, 4);
//	g_free(args[1]);
//	g_free(args[3]);
}