adium-1.4 3280:dcd570c0a79e: Added facebook auth code to adium-s...

commits at adium.im commits at adium.im
Thu Nov 18 04:21:11 UTC 2010


details:	http://hg.adium.im/adium-1.4/rev/dcd570c0a79e
revision:	3280:dcd570c0a79e
author:		Evan Schoenberg
date:		Wed Nov 17 21:13:09 2010 -0600

Added facebook auth code to adium-space instead of libpurple-space
Subject: adium-1.4 3281:d5ac572ea3a1: im.pidgin.adium.1-4 @ 89aca070b8bd35bf6c4cfd5d5ae781c813f555f5 which is libpurple 2.7.5, now with the change for external xmpp auth methods plucked and the facebook mech method removed from the libpurple build

details:	http://hg.adium.im/adium-1.4/rev/d5ac572ea3a1
revision:	3281:d5ac572ea3a1
author:		Evan Schoenberg
date:		Wed Nov 17 21:14:29 2010 -0600

im.pidgin.adium.1-4 @ 89aca070b8bd35bf6c4cfd5d5ae781c813f555f5 which is libpurple 2.7.5, now with the change for external xmpp auth methods plucked and the facebook mech method removed from the libpurple build
Subject: adium-1.4 3282:9ea8436c7bcf: Added our facebook OAUTH info

details:	http://hg.adium.im/adium-1.4/rev/9ea8436c7bcf
revision:	3282:9ea8436c7bcf
author:		Evan Schoenberg
date:		Wed Nov 17 21:15:58 2010 -0600

Added our facebook OAUTH info
Subject: adium-1.4 3283:f1997d7125f5: Add a RELEASE_BUILD preprocessor directive, and don't use a separate user name for beta builds. Fixes #14432

details:	http://hg.adium.im/adium-1.4/rev/f1997d7125f5
revision:	3283:f1997d7125f5
author:		Evan Schoenberg
date:		Wed Nov 17 22:20:06 2010 -0600

Add a RELEASE_BUILD preprocessor directive, and don't use a separate user name for beta builds. Fixes #14432

diffs (781 lines):

diff -r 18de5f703b00 -r f1997d7125f5 Adium.xcodeproj/project.pbxproj
--- a/Adium.xcodeproj/project.pbxproj	Wed Nov 17 16:21:27 2010 -0600
+++ b/Adium.xcodeproj/project.pbxproj	Wed Nov 17 22:20:06 2010 -0600
@@ -403,6 +403,10 @@
 		346BA33F0774C27400628F06 /* ESGlobalEventsPreferencesPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 346BA33D0774C27400628F06 /* ESGlobalEventsPreferencesPlugin.m */; };
 		346BA3450774C2D200628F06 /* ESGlobalEventsPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 346BA3430774C2D200628F06 /* ESGlobalEventsPreferences.m */; };
 		346BA3640774C35500628F06 /* GlobalEventsPreferences.nib in Resources */ = {isa = PBXBuildFile; fileRef = 346BA3630774C35400628F06 /* GlobalEventsPreferences.nib */; };
+		346CE8071294B53F00439BF2 /* fbapi.c in Sources */ = {isa = PBXBuildFile; fileRef = 346CE8051294B53F00439BF2 /* fbapi.c */; };
+		346CE8081294B53F00439BF2 /* fbapi.h in Headers */ = {isa = PBXBuildFile; fileRef = 346CE8061294B53F00439BF2 /* fbapi.h */; };
+		346CE80A1294B59900439BF2 /* auth_fb.c in Sources */ = {isa = PBXBuildFile; fileRef = 346CE8091294B59900439BF2 /* auth_fb.c */; };
+		346CE80E1294B5B000439BF2 /* auth_fb.h in Headers */ = {isa = PBXBuildFile; fileRef = 346CE80D1294B5B000439BF2 /* auth_fb.h */; };
 		346F5D3208A43E7B0055C610 /* CurrentTunes.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 346F5CB308A418FB0055C610 /* CurrentTunes.scpt */; };
 		3470BF600BC6F18500388232 /* AIMessageHistoryPreferencesWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3470BF5E0BC6F18500388232 /* AIMessageHistoryPreferencesWindowController.h */; };
 		3470BF610BC6F18500388232 /* AIMessageHistoryPreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3470BF5F0BC6F18500388232 /* AIMessageHistoryPreferencesWindowController.m */; };
@@ -3072,6 +3076,10 @@
 		346C9C1E0E70E1F8002314EE /* hu */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = hu; path = Resources/hu.lproj/StatusPreferences.nib; sourceTree = "<group>"; };
 		346C9C1F0E70E1F8002314EE /* hu */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = hu; path = Resources/hu.lproj/StatusSortConfiguration.nib; sourceTree = "<group>"; };
 		346C9C200E70E1F8002314EE /* hu */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = hu; path = "Plugins/WebKit Message View/hu.lproj/WebKitPreferencesView.nib"; sourceTree = "<group>"; };
+		346CE8051294B53F00439BF2 /* fbapi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fbapi.c; sourceTree = "<group>"; };
+		346CE8061294B53F00439BF2 /* fbapi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fbapi.h; sourceTree = "<group>"; };
+		346CE8091294B59900439BF2 /* auth_fb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = auth_fb.c; sourceTree = "<group>"; };
+		346CE80D1294B5B000439BF2 /* auth_fb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = auth_fb.h; sourceTree = "<group>"; };
 		346CFDC4087B7836009711C8 /* AdiumIdleManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AdiumIdleManager.h; path = Source/AdiumIdleManager.h; sourceTree = "<group>"; };
 		346CFDC5087B7836009711C8 /* AdiumIdleManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AdiumIdleManager.m; path = Source/AdiumIdleManager.m; sourceTree = "<group>"; };
 		346F5CB308A418FB0055C610 /* CurrentTunes.scpt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.scpt; name = CurrentTunes.scpt; path = Resources/CurrentTunes.scpt; sourceTree = "<group>"; };
@@ -5089,6 +5097,10 @@
 		11EE1B480CDCFAF40097F246 /* libpurple_extensions */ = {
 			isa = PBXGroup;
 			children = (
+				346CE8091294B59900439BF2 /* auth_fb.c */,
+				346CE80D1294B5B000439BF2 /* auth_fb.h */,
+				346CE8051294B53F00439BF2 /* fbapi.c */,
+				346CE8061294B53F00439BF2 /* fbapi.h */,
 				11EE1B490CDCFAF40097F246 /* oscar-adium.c */,
 				11EE1B4A0CDCFAF40097F246 /* oscar-adium.h */,
 				811033500CDE170B00EC6038 /* ssl-cdsa.m */,
@@ -9131,6 +9143,8 @@
 				345D68A20F1FD59F002F2D01 /* PurpleFacebookService.h in Headers */,
 				345D68A80F1FD5AA002F2D01 /* PurpleFacebookAccount.h in Headers */,
 				34064D110F21B34200AA6FE3 /* PurpleFacebookAccountViewController.h in Headers */,
+				346CE8081294B53F00439BF2 /* fbapi.h in Headers */,
+				346CE80E1294B5B000439BF2 /* auth_fb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -9529,6 +9543,7 @@
 			};
 			buildConfigurationList = DADE8E3A085507450062B664 /* Build configuration list for PBXProject "Adium" */;
 			compatibilityVersion = "Xcode 3.1";
+			developmentRegion = English;
 			hasScannedForEncodings = 1;
 			knownRegions = (
 				en,
@@ -10309,6 +10324,8 @@
 				345D68A30F1FD59F002F2D01 /* PurpleFacebookService.m in Sources */,
 				345D68A90F1FD5AA002F2D01 /* PurpleFacebookAccount.m in Sources */,
 				34064D100F21B34200AA6FE3 /* PurpleFacebookAccountViewController.m in Sources */,
+				346CE8071294B53F00439BF2 /* fbapi.c in Sources */,
+				346CE80A1294B59900439BF2 /* auth_fb.c in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff -r 18de5f703b00 -r f1997d7125f5 Frameworks/libpurple.framework/Versions/0/Headers/auth.h
--- a/Frameworks/libpurple.framework/Versions/0/Headers/auth.h	Wed Nov 17 16:21:27 2010 -0600
+++ b/Frameworks/libpurple.framework/Versions/0/Headers/auth.h	Wed Nov 17 22:20:06 2010 -0600
@@ -57,7 +57,9 @@
 #ifdef HAVE_CYRUS_SASL
 JabberSaslMech *jabber_auth_get_cyrus_mech(void);
 #endif
-JabberSaslMech *jabber_auth_get_fb_mech(void);
+
+void jabber_auth_add_mech(JabberSaslMech *);
+void jabber_auth_remove_mech(JabberSaslMech *);
 
 void jabber_auth_init(void);
 void jabber_auth_uninit(void);
diff -r 18de5f703b00 -r f1997d7125f5 Frameworks/libpurple.framework/Versions/0/libpurple
Binary file Frameworks/libpurple.framework/Versions/0/libpurple has changed
diff -r 18de5f703b00 -r f1997d7125f5 Plugins/Purple Service/adiumPurpleCore.m
--- a/Plugins/Purple Service/adiumPurpleCore.m	Wed Nov 17 16:21:27 2010 -0600
+++ b/Plugins/Purple Service/adiumPurpleCore.m	Wed Nov 17 22:20:06 2010 -0600
@@ -37,6 +37,11 @@
 #import <AIUtilities/AIFileManagerAdditions.h>
 #import <Adium/AIAccountControllerProtocol.h>
 
+
+#warning This include and the jabber_auth_add_mech() will be part of the FacebookXMPP account's initialization
+#import "auth.h"
+
+
 #pragma mark Debug
 // Debug ------------------------------------------------------------------------------------------------------
 static void adiumPurpleDebugPrint(PurpleDebugLevel level, const char *category, const char *debug_msg)
@@ -230,6 +235,8 @@
 #endif
 	
 	load_external_plugins();
+	
+	jabber_auth_add_mech(jabber_auth_get_fb_mech());
 }
 
 static void adiumPurpleCoreQuit(void)
diff -r 18de5f703b00 -r f1997d7125f5 Plugins/Purple Service/libpurple_extensions/auth_fb.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Purple Service/libpurple_extensions/auth_fb.c	Wed Nov 17 22:20:06 2010 -0600
@@ -0,0 +1,158 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+#include "internal.h"
+
+#include "account.h"
+#include "debug.h"
+#include "request.h"
+#include "util.h"
+#include "xmlnode.h"
+
+#include "jabber.h"
+#include "auth.h"
+
+#include "fbapi.h"
+
+#define PACKAGE "pidgin"
+
+static JabberSaslState
+fb_handle_challenge(JabberStream *js, xmlnode *packet,
+                            xmlnode **response, char **msg)
+{
+	xmlnode *reply = NULL;
+	gchar *challenge;
+	guchar *decoded;
+	gsize decoded_len;
+	gchar **pairs, *method, *nonce;
+	gsize i;
+	GString *request;
+	gchar *enc_out;
+
+	/* Get base64-encoded challenge from XML */
+	challenge = xmlnode_get_data(packet);
+	if (challenge == NULL) {
+		*msg = g_strdup(_("Invalid response from server"));
+		return JABBER_SASL_STATE_FAIL;
+	}
+
+	/* Decode challenge */
+	decoded = purple_base64_decode(challenge, &decoded_len);
+	if (decoded == NULL) {
+		purple_debug_error("jabber", "X-FACEBOOK-PLATFORM challenge "
+						   "wasn't valid base64: %s\n", challenge);
+	
+		*msg = g_strdup(_("Invalid response from server"));
+
+		g_free(challenge);
+		return JABBER_SASL_STATE_FAIL;
+	}
+	g_free(challenge);
+
+	/* NULL-terminate the challenge so we can parse it */
+	challenge = g_strndup((const gchar *)decoded, decoded_len);
+	g_free(decoded);
+	purple_debug_misc("jabber", "X-FACEBOOK-PLATFORM decoded "
+					  "challenge is %s\n", challenge);
+
+	/* Get method and nonce */
+	method = NULL;
+	nonce = NULL;
+	pairs = g_strsplit(challenge, "&", 0);
+	for (i = 0; pairs[i] != NULL; i++) {
+		if (g_str_has_prefix(pairs[i], "method=")) {
+			g_free(method);
+			// TODO: Should url decode this value
+			method = g_strdup(strchr(pairs[i], '=') + 1);
+		} else if (g_str_has_prefix(pairs[i], "nonce=")) {
+			g_free(nonce);
+			// TODO: Should url decode this value
+			nonce = g_strdup(strchr(pairs[i], '=') + 1);
+		}
+	}
+	g_strfreev(pairs);
+	if (!method || !nonce) {
+		purple_debug_error("jabber", "X-FACEBOOK-PLATFORM challenge "
+						   "is missing method or nonce: %s\n", challenge);
+		*msg = g_strdup(_("Invalid response from server"));
+
+		g_free(method);
+		g_free(nonce);
+		g_free(challenge);
+		return JABBER_SASL_STATE_FAIL;
+	}
+	g_free(challenge);
+
+	request = purple_fbapi_construct_request(purple_connection_get_account(js->gc),
+											 method,
+											 "v", "1.0",
+											 "session_key", purple_connection_get_password(js->gc),
+											 "nonce", nonce,
+											 NULL);
+	g_free(method);
+	g_free(nonce);
+
+	purple_debug_misc("jabber", "X-FACEBOOK-PLATFORM response before "
+					  "encoding is %s\n", request->str);
+	enc_out = purple_base64_encode((const guchar *)request->str, request->len);
+	g_string_free(request, TRUE);
+
+	reply = xmlnode_new("response");
+	xmlnode_set_namespace(reply, NS_XMPP_SASL);
+	xmlnode_insert_data(reply, enc_out, -1);
+
+	g_free(enc_out);
+
+	*response = reply;
+
+	return JABBER_SASL_STATE_CONTINUE;
+}
+
+static JabberSaslState
+fb_start(JabberStream *js, xmlnode *packet, xmlnode **response, char **error)
+{
+
+	/* When connecting with X-FACEBOOK-PLATFORM, the password field must be set to the
+	 * OAUTH 2.0 session key.
+	 */
+	xmlnode *auth = xmlnode_new("auth");
+	xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
+	xmlnode_set_attrib(auth, "mechanism", "X-FACEBOOK-PLATFORM");
+
+	*response = auth;
+	return JABBER_SASL_STATE_CONTINUE;
+}
+
+static JabberSaslMech fb_mech = {
+	255, /* priority */
+	"X-FACEBOOK-PLATFORM", /* name */
+	fb_start,
+	fb_handle_challenge, /* handle_challenge */
+	NULL, /* handle_success */
+	NULL, /* handle_failure */
+	NULL  /* dispose */
+};
+
+JabberSaslMech *jabber_auth_get_fb_mech(void)
+{
+	return &fb_mech;
+}
diff -r 18de5f703b00 -r f1997d7125f5 Plugins/Purple Service/libpurple_extensions/auth_fb.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Purple Service/libpurple_extensions/auth_fb.h	Wed Nov 17 22:20:06 2010 -0600
@@ -0,0 +1,4 @@
+#include "jabber.h"
+#include "auth.h"
+
+JabberSaslMech *jabber_auth_get_fb_mech(void)
\ No newline at end of file
diff -r 18de5f703b00 -r f1997d7125f5 Plugins/Purple Service/libpurple_extensions/fbapi.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Purple Service/libpurple_extensions/fbapi.c	Wed Nov 17 22:20:06 2010 -0600
@@ -0,0 +1,376 @@
+/*
+ * This is the property of its developers.  See the COPYRIGHT file
+ * for more details.
+ *
+ * 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 <sys/time.h>
+
+#include "internal.h"
+#include "cipher.h"
+#include "debug.h"
+#include "util.h"
+
+#include "fbapi.h"
+
+#define PACKAGE "pidgin"
+
+#define API_URL "http://api.facebook.com/restserver.php"
+#define API_SECRET "5bc26e26557c4bda21d924bb527af4ba"
+#define MAX_CONNECTION_ATTEMPTS 3
+
+struct _PurpleFbApiCall {
+	gchar *request;
+	PurpleUtilFetchUrlData *url_data;
+	PurpleFbApiCallback callback;
+	gpointer user_data;
+	GDestroyNotify user_data_destroy_func;
+	unsigned int attempt_number;
+};
+
+static GSList *apicalls = NULL;
+
+/*
+ * Try to strip characters that are not valid XML.  The string is
+ * changed in-place.  This was needed because of this bug:
+ * http://bugs.developers.facebook.com/show_bug.cgi?id=2840
+ * That bug has been fixed, so it's possible this isn't necessary
+ * anymore.
+ *
+ * This page lists which characters are valid:
+ * http://www.w3.org/TR/2008/REC-xml-20081126/#charsets
+ *
+ * Valid XML characters are:
+ * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+ *
+ * Invalid XML characters are:
+ * [#x0-#x8] | #xB | #xC | [#xE-#x1F] | [#xD800-#xDFFF] | #xFFFE | #xFFFF
+ * | [#x110000-#xFFFFFFFF]
+ *
+ * Note: We could maybe use purple_utf8_strip_unprintables() for this (that
+ *       function was added after we had already started using this), but
+ *       we know this function works and changing it is scary.
+ */
+static void purple_fbapi_xml_salvage(char *str)
+{
+	gchar *tmp;
+	gunichar unichar;
+
+	for (tmp = str; tmp[0] != '\0'; tmp = g_utf8_next_char(tmp))
+	{
+		unichar = g_utf8_get_char(tmp);
+		if ((unichar >= 0x1 && unichar <= 0x8)
+				|| unichar == 0xb
+				|| unichar == 0xc
+				|| (unichar >= 0xe && unichar <= 0x1f)
+				|| (unichar >= 0xd800 && unichar <= 0xdfff)
+				|| unichar == 0xfffe
+				|| unichar == 0xffff
+				|| unichar >= 0x110000)
+		{
+			/* This character is not valid XML so replace it with question marks */
+			purple_debug_error("fbapi", "Replacing invalid "
+					"XML character %08x with question marks\n",
+					unichar);
+
+			tmp[0] = '?';
+			if (unichar & 0x0000ff00)
+				tmp[1] = '?';
+			if (unichar & 0x00ff0000)
+				tmp[2] = '?';
+			if (unichar & 0xff000000)
+				tmp[3] = '?';
+		}
+	}
+}
+
+static void purple_fbapi_request_fetch_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+	PurpleFbApiCall *apicall;
+	xmlnode *response;
+	PurpleConnectionError error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
+	char *error_message2 = NULL;
+
+	apicall = user_data;
+
+	if (error_message != NULL) {
+		/* Request failed */
+
+		if (apicall->attempt_number < MAX_CONNECTION_ATTEMPTS) {
+			/* Retry! */
+			apicall->url_data = purple_util_fetch_url_request(API_URL,
+					TRUE, NULL, FALSE, apicall->request, FALSE,
+					purple_fbapi_request_fetch_cb, apicall);
+			apicall->attempt_number++;
+			return;
+		}
+
+		response = NULL;
+		error_message2 = g_strdup(error_message);
+		error = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
+	} else if (url_text != NULL && len > 0) {
+		/* Parse the response as XML */
+		response = xmlnode_from_str(url_text, len);
+
+		if (response == NULL)
+		{
+			gchar *salvaged;
+
+			if (g_utf8_validate(url_text, len, NULL)) {
+				salvaged = g_strdup(url_text);
+			} else {
+				/* Facebook responded with invalid UTF-8.  Bastards. */
+				purple_debug_error("fbapi", "Response is not valid UTF-8\n");
+				salvaged = purple_utf8_salvage(url_text);
+			}
+
+			purple_fbapi_xml_salvage(salvaged);
+			response = xmlnode_from_str(salvaged, -1);
+			g_free(salvaged);
+		}
+
+		if (response == NULL) {
+			purple_debug_error("fbapi", "Could not parse response as XML: %*s\n",
+			(int)len, url_text);
+			error_message2 = g_strdup(_("Invalid response from server"));
+		} else if (g_str_equal(response->name, "error_response")) {
+			/*
+			 * The response is an error message, in the standard format
+			 * for errors from API calls.
+			 */
+			xmlnode *tmp;
+			char *tmpstr;
+
+			tmp = xmlnode_get_child(response, "error_code");
+			if (tmp != NULL) {
+				tmpstr = xmlnode_get_data_unescaped(tmp);
+				if (tmpstr != NULL && strcmp(tmpstr, "293") == 0) {
+					error_message2 = g_strdup(_("Need chat permission"));
+					error = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
+				}
+				g_free(tmpstr);
+			}
+			if (error_message2 == NULL) {
+				error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
+				tmp = xmlnode_get_child(response, "error_msg");
+				if (tmp != NULL)
+					error_message2 = xmlnode_get_data_unescaped(tmp);
+			}
+			if (error_message2 == NULL)
+				error_message2 = g_strdup(_("Unknown"));
+		} else {
+			error_message2 = NULL;
+		}
+	} else {
+		/* Response body was empty */
+		response = NULL;
+		error_message2 = NULL;
+	}
+
+	if (apicall->attempt_number > 1 || error_message2 != NULL)
+		purple_debug_error("fbapi", "Request '%s' %s after %u attempts: %s\n",
+				apicall->request,
+				error_message == NULL ? "succeeded" : "failed",
+				apicall->attempt_number, error_message2);
+
+	/*
+	 * The request either succeeded or failed the maximum number of
+	 * times.  In either case, pass control off to the callback
+	 * function and let them decide what to do.
+	 */
+	apicall->callback(apicall, apicall->user_data, response, error, error_message2);
+	apicall->url_data = NULL;
+	purple_fbapi_request_destroy(apicall);
+
+	xmlnode_free(response);
+	g_free(error_message2);
+}
+
+static gboolean concat_params(gpointer key, gpointer value, gpointer data)
+{
+	GString *tmp;
+
+	tmp = data;
+	g_string_append_printf(tmp, "%s=%s", (const char *)key, (const char *)value);
+
+	return FALSE;
+}
+
+/**
+ * @return A Newly allocated base16 encoded version of the md5
+ *         signature calculated using the algorithm described on the
+ *         Facebook developer wiki.  This string must be g_free'd.
+ */
+static char *generate_signature(const char *api_secret, const GTree *params)
+{
+	GString *tmp;
+	unsigned char hashval[16];
+
+	tmp = g_string_new(NULL);
+	g_tree_foreach((GTree *)params, concat_params, tmp);
+	g_string_append(tmp, api_secret);
+
+	purple_cipher_digest_region("md5", (const unsigned char *)tmp->str,
+			tmp->len, sizeof(hashval), hashval, NULL);
+	g_string_free(tmp, TRUE);
+
+	return purple_base16_encode(hashval, sizeof(hashval));
+}
+
+static gboolean append_params_to_body(gpointer key, gpointer value, gpointer data)
+{
+	GString *body;
+
+	body = data;
+
+	if (body->len > 0)
+		g_string_append_c(body, '&');
+
+	g_string_append(body, purple_url_encode(key));
+	g_string_append_c(body, '=');
+	g_string_append(body, purple_url_encode(value));
+
+	return FALSE;
+}
+
+static GString *purple_fbapi_construct_request_vargs(PurpleAccount *account, const char *method, va_list args)
+{
+	GTree *params;
+	const char *api_key, *api_secret;
+	const char *key, *value;
+	char call_id[21];
+	char *signature;
+	GString *body;
+
+	/* Read all paramters into a sorted tree */
+	params = g_tree_new((GCompareFunc)strcmp);
+	while ((key = va_arg(args, const char *)) != NULL)
+	{
+		value = va_arg(args, const char *);
+		g_tree_insert(params, (char *)key, (char *)value);
+
+		/* If we have a session_key then we need a call_id */
+		if (g_str_equal(key, "session_key")) {
+			struct timeval tv;
+			if (gettimeofday(&tv, NULL) != 0) {
+				time_t now;
+				purple_debug_error("fbapi",
+						"Error calling gettimeofday(): %s\n",
+						g_strerror(errno));
+				now = time(NULL);
+				strftime(call_id, sizeof(call_id), "%s000000", localtime(&now));
+			} else {
+				char tmp[22];
+				strftime(tmp, sizeof(tmp), "%s", localtime(&tv.tv_sec));
+				sprintf(call_id, "%s%06lu", tmp, (long unsigned int)tv.tv_usec);
+			}
+			g_tree_insert(params, "call_id", call_id);
+		}
+	}
+
+	api_key = purple_account_get_string(account, "fb_api_key", PURPLE_FBAPI_KEY);
+	api_secret = purple_account_get_string(account, "fb_api_secret", API_SECRET);
+
+	/* Add the method and api_key parameters to the list */
+	g_tree_insert(params, "method", (char *)method);
+	g_tree_insert(params, "api_key", (char *)api_key);
+
+	/* Add the signature parameter to the list */
+	signature = generate_signature((char *)api_secret, params);
+	g_tree_insert(params, "sig", signature);
+
+	/* Construct the body of the HTTP POST request */
+	body = g_string_new(NULL);
+	g_tree_foreach(params, append_params_to_body, body);
+	g_tree_destroy(params);
+	g_free(signature);
+
+	return body;
+}
+
+GString *purple_fbapi_construct_request(PurpleAccount *account, const char *method, ...)
+{
+	va_list args;
+	GString *body;
+
+	va_start(args, method);
+	body = purple_fbapi_construct_request_vargs(account, method, args);
+	va_end(args);
+
+	return body;
+}
+
+PurpleFbApiCall *purple_fbapi_request_vargs(PurpleAccount *account, PurpleFbApiCallback callback, gpointer user_data, GDestroyNotify user_data_destroy_func, const char *method, va_list args)
+{
+	GString *body;
+	PurpleFbApiCall *apicall;
+
+	body = purple_fbapi_construct_request_vargs(account, method, args);
+
+	/* Construct an HTTP POST request */
+	apicall = g_new(PurpleFbApiCall, 1);
+	apicall->callback = callback;
+	apicall->user_data = user_data;
+	apicall->user_data_destroy_func = user_data_destroy_func;
+	apicall->attempt_number = 1;
+
+	apicall->request = g_strdup_printf("POST /restserver.php HTTP/1.0\r\n"
+			"Connection: close\r\n"
+			"Accept: */*\r\n"
+			"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n"
+			"Content-Length: %zu\r\n\r\n%s", (size_t)body->len, body->str);
+	g_string_free(body, TRUE);
+
+	apicall->url_data = purple_util_fetch_url_request(API_URL,
+			TRUE, NULL, FALSE, apicall->request, FALSE,
+			purple_fbapi_request_fetch_cb, apicall);
+
+	apicalls = g_slist_prepend(apicalls, apicall);
+
+	return apicall;
+}
+
+PurpleFbApiCall *purple_fbapi_request(PurpleAccount *account, PurpleFbApiCallback callback, gpointer user_data, GDestroyNotify user_data_destroy_func, const char *method, ...)
+{
+	va_list args;
+	PurpleFbApiCall *apicall;
+
+	va_start(args, method);
+	apicall = purple_fbapi_request_vargs(account, callback, user_data, user_data_destroy_func, method, args);
+	va_end(args);
+
+	return apicall;
+}
+
+void purple_fbapi_request_destroy(PurpleFbApiCall *apicall)
+{
+	apicalls = g_slist_remove(apicalls, apicall);
+
+	if (apicall->url_data != NULL)
+		purple_util_fetch_url_cancel(apicall->url_data);
+
+	if (apicall->user_data != NULL && apicall->user_data_destroy_func != NULL)
+		apicall->user_data_destroy_func(apicall->user_data);
+
+	g_free(apicall->request);
+	g_free(apicall);
+}
+
+void purple_fbapi_uninit(void)
+{
+	while (apicalls != NULL)
+		purple_fbapi_request_destroy(apicalls->data);
+}
diff -r 18de5f703b00 -r f1997d7125f5 Plugins/Purple Service/libpurple_extensions/fbapi.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Purple Service/libpurple_extensions/fbapi.h	Wed Nov 17 22:20:06 2010 -0600
@@ -0,0 +1,106 @@
+/*
+ * libfacebook
+ *
+ * libfacebook is the property of its developers.  See the COPYRIGHT file
+ * for more details.
+ *
+ * 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/>.
+ */
+
+#ifndef FBAPI_H
+#define FBAPI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <glib.h>
+
+#include "connection.h"
+
+#define PURPLE_FBAPI_KEY "819fd0b010d15eac1b36648f0126ece3"
+
+typedef struct _PurpleFbApiCall PurpleFbApiCall;
+
+/**
+ * This is the callback function when a response is received to an API
+ * request.  The response will always be parsed as XML.
+ *
+ * error_message will be set if the physical TCP connection failed, or
+ * if the API call returned <error_response> as the top level node in
+ * the document.
+ *
+ * error will be set if and only if error_message is set.
+ *
+ * response will be null if error_message is non-null or if the
+ * response was not valid XML.  So if error_message == NULL &&
+ * response == NULL then you know the body was malformed XML.
+ */
+typedef void (*PurpleFbApiCallback)(PurpleFbApiCall *apicall, gpointer user_data, const xmlnode *response, PurpleConnectionError error, const gchar *error_message);
+
+/**
+ * Construct the body of a Facebook API request.
+ *
+ * @param account PurpleAccount of the user
+ * @param method The API method to call.  For example, auth.getSession or
+ *        events.get.
+ * @param attrs key/value pairs of request arguments.  The list must be
+ *        terminated with a NULL.  It should not contain the method,
+ *        api_key, call_id, or sig parameters--these will be appended
+ *        for you.
+ */
+GString *purple_fbapi_construct_request(PurpleAccount *account, const char *method, ...) G_GNUC_NULL_TERMINATED;
+
+/**
+ * @param account PurpleAccount of the user
+ * @param args key/value pairs that will be POSTed to the API URL.  The
+ *        list must be terminated with a NULL.  It should not contain
+ *        the method, api_key, call_id, or sig parameters--these will be
+ *        appended for you.
+ * @see purple_fbapi_request
+ */
+PurpleFbApiCall *purple_fbapi_request_vargs(PurpleAccount *account, PurpleFbApiCallback callback, gpointer user_data, GDestroyNotify user_data_destroy_func, const char *method, va_list args);
+
+/**
+ * @param account PurpleAccount of the user
+ * @param callback The callback function that should be called when we
+ *        receive a response from the server.
+ * @param user_data Optional data to pass to the callback function.
+ * @param user_data_destroy_func An option function to be called and
+ *        passed user_data to free it after this request has finished
+ *        or been canceled.
+ * @param method The API method to call.  For example, auth.getSession or
+ *        events.get.
+ * @param attrs key/value pairs that will be POSTed to the API URL.  The
+ *        list must be terminated with a NULL.  It should not contain
+ *        the method, api_key, call_id, or sig parameters--these will be
+ *        appended for you.
+ */
+PurpleFbApiCall *purple_fbapi_request(PurpleAccount *account, PurpleFbApiCallback callback, gpointer user_data, GDestroyNotify user_data_destroy_func, const char *method, ...) G_GNUC_NULL_TERMINATED;
+
+/*
+ * Destroy a single pending API request.
+ */
+void purple_fbapi_request_destroy(PurpleFbApiCall *apicall);
+
+/**
+ * Destroy all pending API requests.
+ */
+void purple_fbapi_uninit(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FBAPI_H */
diff -r 18de5f703b00 -r f1997d7125f5 Source/AILoginController.m
--- a/Source/AILoginController.m	Wed Nov 17 16:21:27 2010 -0600
+++ b/Source/AILoginController.m	Wed Nov 17 22:20:06 2010 -0600
@@ -110,8 +110,14 @@
 			shouldShowWindow = (userRequestedShowWindow = [[loginDict objectForKey:LOGIN_SHOW_WINDOW] boolValue]);
 		if (!shouldShowWindow) {
 #ifdef DEBUG_BUILD
+	#ifndef RELEASE_BUILD
+			/* Support a different default user for 'Debug' builds but not for 'Release-Debug' builds.
+			 * The former are for developers, who may want this behavior.
+			 * The latter are for beta testers, who are more likely to be just confused (as per #14432).
+			 */
 			userName = [loginDict objectForKey:LOGIN_LAST_USER_DEBUG];
 			if (!userName)
+	#endif
 #endif
 				shouldShowWindow = ((userName = [loginDict objectForKey:LOGIN_LAST_USER]) == nil);
 		}




More information about the commits mailing list